Well, worked on this little Game yesterday.
Its basically Conways Game of Life on a 50×33 Grid, which atm. must be predefined in the sketch.
The Output is visible on a ili9341-screen, but may i’ll try it with the oled too.
It’s not that of a big project, but still a little bit funny
Download(Puush)
Preloaded with gospers glider gun ^^
multi is for the multiplicator, which scales the grid up. maxY and MaxX stand for the grid sizes (which may also needs to be changed)
I tried with 80×80, but its still “slow”. 100×100 didnt worked, 4000bytes too big for the ram (was the error). One way would be saving the grid to an external eeprom.
~Straw
Memory is not a problem if you store your grid of cells packed into bits:
Sketch uses 14,788 bytes (13%) of program storage space. Maximum is 110,592 bytes.
Global variables use 4,256 bytes of dynamic memory.
// Conway's Game Of Life 256x256
// PaulRB
// Jun 2015
#include <SPI.h>
//Pins controlling SSD1306 Graphic OLED
#define OLED_DC 1
#define OLED_CS 0
#define OLED_RESET 2
#define OLED_COLS 128
#define OLED_ROWS 8
#define MATRIX_COLS 256
#define MATRIX_ROWS 32
union MatrixData {
unsigned long long l[MATRIX_ROWS/8];
byte b[MATRIX_ROWS];
};
#define SW_U 17
#define SW_D 20
#define SW_L 18
#define SW_R 16
#define SW_C 19
int scrollHorz = 0;
int scrollVert = 0;
MatrixData Matrix[MATRIX_COLS+1]; // Cell data in ram
void setup() {
pinMode(OLED_DC, OUTPUT);
pinMode(OLED_CS, OUTPUT);
pinMode(OLED_RESET, OUTPUT);
SPI.begin();
SPI.setBitOrder(MSBFIRST); // Set the SPI bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV2); // 2.25 MHz
digitalWrite(OLED_RESET, HIGH);
delay(1);
digitalWrite(OLED_RESET, LOW);
delay(10);
digitalWrite(OLED_RESET, HIGH);
digitalWrite(OLED_DC, LOW);
digitalWrite(OLED_CS, LOW);
SPI.transfer(0xAE); // Display off
SPI.transfer(0xD5); // Set display clock divider
SPI.transfer(0x80);
SPI.transfer(0xA8); // Set multiplex
SPI.transfer(0x3F);
SPI.transfer(0xD3); // Set display offset
SPI.transfer(0x00);
SPI.transfer(0x40); // Set start line to zero
SPI.transfer(0x8D); // Set charge pump
SPI.transfer(0x14);
SPI.transfer(0x20); // Set memory mode
SPI.transfer(0x00);
SPI.transfer(0xA0 | 0x1); // Set segment remapping
SPI.transfer(0xC8); // Set command Scan decode
SPI.transfer(0xDA); // Set Comm pins
SPI.transfer(0x12);
SPI.transfer(0x81); // Set contrast
SPI.transfer(0xCF);
SPI.transfer(0xd9); // Set precharge
SPI.transfer(0xF1);
SPI.transfer(0xDB); // Set Vcom detect
SPI.transfer(0x40);
SPI.transfer(0xA4); // Allow display resume
SPI.transfer(0xA6); // Set normal display
SPI.transfer(0xAF); // Display On
digitalWrite(OLED_CS, HIGH);
//R-pentomino
//Matrix[64].l[4] = 0b0000010;
//Matrix[65].l[4] = 0b0000111;
//Matrix[66].l[4] = 0b0000100;
//Gosper's Glider Gun
//Matrix[64].l[4] = 0b00000000000000000000000000010000000000000000000000;
//Matrix[65].l[4] = 0b00000000000000000000000001010000000000000000000000;
//Matrix[66].l[4] = 0b00000000000000011000000110000000000001100000000000;
//Matrix[67].l[4] = 0b00000000000000100010000110000000000001100000000000;
//Matrix[68].l[4] = 0b00011000000001000001000110000000000000000000000000;
//Matrix[69].l[4] = 0b00011000000001000101100001010000000000000000000000;
//Matrix[70].l[4] = 0b00000000000001000001000000010000000000000000000000;
//Matrix[71].l[4] = 0b00000000000000100010000000000000000000000000000000;
//Matrix[72].l[4] = 0b00000000000000011000000000000000000000000000000000;
randomiseMatrix();
outputMatrix();
Serial.begin(115200);
pinMode(SW_U, INPUT_PULLUP);
pinMode(SW_D, INPUT_PULLUP);
pinMode(SW_L, INPUT_PULLUP);
pinMode(SW_R, INPUT_PULLUP);
pinMode(SW_C, INPUT_PULLUP);
}
void loop() {
unsigned long start = millis();
for (int i=0; i<1000; i++) {
generateMatrix();
outputMatrix();
}
//Serial.print("Gens/s:"); Serial.println(1000000UL/(millis() - start));
}
void outputMatrix() {
digitalWrite(OLED_DC, LOW); //Command mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus
SPI.transfer(0x21); // Set column address
SPI.transfer(0);
SPI.transfer(OLED_COLS-1);
SPI.transfer(0x22); // Set page address
SPI.transfer(0);
SPI.transfer(OLED_ROWS-1);
digitalWrite(OLED_CS, HIGH); //Disable display on SPI bus
digitalWrite(OLED_DC, HIGH); // Data mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus
if (digitalRead(SW_L) == LOW && scrollHorz < (MATRIX_COLS - OLED_COLS)) scrollHorz++;
if (digitalRead(SW_R) == LOW && scrollHorz > 0) scrollHorz--;
if (digitalRead(SW_U) == LOW && scrollVert < ((MATRIX_ROWS - OLED_ROWS)<<3)) scrollVert++;
if (digitalRead(SW_D) == LOW && scrollVert > 0) scrollVert--;
//Send matrix data for display on OLED
for (int col = 0; col < OLED_ROWS; col++) {
int colScrolled = col + (scrollVert>>3);
for (int row = 0; row < OLED_COLS; row++) {
SPI.transfer(Matrix[row+scrollHorz].b[colScrolled]);
}
}
digitalWrite(OLED_CS, HIGH);
}
void randomiseMatrix() {
//Set up initial cells in matrix
randomSeed(analogRead(0));
for (int row = 0; row < MATRIX_COLS; row++) {
for (int col = 0; col < MATRIX_ROWS; col++) {
Matrix[row].b[col] = random(0xff);
}
}
}
void injectGlider() {
int col = random(MATRIX_COLS);
int row = random(MATRIX_ROWS);
Matrix[col++].b[row] |= 0b0000111;
Matrix[col++].b[row] |= 0b0000001;
Matrix[col++].b[row] |= 0b0000010;
}
void rotateLeft(unsigned long long x[]) {
unsigned long long c = 0;
for (int i=0; i<4; i++) {
unsigned long long c2 = x[i] >> 63;
x[i] = x[i] << 1 | c;
c = c2;
}
x[0] |= c;
}
void rotateRight(unsigned long long x[]) {
unsigned long long c = 0;
for (int i=3; i>=0; i--) {
unsigned long long c2 = x[i] << 63;
x[i] = x[i] >> 1 | c;
c = c2;
}
x[3] |= c;
}
int generateMatrix() {
//Variables holding data on neighbouring cells
MatrixData NeighbourN, NeighbourNW, NeighbourNE, CurrCells, NeighbourW, NeighbourE, NeighbourS, NeighbourSW, NeighbourSE;
//Variables used in calculating new cells
unsigned long long tot1, carry, tot2, tot4, NewCells;
int changes = 0; // counts the changes in the matrix
static int prevChanges[4]; // counts the changes in the matrix on prev 4 generations
static int staleCount = 0; // counts the consecutive occurrances of the same number of changes in the matrix
//set up N, NW, NE, W & E neighbour data
NeighbourN = Matrix[MATRIX_COLS-1];
CurrCells = Matrix[0];
Matrix[MATRIX_COLS] = CurrCells; // copy row 0 to location after last row to remove need for wrap-around code in the loop
NeighbourNW = NeighbourN;
rotateLeft(NeighbourNW.l);
NeighbourNE = NeighbourN;
rotateRight(NeighbourNE.l);
NeighbourW = CurrCells;
rotateLeft(NeighbourW.l);
NeighbourE = CurrCells;
rotateRight(NeighbourE.l);
//Process each row of the matrix
for (int row = 0; row < MATRIX_COLS; row++) {
//Pick up new S, SW & SE neighbours
NeighbourS = Matrix[row + 1];
NeighbourSW = NeighbourS;
rotateLeft(NeighbourSW.l);
NeighbourSE = NeighbourS;
rotateRight(NeighbourSE.l);
for (int i=0; i<MATRIX_ROWS/8; i++) {
//Count the live neighbours (in parallel) for the current row of cells
//However, if total goes over 3, we don't care (see below), so counting stops at 4
tot1 = NeighbourN.l[i];
tot2 = tot1 & NeighbourNW.l[i]; tot1 = tot1 ^ NeighbourNW.l[i];
carry = tot1 & NeighbourNE.l[i]; tot1 = tot1 ^ NeighbourNE.l[i]; tot4 = tot2 & carry; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourW.l[i]; tot1 = tot1 ^ NeighbourW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourE.l[i]; tot1 = tot1 ^ NeighbourE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourS.l[i]; tot1 = tot1 ^ NeighbourS.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSW.l[i]; tot1 = tot1 ^ NeighbourSW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSE.l[i]; tot1 = tot1 ^ NeighbourSE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
//Calculate the updated cells:
// <2 or >3 neighbours, cell dies
// =2 neighbours, cell continues to live
// =3 neighbours, new cell born
NewCells = (CurrCells.l[i] | tot1) & tot2 & ~ tot4;
//Have any cells changed?
if (NewCells != CurrCells.l[i]) {
//Count the change for "stale" test
changes++;
Matrix[row].l[i] = NewCells;
}
}
//Current cells (before update), E , W, SE, SW and S neighbours become
//new N, NW, NE, E, W neighbours and current cells for next loop
NeighbourN = CurrCells;
NeighbourNW = NeighbourW;
NeighbourNE = NeighbourE;
NeighbourE = NeighbourSE;
NeighbourW = NeighbourSW;
CurrCells = NeighbourS;
}
if (changes != prevChanges[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
staleCount = 0;
}
else {
staleCount++; //Detect "stale" matrix
}
if (staleCount > 64) injectGlider(); //Inject a glider
//Serial.println(changes);
for (int i=3; i>0; i--) {
prevChanges[i] = prevChanges[i-1];
}
prevChanges[0] = changes;
}
Code:
// Conway's Game Of Life 256x256
// PaulRB
// Jun 2015
#include <SPI.h>
//Pins controlling SSD1306 Graphic OLED
#define OLED_DC 1
#define OLED_CS 0
#define OLED_RESET 2
#define OLED_COLS 128
#define OLED_ROWS 8
#define MATRIX_COLS 256
#define MATRIX_ROWS 32
union MatrixData {
unsigned long long l[MATRIX_ROWS/8];
byte b[MATRIX_ROWS];
};
MatrixData Matrix[MATRIX_COLS+1]; // Cell data in ram
int leftChanges, rightChanges, topChanges, bottomChanges;
void setup() {
byte startupCodes[] = {
0xAE, // Display off
0xD5, // Set display clock divider
0x80,
0xA8, // Set multiplex
0x3F,
0xD3, // Set display offset
0x00,
0x40, // Set start line to zero
0x8D, // Set charge pump
0x14,
0x20, // Set memory mode
0x00,
0xA1, // Set segment remapping
0xC8, // Set command Scan decode
0xDA, // Set Comm pins
0x12,
0x81, // Set contrast
0xCF,
0xd9, // Set precharge
0xF1,
0xDB, // Set Vcom detect
0x40,
0xA4, // Allow display resume
0xA6, // Set normal display
0xAF // Display On
};
pinMode(OLED_DC, OUTPUT);
pinMode(OLED_CS, OUTPUT);
pinMode(OLED_RESET, OUTPUT);
SPI.begin();
SPI.setBitOrder(MSBFIRST); // Set the SPI bit order
SPI.setDataMode(SPI_MODE0); //Set the SPI data mode 0
SPI.setClockDivider(SPI_CLOCK_DIV2); // 2.25 MHz
digitalWrite(OLED_RESET, HIGH);
delay(1);
digitalWrite(OLED_RESET, LOW);
delay(10);
digitalWrite(OLED_RESET, HIGH);
digitalWrite(OLED_DC, LOW);
digitalWrite(OLED_CS, LOW);
for (int i = 0; i < sizeof(startupCodes); i++) SPI.transfer(startupCodes[i]);
digitalWrite(OLED_CS, HIGH);
//R-pentomino
//Matrix[64].l[4] = 0b0000010;
//Matrix[65].l[4] = 0b0000111;
//Matrix[66].l[4] = 0b0000100;
//injectGlider();
//Gosper's Glider Gun
//Matrix[64].l[4] = 0b00000000000000000000000000010000000000000000000000;
//Matrix[65].l[4] = 0b00000000000000000000000001010000000000000000000000;
//Matrix[66].l[4] = 0b00000000000000011000000110000000000001100000000000;
//Matrix[67].l[4] = 0b00000000000000100010000110000000000001100000000000;
//Matrix[68].l[4] = 0b00011000000001000001000110000000000000000000000000;
//Matrix[69].l[4] = 0b00011000000001000101100001010000000000000000000000;
//Matrix[70].l[4] = 0b00000000000001000001000000010000000000000000000000;
//Matrix[71].l[4] = 0b00000000000000100010000000000000000000000000000000;
//Matrix[72].l[4] = 0b00000000000000011000000000000000000000000000000000;
randomiseMatrix();
outputMatrix();
//Serial.begin(115200);
//pinMode(SW_U, INPUT_PULLUP);
//pinMode(SW_D, INPUT_PULLUP);
//pinMode(SW_L, INPUT_PULLUP);
//pinMode(SW_R, INPUT_PULLUP);
//pinMode(SW_C, INPUT_PULLUP);
}
void loop() {
unsigned long start = millis();
for (int i=0; i<1000; i++) {
generateMatrix();
if (leftChanges - rightChanges > 16) scrollRight();
if (rightChanges - leftChanges > 16) scrollLeft();
if (bottomChanges - topChanges > 16) scrollDown();
if (topChanges - bottomChanges > 16) scrollUp();
outputMatrix();
}
//Serial.print("Gens/s:"); Serial.println(1000000UL/(millis() - start));
}
void outputMatrix() {
digitalWrite(OLED_DC, LOW); //Command mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus
SPI.transfer(0x21); // Set column address
SPI.transfer(0);
SPI.transfer(OLED_COLS-1);
SPI.transfer(0x22); // Set page address
SPI.transfer(0);
SPI.transfer(OLED_ROWS-1);
digitalWrite(OLED_CS, HIGH); //Disable display on SPI bus
digitalWrite(OLED_DC, HIGH); // Data mode
digitalWrite(OLED_CS, LOW); //Enable display on SPI bus
//Send matrix data for display on OLED
for (int col = MATRIX_ROWS/2 - OLED_ROWS/2; col < MATRIX_ROWS/2 + OLED_ROWS/2; col++) {
for (int row = MATRIX_COLS/2 - OLED_COLS/2; row < MATRIX_COLS/2 + OLED_COLS/2; row++) {
SPI.transfer(Matrix[row].b[col]);
}
}
digitalWrite(OLED_CS, HIGH);
}
void randomiseMatrix() {
//Set up initial cells in matrix
randomSeed(analogRead(0));
for (int row = 0; row < MATRIX_COLS; row++) {
for (int col = 0; col < MATRIX_ROWS; col++) {
Matrix[row].b[col] = random(0xff);
}
}
}
void injectGlider() {
int col = random(MATRIX_COLS);
int row = random(MATRIX_ROWS);
Matrix[col++].b[row] |= 0b0000111;
Matrix[col++].b[row] |= 0b0000001;
Matrix[col++].b[row] |= 0b0000010;
}
void rotateLeft(unsigned long long x[]) {
unsigned long long carryIn = 0, carryOut;
for (int i=0; i<4; i++) {
carryOut = x[i] >> 63;
x[i] = x[i] << 1 | carryIn;
carryIn = carryOut;
}
x[0] |= carryOut;
}
void rotateRight(unsigned long long x[]) {
unsigned long long carryIn = 0, carryOut;
for (int i=3; i>=0; i--) {
carryOut = x[i] << 63;
x[i] = x[i] >> 1 | carryIn;
carryIn = carryOut;
}
x[3] |= carryOut;
}
void scrollLeft() {
Matrix[MATRIX_COLS] = Matrix[0];
for (int row = 0; row < MATRIX_COLS+1; row++) Matrix[row] = Matrix[row+1];
}
void scrollRight() {
for (int row = MATRIX_COLS; row > 0; row--) Matrix[row] = Matrix[row-1];
Matrix[0] = Matrix[MATRIX_COLS];
}
void scrollUp() {
for (int row = 0; row < MATRIX_COLS; row++) rotateLeft(Matrix[row].l);
}
void scrollDown() {
for (int row = 0; row < MATRIX_COLS; row++) rotateRight(Matrix[row].l);
}
int generateMatrix() {
//Variables holding data on neighbouring cells
MatrixData NeighbourN, NeighbourNW, NeighbourNE, CurrCells, NeighbourW, NeighbourE, NeighbourS, NeighbourSW, NeighbourSE;
//Variables used in calculating new cells
unsigned long long tot1, carry, tot2, tot4, NewCells;
int changes = 0; // counts the changes in the matrix
static int prevChanges[4]; // counts the changes in the matrix on prev 4 generations
static int staleCount = 0; // counts the consecutive occurrances of the same number of changes in the matrix
leftChanges = rightChanges = topChanges = bottomChanges = 0;
//set up N, NW, NE, W & E neighbour data
NeighbourN = Matrix[MATRIX_COLS-1];
CurrCells = Matrix[0];
Matrix[MATRIX_COLS] = CurrCells; // copy row 0 to location after last row to remove need for wrap-around code in the loop
NeighbourNW = NeighbourN;
rotateLeft(NeighbourNW.l);
NeighbourNE = NeighbourN;
rotateRight(NeighbourNE.l);
NeighbourW = CurrCells;
rotateLeft(NeighbourW.l);
NeighbourE = CurrCells;
rotateRight(NeighbourE.l);
//Process each row of the matrix
for (int row = 0; row < MATRIX_COLS; row++) {
//Pick up new S, SW & SE neighbours
NeighbourS = Matrix[row + 1];
NeighbourSW = NeighbourS;
rotateLeft(NeighbourSW.l);
NeighbourSE = NeighbourS;
rotateRight(NeighbourSE.l);
for (int i=0; i<MATRIX_ROWS/8; i++) {
//Count the live neighbours (in parallel) for the current row of cells
//However, if total goes over 3, we don't care (see below), so counting stops at 4
tot1 = NeighbourN.l[i];
tot2 = tot1 & NeighbourNW.l[i]; tot1 = tot1 ^ NeighbourNW.l[i];
carry = tot1 & NeighbourNE.l[i]; tot1 = tot1 ^ NeighbourNE.l[i]; tot4 = tot2 & carry; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourW.l[i]; tot1 = tot1 ^ NeighbourW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourE.l[i]; tot1 = tot1 ^ NeighbourE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourS.l[i]; tot1 = tot1 ^ NeighbourS.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSW.l[i]; tot1 = tot1 ^ NeighbourSW.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
carry = tot1 & NeighbourSE.l[i]; tot1 = tot1 ^ NeighbourSE.l[i]; tot4 = tot2 & carry | tot4; tot2 = tot2 ^ carry;
//Calculate the updated cells:
// <2 or >3 neighbours, cell dies
// =2 neighbours, cell continues to live
// =3 neighbours, new cell born
NewCells = (CurrCells.l[i] | tot1) & tot2 & ~ tot4;
//Have any cells changed?
if (NewCells != CurrCells.l[i]) {
//Count the change for "stale" test
changes++;
if (row >= MATRIX_COLS/2) rightChanges++; else leftChanges++;
if (i >= MATRIX_ROWS/16) bottomChanges++; else topChanges++;
Matrix[row].l[i] = NewCells;
}
}
//Current cells (before update), E , W, SE, SW and S neighbours become
//new N, NW, NE, E, W neighbours and current cells for next loop
NeighbourN = CurrCells;
NeighbourNW = NeighbourW;
NeighbourNE = NeighbourE;
NeighbourE = NeighbourSE;
NeighbourW = NeighbourSW;
CurrCells = NeighbourS;
}
if (changes != prevChanges[0] && changes != prevChanges[1] && changes != prevChanges[2] && changes != prevChanges[3]) {
staleCount = 0;
}
else {
staleCount++; //Detect "stale" matrix
}
if (staleCount > 64) injectGlider(); //Inject a glider
for (int i=3; i>0; i--) {
prevChanges[i] = prevChanges[i-1];
}
prevChanges[0] = changes;
}
Looks very cool.
Did you figure out your crash bug?
Also, in your yourtube comments,
Can you link to my Youtube vids about how you install, and also to the repo and this forum?
You could try one of those coloured SPI based displays, eg ILI9341 (though it may be a bit slower unless you can use DMA to do the transfers)
Don’t know if I still have the blue throbbing/crash issue, will leave the sketch running a few hours and see what happens. Maybe even small changes can trigger/avoid the issue.
Thanks for making all this happen on the Arduino IDE, by the way!
If you still have the issue with blue throbbing, let me know and i will see if i can replicate it and then try running with a STLink adaptor and GDB running to see if it can shed any light on why its crashing
Roger, ran the code continuously for over 3 hours yesterday with no crash/blue throbbing. No idea what I did to fix it, unfortunately. If it starts up again, will attempt to isolate what I changed that causes it, although if it only occurs after several hours, I’m likely to miss it.
I wonder if a loose connection to the display could cause it (not quite sure how)
Anyway. There’s no way to debug it if we can’t replicate the bug
Where did I hear that before….. ![]()


