/* * life_Adafruit_GFX.ino * John Conway's Game of Life for Adafruit_GFX library * based on John Conway's Game of Life for openGLCD * * 20220919 Version 1.01 */ #include #include // bitbang spi: // PCF8814 display(ce, clk, mosi, rst) // PCF8814 display(13,9,11,10); PCF8814 display(10,13,11,8); #define DISPLAY_HEIGHT 65 #define DISPLAY_WIDTH 96 /********* user configurable defines ************/ #define CELL_SIZE 4 // the height and width of each cell #define DELAY 50 // number of milliseconds delay between the display of each generation /******* end of user configurable defines ********/ #define ROWS (DISPLAY_HEIGHT / CELL_SIZE) #define COLUMNS (DISPLAY_WIDTH / CELL_SIZE) #define BYTES_PER_COLUMN ((COLUMNS + 7)/ 8) //this is the number of bytes needed to hold a column #define MAX_ITERATIONS 500 // iterations are terminated if this number is exceeded #define STABLE_GENERATIONS 4 // must be at least 2, this is the number of back generations checked for stability #define MIN_OBJECTS 2 // minmum number of objects to be seeded by the random generater #define MAX_OBJECTS 4 // maximum number of objects for random seed generator #define MARGIN 4 // minimum distance from screen edge for randomly seeded objects #define BITVAL(_pos) (1<< (7-(_pos % 8))) // macro to get a bitfield value #define ALIVE true #define DEAD false /* ======== Life Objects ======== * these objects can be displayed on the Arduino life screen * to add an object, create and initialise a structure following the examples here * add a reference to your structure to the object table, name your object by adding to the enum list */ // structure template for life objects typedef struct { byte height ; byte width ; byte bitfield[]; } object_t, *object_ptr_t; // definitions and initialisations for all defined objects here struct { byte height ; byte width ; byte bitfield[2]; } static table = {2,4, { 0b10010000 , // * * 0b11110000 } }; // **** struct { byte height ; byte width ; byte bitfield[3]; } static glider = {3,3, { 0b00100000 , // * 0b10100000 , // * * 0b01100000 }}; // ** #ifdef NOTUSED struct { byte height ; byte width ; byte bitfield[3]; } static glider2 = {3,3, { 0b10100000 , // * * 0b11000000 , // ** 0b11110000 }}; // **** #endif struct { byte height ; byte width ; byte bitfield[4]; } static loaf = {4,4, { 0b01100000 , // ** 0b10010000 , // * * 0b01010000 , // * * 0b00100000 }}; // * struct { byte height ; byte width ; byte bitfield[3]; } static ship = {3,3, { 0b11000000 , // ** 0b10100000 , // * * 0b01100000 }}; // ** struct { byte height ; byte width ; byte bitfield[4]; } static behive = {4,3, { 0b01000000 , // * 0b10100000 , // * * 0b10100000 , // * * 0b01000000}}; // * struct { byte height ; byte width ; byte bitfield[1]; } static blinker = {1,3, { 0b11100000 }}; // *** struct { byte height ; byte width ; byte bitfield[2]; } static block = {2,2, { 0b11000000 , // ** 0b11000000}}; // ** // place all the objects in the object table object_ptr_t objectTable[] = { (object_ptr_t)&table, (object_ptr_t)&glider, (object_ptr_t)&loaf, (object_ptr_t)&ship, (object_ptr_t)&behive, (object_ptr_t)&blinker, (object_ptr_t)&block }; #define OBJECT_COUNT (sizeof( objectTable/ sizeof(object_ptr_t))) // enumerate all the objects, add any new objects to the end of this enum (note case difference ) // the life program references all objects using the names in this list enum {Table,Glider,Loaf,Ship,Behive,Blinker,Block}; byte generations[STABLE_GENERATIONS][ROWS][BYTES_PER_COLUMN]; // array used to calculate, draw and check if iterations are changing void setup(){ display.begin(); // initialise the library, non inverted writes pixels onto a clear screen display.setTextSize(2); // Normal 1:1 pixel scale display.setTextColor(BLACK); // Draw black text display.clearDisplay(); display.setCursor((DISPLAY_WIDTH/2)-24,(DISPLAY_HEIGHT/2)-6); display.println(F("Life")); display.display(); display.setTextSize(1); delay(3000); seed(0); } void loop (void){ display.clearDisplay(); unsigned int i = generate(); display.setCursor(0,0); display.setTextColor(BLACK,WHITE); display.print(i); display.print(F(" generations")); display.display(); delay(2000); seed(1); // use random seed } unsigned int generate(){ unsigned int iteration = 0; byte thisGeneration,nextGeneration; thisGeneration = nextGeneration = 0; //display the initial array for(int row = 0; row < ROWS; row++) { for(int column = 0; column < COLUMNS; column++) { if(isAlive(thisGeneration,row,column)) { //display.drawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, BLACK); display.fillRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, BLACK); } else { //display.drawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, WHITE); display.fillRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, WHITE); } } } display.display(); delay(1500); // show the seeded condition a little longer do{ thisGeneration = iteration % STABLE_GENERATIONS; nextGeneration = (thisGeneration+1) % STABLE_GENERATIONS; delay(DELAY); memset(generations[nextGeneration],0,sizeof(generations[0])); // clear the array for the next generation //see who lives and who dies for(int row = 0; row < ROWS; row++){ for(int column = 0; column < COLUMNS; column++){ boolean cell = isAlive(thisGeneration,row,column); byte n = getNeighborCount(thisGeneration,row,column); if(cell == DEAD){ if(n==3) { setLife(nextGeneration,row,column,ALIVE); // birth //display.drawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, BLACK); display.fillRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, BLACK); } } else{ if(n==2 || n==3) { setLife(nextGeneration,row,column,ALIVE); //survival // No need to draw cell as it is already there. } else { setLife(nextGeneration,row,column,DEAD); //death //display.drawRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, WHITE); display.fillRoundRect(column * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE, CELL_SIZE/2, WHITE); } } } } iteration++; display.setTextColor(BLACK,WHITE); display.setCursor(DISPLAY_WIDTH-18,DISPLAY_HEIGHT-12); display.print(iteration); display.display(); } while(isStable(thisGeneration) == false && iteration < MAX_ITERATIONS ) ; return iteration; } void seed(int seedValue){ // a hard coded seed is used if seedvalue is zero, otherwise a random seed is used memset(generations,0,sizeof(generations)); // clear all generation arrays if(seedValue == 0){ // load default objects, arguments are offsets from the center of the screen loadObject(Table,2,-9); // put a table object 2 down from the center and 9 columns left or center loadObject(Glider, -5,-9); // put a glider 5 rows above the center and 9 columns left of center } else{ // load some random objects and place at random positions byte nbrObjects = random(MIN_OBJECTS,MAX_OBJECTS+1); // make a random number of objects for(byte i=0; i < nbrObjects;i++){ byte obj = random(7); // OBJECT_COUNT); int column = random(MARGIN - (COLUMNS/2), (COLUMNS/2) - MARGIN); int row = random(MARGIN - (ROWS/2) , (ROWS/2) - MARGIN); loadObject(obj,row,column); } } } void loadObject(void * object, char y, char x){ // object_ptr is the object to be loaded // y and x are offsets from the center of the display object_ptr_t object_ptr = (object_ptr_t)object; byte row = (ROWS/2) + y; byte column = (COLUMNS/2) + x; for(byte ys = 0; ys < object_ptr->height; ys++ ){ for(byte xs=0; xs < object_ptr->width; xs++ ){ if( (row +ys < ROWS) && (column +xs < COLUMNS)){ // this code only works on widths up to 8 bits boolean value = object_ptr->bitfield[ys] & BITVAL(xs) ; setLife(0, row+ys, column+xs, value) ; // objects always loaded into the first array } } } } void loadObject(int object, char y, char x){ loadObject(objectTable[object], y, x ); } boolean isStable(byte thisGeneration){ // returns true if any two captured generations are the same for(byte i=0; i < STABLE_GENERATIONS; i++) if(i != thisGeneration) if(memcmp(generations[thisGeneration], generations[i], sizeof(generations[0])) == 0) return true; return false; } boolean isAlive( byte generation, byte row, byte column){ byte b = generations[generation][row][column /8]; return b & BITVAL(column) ; } void setLife( byte generation, byte row, byte column, boolean state){ byte elem = column /8; byte b = generations[generation][row][elem]; if(state != DEAD){ b = b | BITVAL(column); } else{ b &= ~(BITVAL(column)) ; } generations[generation][row][elem] = b; } byte getNeighborCount( byte generation, byte row, byte column){ byte count = 0; for(byte d = 0;d < 8; d++) if(isNeighborAlive(generation,row,column,d) != 0) count++; return count; } boolean isNeighborAlive( byte generation , byte row, byte column, byte dir){ byte nrow=row; byte ncol=column; if(dir == 7 || dir == 0 || dir == 1) nrow--; if(dir == 3 || dir == 4 || dir == 5) nrow++; if(dir == 5 || dir == 6 || dir == 7) ncol--; if(dir == 1 || dir == 2 || dir == 3) ncol++; if(ncol==255) ncol = COLUMNS - 1; if(ncol==COLUMNS) ncol = 0; if(nrow==255) nrow = ROWS - 1; if(nrow==ROWS) nrow = 0; return isAlive(generation,nrow,ncol); }