-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Game of Life Rework #4995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Game of Life Rework #4995
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5196,112 +5196,163 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f | |||||||||||||||||||
/////////////////////////////////////////// | ||||||||||||||||||||
// 2D Cellular Automata Game of life // | ||||||||||||||||||||
/////////////////////////////////////////// | ||||||||||||||||||||
typedef struct ColorCount { | ||||||||||||||||||||
CRGB color; | ||||||||||||||||||||
int8_t count; | ||||||||||||||||||||
} colorCount; | ||||||||||||||||||||
typedef struct Cell { | ||||||||||||||||||||
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2; | ||||||||||||||||||||
} Cell; | ||||||||||||||||||||
|
||||||||||||||||||||
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 | ||||||||||||||||||||
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 , Modified By: Brandon Butler | ||||||||||||||||||||
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up | ||||||||||||||||||||
const int cols = SEG_W, rows = SEG_H; | ||||||||||||||||||||
const unsigned maxIndex = cols * rows; | ||||||||||||||||||||
|
||||||||||||||||||||
const int cols = SEG_W; | ||||||||||||||||||||
const int rows = SEG_H; | ||||||||||||||||||||
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; }; | ||||||||||||||||||||
const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled | ||||||||||||||||||||
const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) | ||||||||||||||||||||
|
||||||||||||||||||||
if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed | ||||||||||||||||||||
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data); | ||||||||||||||||||||
uint16_t *crcBuffer = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize); | ||||||||||||||||||||
|
||||||||||||||||||||
CRGB backgroundColor = SEGCOLOR(1); | ||||||||||||||||||||
|
||||||||||||||||||||
if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { | ||||||||||||||||||||
SEGENV.step = strip.now; | ||||||||||||||||||||
SEGENV.aux0 = 0; | ||||||||||||||||||||
|
||||||||||||||||||||
//give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) | ||||||||||||||||||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { | ||||||||||||||||||||
unsigned state = hw_random8()%2; | ||||||||||||||||||||
if (state == 0) | ||||||||||||||||||||
SEGMENT.setPixelColorXY(x,y, backgroundColor); | ||||||||||||||||||||
else | ||||||||||||||||||||
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255)); | ||||||||||||||||||||
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed | ||||||||||||||||||||
|
||||||||||||||||||||
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data); | ||||||||||||||||||||
|
||||||||||||||||||||
Comment on lines
+5209
to
+5212
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix allocation size to match iteration domain (cols*rows). You iterate maxIndex = cols*rows but allocate SEGMENT.length() cells. In some 2D mappings these can diverge. Allocate exactly maxIndex to avoid OOB writes on atypical mappings. Apply this diff: - if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
+ if (!SEGENV.allocateData(maxIndex * sizeof(Cell))) return mode_static(); // allocation failed 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||
uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity | ||||||||||||||||||||
bool mutate = SEGMENT.check3; | ||||||||||||||||||||
uint8_t blur = map(SEGMENT.custom1, 0, 255, 255, 4); | ||||||||||||||||||||
|
||||||||||||||||||||
uint32_t bgColor = SEGCOLOR(1); | ||||||||||||||||||||
uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255); | ||||||||||||||||||||
|
||||||||||||||||||||
bool setup = SEGENV.call == 0; | ||||||||||||||||||||
if (setup) { | ||||||||||||||||||||
// Calculate glider length LCM(rows,cols)*4 once | ||||||||||||||||||||
unsigned a = rows, b = cols; | ||||||||||||||||||||
while (b) { unsigned t = b; b = a % b; a = t; } | ||||||||||||||||||||
gliderLength = (cols * rows / a) << 2; | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+5223
to
+5226
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard gliderLength against overflow. gliderLength = LCM(rows,cols)*4 stored in uint16_t can overflow on larger grids. Use 32-bit during calc and clamp/store to aux1. - gliderLength = (cols * rows / a) << 2;
+ uint32_t gl = ((uint32_t)cols * (uint32_t)rows / a) << 2;
+ gliderLength = (gl > UINT16_MAX) ? UINT16_MAX : (uint16_t)gl; 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||
|
||||||||||||||||||||
if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix | ||||||||||||||||||||
bool paused = SEGENV.step > strip.now; | ||||||||||||||||||||
|
||||||||||||||||||||
// Setup New Game of Life | ||||||||||||||||||||
if ((!paused && generation == 0) || setup) { | ||||||||||||||||||||
SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds | ||||||||||||||||||||
generation = 1; | ||||||||||||||||||||
paused = true; | ||||||||||||||||||||
//Setup Grid | ||||||||||||||||||||
memset(cells, 0, maxIndex * sizeof(Cell)); | ||||||||||||||||||||
|
||||||||||||||||||||
for (unsigned i = maxIndex; i--; ) { | ||||||||||||||||||||
bool isAlive = !hw_random8(3); // ~33% | ||||||||||||||||||||
cells[i].alive = isAlive; | ||||||||||||||||||||
cells[i].faded = !isAlive; | ||||||||||||||||||||
unsigned x = i % cols, y = i / cols; | ||||||||||||||||||||
cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1); | ||||||||||||||||||||
|
||||||||||||||||||||
SEGMENT.setPixelColorXY(x, y, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (paused || (strip.now - SEGENV.step < 1000 / map(SEGMENT.speed,0,255,1,42))) { | ||||||||||||||||||||
// Redraw if paused or between updates to remove blur | ||||||||||||||||||||
for (unsigned i = maxIndex; i--; ) { | ||||||||||||||||||||
if (!cells[i].alive) { | ||||||||||||||||||||
uint32_t cellColor = SEGMENT.getPixelColorXY(i % cols, i / cols); | ||||||||||||||||||||
if (cellColor != bgColor) { | ||||||||||||||||||||
uint32_t newColor; | ||||||||||||||||||||
bool needsColor = false; | ||||||||||||||||||||
if (cells[i].faded) { newColor = bgColor; needsColor = true; } | ||||||||||||||||||||
else { | ||||||||||||||||||||
uint32_t blended = color_blend(cellColor, bgColor, 2); | ||||||||||||||||||||
if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; } | ||||||||||||||||||||
newColor = blended; needsColor = true; | ||||||||||||||||||||
} | ||||||||||||||||||||
if (needsColor) SEGMENT.setPixelColorXY(i % cols, i / cols, newColor); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black; | ||||||||||||||||||||
memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen); | ||||||||||||||||||||
} else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) { | ||||||||||||||||||||
// update only when appropriate time passes (in 42 FPS slots) | ||||||||||||||||||||
return FRAMETIME; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
// Repeat detection | ||||||||||||||||||||
bool updateOscillator = generation % 16 == 0; | ||||||||||||||||||||
bool updateSpaceship = gliderLength && generation % gliderLength == 0; | ||||||||||||||||||||
bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true; | ||||||||||||||||||||
|
||||||||||||||||||||
unsigned cIndex = maxIndex-1; | ||||||||||||||||||||
for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) { | ||||||||||||||||||||
Cell& cell = cells[cIndex]; | ||||||||||||||||||||
|
||||||||||||||||||||
if (cell.alive) emptyGrid = false; | ||||||||||||||||||||
if (cell.oscillatorCheck != cell.alive) repeatingOscillator = false; | ||||||||||||||||||||
if (cell.spaceshipCheck != cell.alive) repeatingSpaceship = false; | ||||||||||||||||||||
if (updateOscillator) cell.oscillatorCheck = cell.alive; | ||||||||||||||||||||
if (updateSpaceship) cell.spaceshipCheck = cell.alive; | ||||||||||||||||||||
|
||||||||||||||||||||
unsigned neighbors = 0, aliveParents = 0, parentIdx[3]; | ||||||||||||||||||||
// Count alive neighbors | ||||||||||||||||||||
for (int i = 1; i >= -1; i--) for (int j = 1; j >= -1; j--) if (i || j) { | ||||||||||||||||||||
int nX = x + j, nY = y + i; | ||||||||||||||||||||
if (cell.edgeCell) { | ||||||||||||||||||||
nX = (nX + cols) % cols; | ||||||||||||||||||||
nY = (nY + rows) % rows; | ||||||||||||||||||||
} | ||||||||||||||||||||
unsigned nIndex = nX + nY * cols; | ||||||||||||||||||||
Cell& neighbor = cells[nIndex]; | ||||||||||||||||||||
if (neighbor.alive) { | ||||||||||||||||||||
if (++neighbors > 3) break; | ||||||||||||||||||||
if (!neighbor.toggleStatus) { // Alive and not dying | ||||||||||||||||||||
parentIdx[aliveParents++] = nIndex; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
//copy previous leds (save previous generation) | ||||||||||||||||||||
//NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset | ||||||||||||||||||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y); | ||||||||||||||||||||
|
||||||||||||||||||||
//calculate new leds | ||||||||||||||||||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { | ||||||||||||||||||||
|
||||||||||||||||||||
colorCount colorsCount[9]; // count the different colors in the 3*3 matrix | ||||||||||||||||||||
for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount | ||||||||||||||||||||
|
||||||||||||||||||||
// iterate through neighbors and count them and their different colors | ||||||||||||||||||||
int neighbors = 0; | ||||||||||||||||||||
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix | ||||||||||||||||||||
if (i==0 && j==0) continue; // ignore itself | ||||||||||||||||||||
// wrap around segment | ||||||||||||||||||||
int xx = x+i, yy = y+j; | ||||||||||||||||||||
if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0; | ||||||||||||||||||||
if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0; | ||||||||||||||||||||
|
||||||||||||||||||||
unsigned xy = XY(xx, yy); // previous cell xy to check | ||||||||||||||||||||
// count different neighbours and colors | ||||||||||||||||||||
if (prevLeds[xy] != backgroundColor) { | ||||||||||||||||||||
neighbors++; | ||||||||||||||||||||
bool colorFound = false; | ||||||||||||||||||||
int k; | ||||||||||||||||||||
for (k=0; k<9 && colorsCount[k].count != 0; k++) | ||||||||||||||||||||
if (colorsCount[k].color == prevLeds[xy]) { | ||||||||||||||||||||
colorsCount[k].count++; | ||||||||||||||||||||
colorFound = true; | ||||||||||||||||||||
} | ||||||||||||||||||||
if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array | ||||||||||||||||||||
uint32_t newColor; | ||||||||||||||||||||
bool needsColor = false; | ||||||||||||||||||||
|
||||||||||||||||||||
if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation | ||||||||||||||||||||
cell.toggleStatus = 1; | ||||||||||||||||||||
if (blur == 255) cell.faded = 1; | ||||||||||||||||||||
newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColorXY(x, y), bgColor, blur); | ||||||||||||||||||||
needsColor = true; | ||||||||||||||||||||
} | ||||||||||||||||||||
else if (!cell.alive) { | ||||||||||||||||||||
if (neighbors == 3 && (!mutate || hw_random8(128)) || // Normal birth with 1/128 failure chance if mutate | ||||||||||||||||||||
(mutate && neighbors == 2 && !hw_random8(128))) { // Mutation birth with 2 neighbors with 1/128 chance if mutate | ||||||||||||||||||||
cell.toggleStatus = 1; | ||||||||||||||||||||
cell.faded = 0; | ||||||||||||||||||||
|
||||||||||||||||||||
if (aliveParents) { | ||||||||||||||||||||
// Set color based on random neighbor | ||||||||||||||||||||
unsigned parentIndex = parentIdx[random8(aliveParents)]; | ||||||||||||||||||||
birthColor = SEGMENT.getPixelColorXY(parentIndex % cols, parentIndex / cols); | ||||||||||||||||||||
} | ||||||||||||||||||||
newColor = birthColor; | ||||||||||||||||||||
needsColor = true; | ||||||||||||||||||||
} | ||||||||||||||||||||
else if (!cell.faded) {// No change, fade dead cells | ||||||||||||||||||||
uint32_t cellColor = SEGMENT.getPixelColorXY(x, y); | ||||||||||||||||||||
uint32_t blended = color_blend(cellColor, bgColor, blur); | ||||||||||||||||||||
if (blended == cellColor) { blended = bgColor; cell.faded = 1; } | ||||||||||||||||||||
newColor = blended; | ||||||||||||||||||||
needsColor = true; | ||||||||||||||||||||
} | ||||||||||||||||||||
} // i,j | ||||||||||||||||||||
|
||||||||||||||||||||
// Rules of Life | ||||||||||||||||||||
uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte | ||||||||||||||||||||
uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0); | ||||||||||||||||||||
if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness | ||||||||||||||||||||
else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation | ||||||||||||||||||||
else if ((col == bgc) && (neighbors == 3)) { // Reproduction | ||||||||||||||||||||
// find dominant color and assign it to a cell | ||||||||||||||||||||
colorCount dominantColorCount = {backgroundColor, 0}; | ||||||||||||||||||||
for (int i=0; i<9 && colorsCount[i].count != 0; i++) | ||||||||||||||||||||
if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i]; | ||||||||||||||||||||
// assign the dominant color w/ a bit of randomness to avoid "gliders" | ||||||||||||||||||||
if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); | ||||||||||||||||||||
} else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation | ||||||||||||||||||||
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255)); | ||||||||||||||||||||
} | ||||||||||||||||||||
// else do nothing! | ||||||||||||||||||||
} //x,y | ||||||||||||||||||||
|
||||||||||||||||||||
// calculate CRC16 of leds | ||||||||||||||||||||
uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize); | ||||||||||||||||||||
// check if we had same CRC and reset if needed | ||||||||||||||||||||
bool repetition = false; | ||||||||||||||||||||
for (int i=0; i<crcBufferLen && !repetition; i++) repetition = (crc == crcBuffer[i]); // (Ewowi) | ||||||||||||||||||||
// same CRC would mean image did not change or was repeating itself | ||||||||||||||||||||
if (!repetition) SEGENV.step = strip.now; //if no repetition avoid reset | ||||||||||||||||||||
// remember CRCs across frames | ||||||||||||||||||||
crcBuffer[SEGENV.aux0] = crc; | ||||||||||||||||||||
++SEGENV.aux0 %= crcBufferLen; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (needsColor) SEGMENT.setPixelColorXY(x, y, newColor); | ||||||||||||||||||||
} | ||||||||||||||||||||
// Loop through cells, if toggle, swap alive status | ||||||||||||||||||||
for (unsigned i = maxIndex; i--; ) { | ||||||||||||||||||||
cells[i].alive ^= cells[i].toggleStatus; | ||||||||||||||||||||
cells[i].toggleStatus = 0; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (repeatingOscillator || repeatingSpaceship || emptyGrid) { | ||||||||||||||||||||
generation = 0; // reset on next call | ||||||||||||||||||||
SEGENV.step += 1000; // pause final generation for 1 second | ||||||||||||||||||||
} | ||||||||||||||||||||
else { | ||||||||||||||||||||
++generation; | ||||||||||||||||||||
SEGENV.step = strip.now; | ||||||||||||||||||||
} | ||||||||||||||||||||
return FRAMETIME; | ||||||||||||||||||||
} // mode_2Dgameoflife() | ||||||||||||||||||||
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2"; | ||||||||||||||||||||
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128"; | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
///////////////////////// | ||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Enforce 1-byte Cell packing at compile time.
Bitfield packing is implementation-defined. Add a static_assert to guarantee 1 byte per cell across toolchains.
typedef struct Cell { uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2; } Cell; +static_assert(sizeof(Cell) == 1, "Cell must be 1 byte to meet memory/perf goals");
🤖 Prompt for AI Agents