@@ -5196,112 +5196,162 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
51965196// /////////////////////////////////////////
51975197// 2D Cellular Automata Game of life //
51985198// /////////////////////////////////////////
5199- typedef struct ColorCount {
5200- CRGB color;
5201- int8_t count;
5202- } colorCount;
5199+ typedef struct Cell {
5200+ uint8_t alive : 1 , faded : 1 , toggleStatus : 1 , edgeCell: 1 , oscillatorCheck : 1 , spaceshipCheck : 1 , unused : 2 ;
5201+ } Cell;
52035202
5204- uint16_t mode_2Dgameoflife (void ) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color
5203+ uint16_t mode_2Dgameoflife (void ) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
5204+ // and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
52055205 if (!strip.isMatrix || !SEGMENT.is2D ()) return mode_static (); // not a 2D set-up
5206+ const int cols = SEG_W, rows = SEG_H;
5207+ const unsigned maxIndex = cols * rows;
52065208
5207- const int cols = SEG_W;
5208- const int rows = SEG_H;
5209- const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
5210- const unsigned dataSize = sizeof (CRGB) * SEGMENT.length (); // using width*height prevents reallocation if mirroring is enabled
5211- const int crcBufferLen = 2 ; // (SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi)
5209+ if (!SEGENV.allocateData (SEGMENT.length () * sizeof (Cell))) return mode_static (); // allocation failed
52125210
5213- if (!SEGENV.allocateData (dataSize + sizeof (uint16_t )*crcBufferLen)) return mode_static (); // allocation failed
5214- CRGB *prevLeds = reinterpret_cast <CRGB*>(SEGENV.data );
5215- uint16_t *crcBuffer = reinterpret_cast <uint16_t *>(SEGENV.data + dataSize);
5211+ Cell *cells = reinterpret_cast <Cell*> (SEGENV.data );
52165212
5217- CRGB backgroundColor = SEGCOLOR (1 );
5213+ uint16_t & generation = SEGENV.aux0 , &gliderLength = SEGENV.aux1 ; // rename aux variables for clarity
5214+ bool mutate = SEGMENT.check3 ;
5215+ uint8_t blur = map (SEGMENT.custom1 , 0 , 255 , 255 , 4 );
52185216
5219- if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000 ) {
5220- SEGENV.step = strip.now ;
5221- SEGENV.aux0 = 0 ;
5217+ uint32_t bgColor = SEGCOLOR (1 );
5218+ uint32_t birthColor = SEGMENT.color_from_palette (128 , false , PALETTE_SOLID_WRAP, 255 );
52225219
5223- // give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen)
5224- for (int x = 0 ; x < cols; x++) for (int y = 0 ; y < rows; y++) {
5225- unsigned state = hw_random8 ()%2 ;
5226- if (state == 0 )
5227- SEGMENT.setPixelColorXY (x,y, backgroundColor);
5228- else
5229- SEGMENT.setPixelColorXY (x,y, SEGMENT.color_from_palette (hw_random8 (), false , PALETTE_SOLID_WRAP, 255 ));
5220+ bool setup = SEGENV.call == 0 ;
5221+ if (setup) {
5222+ // Calculate glider length LCM(rows,cols)*4 once
5223+ unsigned a = rows, b = cols;
5224+ while (b) { unsigned t = b; b = a % b; a = t; }
5225+ gliderLength = (cols * rows / a) << 2 ;
5226+ }
5227+
5228+ if (abs (long (strip.now ) - long (SEGENV.step )) > 2000 ) SEGENV.step = 0 ; // Timebase jump fix
5229+ bool paused = SEGENV.step > strip.now ;
5230+
5231+ // Setup New Game of Life
5232+ if ((!paused && generation == 0 ) || setup) {
5233+ SEGENV.step = strip.now + 1280 ; // show initial state for 1.28 seconds
5234+ generation = 1 ;
5235+ paused = true ;
5236+ // Setup Grid
5237+ memset (cells, 0 , maxIndex * sizeof (Cell));
5238+
5239+ for (unsigned i = 0 ; i < maxIndex; i++) {
5240+ bool isAlive = !hw_random8 (3 ); // ~33%
5241+ cells[i].alive = isAlive;
5242+ cells[i].faded = !isAlive;
5243+ unsigned x = i % cols, y = i / cols;
5244+ cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1 );
5245+
5246+ SEGMENT.setPixelColor (i, isAlive ? SEGMENT.color_from_palette (hw_random8 (), false , PALETTE_SOLID_WRAP, 0 ) : bgColor);
52305247 }
5248+ }
52315249
5232- for (int y = 0 ; y < rows; y++) for (int x = 0 ; x < cols; x++) prevLeds[XY (x,y)] = CRGB::Black;
5233- memset (crcBuffer, 0 , sizeof (uint16_t )*crcBufferLen);
5234- } else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t )map (SEGMENT.speed ,0 ,255 ,64 ,4 )) {
5235- // update only when appropriate time passes (in 42 FPS slots)
5250+ if (paused || (strip.now - SEGENV.step < 1000 / map (SEGMENT.speed ,0 ,255 ,1 ,42 ))) {
5251+ // Redraw if paused or between updates to remove blur
5252+ for (unsigned i = maxIndex; i--; ) {
5253+ if (!cells[i].alive ) {
5254+ uint32_t cellColor = SEGMENT.getPixelColor (i);
5255+ if (cellColor != bgColor) {
5256+ uint32_t newColor;
5257+ bool needsColor = false ;
5258+ if (cells[i].faded ) { newColor = bgColor; needsColor = true ; }
5259+ else {
5260+ uint32_t blended = color_blend (cellColor, bgColor, 2 );
5261+ if (blended == cellColor) { blended = bgColor; cells[i].faded = 1 ; }
5262+ newColor = blended; needsColor = true ;
5263+ }
5264+ if (needsColor) SEGMENT.setPixelColor (i, newColor);
5265+ }
5266+ }
5267+ }
52365268 return FRAMETIME;
52375269 }
52385270
5239- // copy previous leds (save previous generation)
5240- // NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset
5241- for (int x = 0 ; x < cols; x++) for (int y = 0 ; y < rows; y++) prevLeds[XY (x,y)] = SEGMENT.getPixelColorXY (x,y);
5242-
5243- // calculate new leds
5244- for (int x = 0 ; x < cols; x++) for (int y = 0 ; y < rows; y++) {
5271+ // Repeat detection
5272+ bool updateOscillator = generation % 16 == 0 ;
5273+ bool updateSpaceship = gliderLength && generation % gliderLength == 0 ;
5274+ bool repeatingOscillator = true , repeatingSpaceship = true , emptyGrid = true ;
5275+
5276+ unsigned cIndex = maxIndex-1 ;
5277+ for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) {
5278+ Cell& cell = cells[cIndex];
5279+
5280+ if (cell.alive ) emptyGrid = false ;
5281+ if (cell.oscillatorCheck != cell.alive ) repeatingOscillator = false ;
5282+ if (cell.spaceshipCheck != cell.alive ) repeatingSpaceship = false ;
5283+ if (updateOscillator) cell.oscillatorCheck = cell.alive ;
5284+ if (updateSpaceship) cell.spaceshipCheck = cell.alive ;
5285+
5286+ unsigned neighbors = 0 , aliveParents = 0 , parentIdx[3 ];
5287+ // Count alive neighbors
5288+ for (int i = -1 ; i <= 1 ; i++) for (int j = -1 ; j <= 1 ; j++) if (i || j) {
5289+ int nX = x + j, nY = y + i;
5290+ if (cell.edgeCell ) {
5291+ nX = (nX + cols) % cols;
5292+ nY = (nY + rows) % rows;
5293+ }
5294+ unsigned nIndex = nX + nY * cols;
5295+ Cell& neighbor = cells[nIndex];
5296+ if (neighbor.alive ) {
5297+ neighbors++;
5298+ if (!neighbor.toggleStatus && neighbors < 4 ) { // Alive and not dying
5299+ parentIdx[aliveParents++] = nIndex;
5300+ }
5301+ }
5302+ }
52455303
5246- colorCount colorsCount[ 9 ]; // count the different colors in the 3*3 matrix
5247- for ( int i= 0 ; i< 9 ; i++) colorsCount[i] = {backgroundColor, 0 }; // init colorsCount
5304+ uint32_t newColor;
5305+ bool needsColor = false ;
52485306
5249- // iterate through neighbors and count them and their different colors
5250- int neighbors = 0 ;
5251- for (int i = -1 ; i <= 1 ; i++) for (int j = -1 ; j <= 1 ; j++) { // iterate through 3*3 matrix
5252- if (i==0 && j==0 ) continue ; // ignore itself
5253- // wrap around segment
5254- int xx = x+i, yy = y+j;
5255- if (x+i < 0 ) xx = cols-1 ; else if (x+i >= cols) xx = 0 ;
5256- if (y+j < 0 ) yy = rows-1 ; else if (y+j >= rows) yy = 0 ;
5307+ if (cell.alive && (neighbors < 2 || neighbors > 3 )) { // Loneliness or Overpopulation
5308+ cell.toggleStatus = 1 ;
5309+ if (blur == 255 ) cell.faded = 1 ;
5310+ newColor = cell.faded ? bgColor : color_blend (SEGMENT.getPixelColor (cIndex), bgColor, blur);
5311+ needsColor = true ;
5312+ }
5313+ else if (!cell.alive ) {
5314+ byte mutationRoll = mutate ? hw_random8 (128 ) : 1 ; // if 0: 3 neighbor births fail and 2 neighbor births mutate
5315+ if ((neighbors == 3 && mutationRoll) || (mutate && neighbors == 2 && !mutationRoll)) { // Reproduction or Mutation
5316+ cell.toggleStatus = 1 ;
5317+ cell.faded = 0 ;
52575318
5258- unsigned xy = XY (xx, yy); // previous cell xy to check
5259- // count different neighbours and colors
5260- if (prevLeds[xy] != backgroundColor) {
5261- neighbors++;
5262- bool colorFound = false ;
5263- int k;
5264- for (k=0 ; k<9 && colorsCount[k].count != 0 ; k++)
5265- if (colorsCount[k].color == prevLeds[xy]) {
5266- colorsCount[k].count ++;
5267- colorFound = true ;
5268- }
5269- if (!colorFound) colorsCount[k] = {prevLeds[xy], 1 }; // add new color found in the array
5319+ if (aliveParents) {
5320+ // Set color based on random neighbor
5321+ unsigned parentIndex = parentIdx[random8 (aliveParents)];
5322+ birthColor = SEGMENT.getPixelColor (parentIndex);
5323+ }
5324+ newColor = birthColor;
5325+ needsColor = true ;
52705326 }
5271- } // i,j
5272-
5273- // Rules of Life
5274- uint32_t col = uint32_t (prevLeds[XY (x,y)]) & 0x00FFFFFF ; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte
5275- uint32_t bgc = RGBW32 (backgroundColor.r , backgroundColor.g , backgroundColor.b , 0 );
5276- if ((col != bgc) && (neighbors < 2 )) SEGMENT.setPixelColorXY (x,y, bgc); // Loneliness
5277- else if ((col != bgc) && (neighbors > 3 )) SEGMENT.setPixelColorXY (x,y, bgc); // Overpopulation
5278- else if ((col == bgc) && (neighbors == 3 )) { // Reproduction
5279- // find dominant color and assign it to a cell
5280- colorCount dominantColorCount = {backgroundColor, 0 };
5281- for (int i=0 ; i<9 && colorsCount[i].count != 0 ; i++)
5282- if (colorsCount[i].count > dominantColorCount.count ) dominantColorCount = colorsCount[i];
5283- // assign the dominant color w/ a bit of randomness to avoid "gliders"
5284- if (dominantColorCount.count > 0 && hw_random8 (128 )) SEGMENT.setPixelColorXY (x,y, dominantColorCount.color );
5285- } else if ((col == bgc) && (neighbors == 2 ) && !hw_random8 (128 )) { // Mutation
5286- SEGMENT.setPixelColorXY (x,y, SEGMENT.color_from_palette (hw_random8 (), false , PALETTE_SOLID_WRAP, 255 ));
5287- }
5288- // else do nothing!
5289- } // x,y
5290-
5291- // calculate CRC16 of leds
5292- uint16_t crc = crc16 ((const unsigned char *)prevLeds, dataSize);
5293- // check if we had same CRC and reset if needed
5294- bool repetition = false ;
5295- for (int i=0 ; i<crcBufferLen && !repetition; i++) repetition = (crc == crcBuffer[i]); // (Ewowi)
5296- // same CRC would mean image did not change or was repeating itself
5297- if (!repetition) SEGENV.step = strip.now ; // if no repetition avoid reset
5298- // remember CRCs across frames
5299- crcBuffer[SEGENV.aux0 ] = crc;
5300- ++SEGENV.aux0 %= crcBufferLen;
5327+ else if (!cell.faded ) {// No change, fade dead cells
5328+ uint32_t cellColor = SEGMENT.getPixelColor (cIndex);
5329+ uint32_t blended = color_blend (cellColor, bgColor, blur);
5330+ if (blended == cellColor) { blended = bgColor; cell.faded = 1 ; }
5331+ newColor = blended;
5332+ needsColor = true ;
5333+ }
5334+ }
5335+
5336+ if (needsColor) SEGMENT.setPixelColor (cIndex, newColor);
5337+ }
5338+ // Loop through cells, if toggle, swap alive status
5339+ for (unsigned i = maxIndex; i--; ) {
5340+ cells[i].alive ^= cells[i].toggleStatus ;
5341+ cells[i].toggleStatus = 0 ;
5342+ }
53015343
5344+ if (repeatingOscillator || repeatingSpaceship || emptyGrid) {
5345+ generation = 0 ; // reset on next call
5346+ SEGENV.step += 1024 ; // pause final generation for ~1 second
5347+ }
5348+ else {
5349+ ++generation;
5350+ SEGENV.step = strip.now ;
5351+ }
53025352 return FRAMETIME;
53035353} // mode_2Dgameoflife()
5304- static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = " Game Of Life@!;!,!;!;2" ;
5354+ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = " Game Of Life@!,,Blur,,,,,Mutation ;!,!;!;2;pal=11,sx=128 " ;
53055355
53065356
53075357// ///////////////////////
0 commit comments