Skip to content

Commit 6dd8f6e

Browse files
authored
Merge pull request #133 from Brandon502/mdev
Game of Life Memory Leak Fix + Other Changes Fixed memory leak using new. Removed gameoflife struct and reverted back to using pointers to segenv.data (similar to starLeds version). Added Blur slider. Blurs dead cells slowly to bgColor depending on slider instead of immediately dying. Doesn't effect game logic. Removed confusing pauseFrames, uses .step to pause now. Keeps track of last palette used and immediately recolors cells if new palette/color selected. Can be improved. Changes gameSpeed slider to be more consistent. Speed = updates/second now in increments of 4.
2 parents 2edfcb3 + 9ef8ff6 commit 6dd8f6e

File tree

1 file changed

+104
-92
lines changed

1 file changed

+104
-92
lines changed

wled00/FX.cpp

Lines changed: 104 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5164,66 +5164,61 @@ static void setBitValue(uint8_t* byteArray, size_t n, bool value) {
51645164
else
51655165
byteArray[byteIndex] &= ~(1 << bitIndex);
51665166
}
5167-
// create game of life struct to hold cells and future cells
5168-
struct gameOfLife {
5169-
uint8_t* cells;
5170-
uint8_t* futureCells;
5171-
uint8_t gliderLength;
5172-
uint16_t oscillatorCRC;
5173-
uint16_t spaceshipCRC;
5174-
};
5167+
51755168
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
51765169
// and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
51775170
if (!strip.isMatrix) return mode_static(); // not a 2D set-up
51785171

51795172
const uint16_t cols = SEGMENT.virtualWidth();
51805173
const uint16_t rows = SEGMENT.virtualHeight();
5181-
const size_t dataSize = (SEGMENT.length() / 8) + (((SEGMENT.length() % 8) != 0) ? 1 : 0); // add one byte when extra bits needed (length not a multiple of 8)
5182-
const size_t totalSize = dataSize*2 + sizeof(gameOfLife);
5174+
const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte
5175+
const size_t detectionSize = sizeof(uint16_t) * 3; // 2 CRCs, gliderLength
5176+
const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); // detectionSize + prevPalette
51835177

51845178
if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed
5185-
gameOfLife* gol = reinterpret_cast<gameOfLife*>(SEGENV.data);
5186-
5187-
if (gol->cells == nullptr) {
5188-
gol->cells = new uint8_t[dataSize];
5189-
gol->futureCells = new uint8_t[dataSize];
5190-
}
5191-
5192-
uint16_t &generation = SEGENV.aux0; //rename aux0 and aux1 for readability (not needed)
5193-
uint16_t &pauseFrames = SEGENV.aux1;
5194-
CRGB backgroundColor = SEGCOLOR(1);
5195-
CRGB color;
5179+
byte *cells = reinterpret_cast<byte*>(SEGENV.data);
5180+
byte *futureCells = reinterpret_cast<byte*>(SEGENV.data + dataSize);
5181+
uint16_t *gliderLength = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2);
5182+
uint16_t *oscillatorCRC = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2 + sizeof(uint16_t));
5183+
uint16_t *spaceshipCRC = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 2);
5184+
uint8_t *prevPalette = reinterpret_cast<uint8_t*>(SEGENV.data + dataSize * 2 + detectionSize);
5185+
5186+
uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability
5187+
bool allColors = SEGMENT.check1;
5188+
bool overlayBG = SEGMENT.check2;
5189+
bool wrap = SEGMENT.check3;
5190+
byte blur = map(SEGMENT.custom1, 0, 255, 255, 0);
5191+
bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode
5192+
byte bgBlur = map(SEGMENT.custom1 - 220, 0, 35, 255, 128);
5193+
uint32_t bgColor = SEGCOLOR(1);
5194+
uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0);
5195+
uint16_t cIndex;
51965196

51975197
if (SEGENV.call == 0) {
51985198
SEGMENT.setUpLeds();
51995199
SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially
52005200
}
5201-
//start new game of life
5202-
if ((SEGENV.call == 0 || generation == 0) && pauseFrames == 0) {
5203-
SEGENV.step = strip.now; // .step = previous call time
5201+
// Setup New Game of Life
5202+
if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) {
5203+
SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds
52045204
generation = 1;
5205-
pauseFrames = 75; // show initial state for longer
5205+
*prevPalette = SEGMENT.palette;
52065206
random16_set_seed(strip.now>>2); //seed the random generator
52075207
//Setup Grid
5208+
memset(cells, 0, dataSize);
52085209
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
5209-
uint8_t state = (random8() < 82) ? 1 : 0; // ~32% chance of being alive
5210-
if (state == 0) {
5211-
setBitValue(gol->cells, y * cols + x, false);
5212-
setBitValue(gol->futureCells, y * cols + x, false);
5213-
if (SEGMENT.check2) continue;
5214-
SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0));
5215-
}
5216-
else {
5217-
setBitValue(gol->cells, y * cols + x, true);
5218-
setBitValue(gol->futureCells, y * cols + x, true);
5219-
color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
5220-
SEGMENT.setPixelColorXY(x,y,!SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0));
5210+
cIndex = y * cols + x;
5211+
if (random8(100) < 32) { // ~32% chance of being alive
5212+
setBitValue(cells, cIndex, true);
5213+
if (!overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop
52215214
}
52225215
}
5216+
memcpy(futureCells, cells, dataSize);
52235217

5224-
//Clear CRCs
5225-
gol->oscillatorCRC = 0;
5226-
gol->spaceshipCRC = 0;
5218+
//Set CRCs
5219+
uint16_t crc = crc16((const unsigned char*)cells, dataSize);
5220+
*oscillatorCRC = crc;
5221+
*spaceshipCRC = crc;
52275222

52285223
//Calculate glider length LCM(rows,cols)*4
52295224
uint8_t a = rows;
@@ -5233,108 +5228,125 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
52335228
b = a % b;
52345229
a = t;
52355230
}
5236-
gol->gliderLength = cols * rows / a * 4;
5237-
return FRAMETIME;
5231+
*gliderLength = cols * rows / a * 4;
52385232
}
5239-
//Redraw immediately if overlay to avoid flicker
5240-
if (SEGMENT.check2) {
5241-
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
5242-
//redraw foreground/alive
5243-
if (getBitValue(gol->cells, y * cols + x)) {
5244-
color = SEGMENT.getPixelColorXY(x,y);
5245-
SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0));
5246-
}
5247-
}
5248-
}
5249-
if (pauseFrames || strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,2)) {
5250-
if(pauseFrames) pauseFrames--;
5251-
return FRAMETIME; //skip if not enough time has passed
5233+
5234+
int aliveCount = 0; // Solo glider detection
5235+
bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG;
5236+
bool palChanged = SEGMENT.palette != *prevPalette && !allColors;
5237+
if (palChanged) *prevPalette = SEGMENT.palette;
5238+
5239+
// Redraw Loop
5240+
// Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker)
5241+
// Always redraw dead cells if not overlaying background. Allows overlayFG by default.
5242+
// Generation 1 draws alive cells randomly and fades dead cells
5243+
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
5244+
cIndex = y * cols + x;
5245+
bool alive = getBitValue(cells, cIndex);
5246+
if (alive) aliveCount++;
5247+
uint32_t cellColor = SEGMENT.getPixelColorXY(x,y);
5248+
bool aliveBgColor = (alive && !overlayBG && generation == 1 && cellColor == bgColor);
5249+
5250+
if ( alive && (palChanged || (aliveBgColor && !random(12)))) { // Palette change or spawn initial colors randomly
5251+
uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
5252+
SEGMENT.setPixelColorXY(x,y, randomColor); // Recolor alive cells
5253+
aliveBgColor = false;
5254+
}
5255+
else if ( alive && overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG
5256+
if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette
5257+
else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));// Blur dead cells (paused)
5258+
else if ((!alive || aliveBgColor) && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw dead cells/alive off cells for default overlayFG
5259+
else if (!alive && !overlayBG && generation == 1) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells (bgBlendMode) on generation 1
52525260
}
5261+
5262+
if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) return FRAMETIME; //skip if not enough time has passed (1-64 updates/sec)
5263+
52535264
//Update Game of Life
52545265
bool cellChanged = false; // Detect still live and dead grids
5255-
//cell index and coordinates
5256-
uint16_t cIndex;
5266+
//cell coordinates
52575267
uint16_t cX;
52585268
uint16_t cY;
52595269
//Loop through all cells. Count neighbors, apply rules, setPixel
52605270
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
52615271
byte neighbors = 0;
52625272
byte colorCount = 0; //track number of valid colors
5263-
CRGB nColors[3]; // track 3 colors, dying cells may overwrite but this wont be used
5273+
uint32_t nColors[3]; // track 3 colors, dying cells may overwrite but this wont be used
52645274

52655275
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix
5266-
if (i==0 && j==0) continue; // ignore itself
5267-
if (!SEGMENT.check3 || generation % 1500 == 0) { //no wrap disable wrap every 1500 generations to prevent undetected repeats
5268-
cX = x+i;
5269-
cY = y+j;
5276+
if (i == 0 && j == 0) continue; // ignore itself
5277+
if (!wrap || generation % 1500 == 0 || aliveCount == 5) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats
5278+
cX = x + i;
5279+
cY = y + j;
52705280
if (cX < 0 || cY < 0 || cX >= cols || cY >= rows) continue; //skip if out of bounds
52715281
} else { //wrap around
5272-
cX = (x+i+cols) % cols;
5273-
cY = (y+j+rows) % rows;
5282+
cX = (x + i + cols) % cols;
5283+
cY = (y + j + rows) % rows;
52745284
}
5275-
cIndex = cY * cols + cX;
5285+
cIndex = cY * cols + cX; //neighbor cell index
52765286
// count neighbors and store upto 3 neighbor colors
5277-
if (getBitValue(gol->cells, cIndex)) { //if alive
5287+
if (getBitValue(cells, cIndex)) { //if alive
52785288
neighbors++;
5289+
if (!getBitValue(futureCells, cIndex) || SEGMENT.getPixelColorXY(cX,cY) == bgColor) continue; //parent just died, color lost
52795290
color = SEGMENT.getPixelColorXY(cX, cY);
5280-
if (color == backgroundColor) continue; //parent just died, color lost
5281-
nColors[colorCount%3] = color;
5291+
nColors[colorCount % 3] = color;
52825292
colorCount++;
52835293
}
52845294
}
52855295

52865296
// Rules of Life
5287-
bool cellValue = getBitValue(gol->cells, y * cols + x);
5297+
cIndex = y * cols + x; //current cell index
5298+
bool cellValue = getBitValue(cells, cIndex);
52885299
if ((cellValue) && (neighbors < 2 || neighbors > 3)) {
5289-
// Loneliness or overpopulation
5300+
// Loneliness or Overpopulation
52905301
cellChanged = true;
5291-
setBitValue(gol->futureCells, y * cols + x, false);
5292-
if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0));
5302+
setBitValue(futureCells, cIndex, false);
5303+
// Blur/turn off dying cells
5304+
if (!overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur));
52935305
}
52945306
else if (!(cellValue) && (neighbors == 3)) {
52955307
// Reproduction
5296-
setBitValue(gol->futureCells, y * cols + x, true);
5308+
setBitValue(futureCells, cIndex, true);
52975309
cellChanged = true;
5298-
// find dominant color and assign it to a cell
5299-
// no longer storing colors, if parent dies the color is lost
5300-
CRGB dominantColor;
5310+
// find dominant color and assign it to a new born cell no longer storing colors, if parent dies the color is lost
5311+
uint32_t dominantColor;
53015312
if (colorCount == 3) { //All parents survived
53025313
if ((nColors[0] == nColors[1]) || (nColors[0] == nColors[2])) dominantColor = nColors[0];
53035314
else if (nColors[1] == nColors[2]) dominantColor = nColors[1];
5304-
else dominantColor = nColors[random8()%3];
5315+
else dominantColor = nColors[random8(3)];
53055316
}
5306-
else if (colorCount == 2) dominantColor = nColors[random8()%2]; // 1 leading parent died
5307-
else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died
5308-
else dominantColor = color; // all parents died last used color
5317+
else if (colorCount == 2) dominantColor = nColors[random8(2)]; // 1 leading parent died
5318+
else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died
5319+
else dominantColor = color; // all parents died use last seen color
53095320
// mutate color chance
5310-
if (random8() < SEGMENT.intensity) dominantColor = !SEGMENT.check1?SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0): random16()*random16();
5311-
5312-
if (SEGMENT.check1) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors
5321+
if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
53135322
SEGMENT.setPixelColorXY(x,y, dominantColor);
5314-
}
5323+
}
5324+
else { // blur dead cells
5325+
if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, blur));
5326+
}
53155327
}
53165328
//update cell values
5317-
memcpy(gol->cells, gol->futureCells, dataSize);
5329+
memcpy(cells, futureCells, dataSize);
53185330

53195331
// Get current crc value
5320-
uint16_t crc = crc16((const unsigned char*)gol->cells, dataSize);
5332+
uint16_t crc = crc16((const unsigned char*)cells, dataSize);
53215333

53225334
bool repetition = false;
5323-
if (!cellChanged || crc == gol->oscillatorCRC || crc == gol->spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values
5335+
if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values
53245336
if (repetition) {
5325-
generation = 0; // reset on next call
5326-
pauseFrames = 50;
5337+
generation = 0; // reset on next call
5338+
SEGENV.step += 1000; // pause final generation for 1 second
53275339
return FRAMETIME;
53285340
}
53295341
// Update CRC values
5330-
if (generation % 16 == 0) gol->oscillatorCRC = crc;
5331-
if (generation % gol->gliderLength == 0) gol->spaceshipCRC = crc;
5342+
if (generation % 16 == 0) *oscillatorCRC = crc;
5343+
if (*gliderLength && generation % *gliderLength == 0) *spaceshipCRC = crc;
53325344

53335345
generation++;
53345346
SEGENV.step = strip.now;
53355347
return FRAMETIME;
53365348
} // mode_2Dgameoflife()
5337-
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=0,o3=1";
5349+
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1";
53385350

53395351
/////////////////////////
53405352
// 2D Hiphotic //

0 commit comments

Comments
 (0)