Created
May 1, 2016 17:45
-
-
Save PaulRB/ea998292b684980a350462b9297dde6c to your computer and use it in GitHub Desktop.
Conway's Game Of Life for WeMos D1 Mini
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Conway's Game Of Life | |
// For WeMos D1 Mini and 128x64 OLED display | |
// PaulRB | |
// April 2016 | |
#include <SPI.h> | |
//Pins controlling SSD1306 Graphic OLED | |
#define OLED_DC D8 | |
#define OLED_COLS 128 | |
#define OLED_ROWS 64 | |
SPISettings ssd1306(100000000UL, MSBFIRST, SPI_MODE0); | |
#define MATRIX_COLS 512 | |
#define MATRIX_ROWS 512 | |
union MatrixData { | |
unsigned long long l[MATRIX_ROWS/8/8]; | |
byte b[MATRIX_ROWS/8]; | |
}; | |
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 | |
}; | |
Serial.begin(115200); | |
pinMode(OLED_DC, OUTPUT); | |
//Initialise the SSD1306 display | |
SPI.begin(); | |
digitalWrite(OLED_DC, LOW); //Command mode | |
SPI.beginTransaction(ssd1306); | |
for (int i = 0; i < sizeof(startupCodes); i++) SPI.transfer(startupCodes[i]); | |
SPI.endTransaction(); | |
//R-pentomino | |
//Matrix[127].l[4] = 0b0000010; | |
//Matrix[128].l[4] = 0b0000111; | |
//Matrix[129].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(); | |
} | |
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(); | |
yield(); | |
} | |
Serial.print("Gens/s:"); Serial.println(1000000UL/(millis() - start)); | |
} | |
void outputMatrix() { | |
digitalWrite(OLED_DC, LOW); //Command mode | |
SPI.beginTransaction(ssd1306); | |
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/8-1); | |
digitalWrite(OLED_DC, HIGH); // Data mode | |
//Send matrix data for display on OLED | |
for (int col = MATRIX_ROWS/8/2 - OLED_ROWS/8/2; col < MATRIX_ROWS/8/2 + OLED_ROWS/8/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]); | |
} | |
} | |
SPI.endTransaction(); | |
} | |
void randomiseMatrix() { | |
//Set up initial cells in matrix | |
randomSeed(analogRead(A0)); | |
for (int row = 0; row < MATRIX_COLS; row++) { | |
for (int col = 0; col < MATRIX_ROWS/8; col++) { | |
Matrix[row].b[col] = random(0xff) & random(0xff) & random(0xff); | |
} | |
} | |
} | |
void injectGlider() { | |
byte glider[4][3] = { | |
{ 0b0000111, | |
0b0000001, | |
0b0000010 }, | |
{ 0b0000010, | |
0b0000001, | |
0b0000111 }, | |
{ 0b0000011, | |
0b0000101, | |
0b0000001 }, | |
{ 0b0000110, | |
0b0000101, | |
0b0000100 } | |
}; | |
Serial.println("Inecting..."); | |
int col = MATRIX_COLS/2; | |
int row = MATRIX_ROWS/8/2; | |
int dir = random(4); | |
Matrix[col++].b[row] |= glider[dir][0]; | |
Matrix[col++].b[row] |= glider[dir][1]; | |
Matrix[col++].b[row] |= glider[dir][2]; | |
} | |
void rotateLeft(unsigned long long x[]) { | |
unsigned long long carryIn = 0, carryOut; | |
for (int i=0; i<MATRIX_ROWS/8/8; 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=MATRIX_ROWS/8/8-1; 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/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/8/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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment