From 57a7674b6de895b6fb5c380b9585e3798e0b1ca1 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:02:41 -0400 Subject: [PATCH 1/5] GoL - Revert Stability commit. Remove drawGrid Background and overlay options added back in. drawGrid function removed, uses loop before updating instead. Easier to edit/read. Slight change to blending. --- src/App/LedEffects.h | 87 ++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index abb5ef38..79551386 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1363,23 +1363,11 @@ class GameOfLife: public Effect { uint8_t dim() {return _3D;} //supports 3D but also 2D (1D as well?) const char * tags() {return "💡💫";} - void drawGrid(Leds &leds, byte *cells, CRGB bgColor, byte overlay = 0, bool drawAliveRandomColor = false, bool drawDead =false, bool blur = false) { - // Redraws the grid. drawAliveRandomColor is used when palettes change or new game is started drawDead is used to blur dead cells further - for (int x = 0; x < leds.size.x; x++) for (int y = 0; y < leds.size.y; y++) for (int z = 0; z < leds.size.z; z++){ - if (!leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; - bool alive = getBitValue(cells, leds.XYZNoSpin({x,y,z})); - if (overlay) { - if (overlay == 1 && alive) leds.setPixelColor({x,y,z}, bgColor, 0); - else if (overlay == 2 && !alive) leds.setPixelColor({x,y,z}, bgColor); - continue; - } - else if (drawAliveRandomColor && alive) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); - else if (drawDead && !alive) blur ? leds.setPixelColor({x,y,z}, bgColor) : leds.setPixelColor({x,y,z}, bgColor, 0); - } - } - void loop(Leds &leds) { //Binding of controls. Keep before binding of vars and keep in same order as in controls() + byte overlay = leds.sharedData.read(); + Coord3D bgC = leds.sharedData.read(); + // Coord3D bgC = mdl->getValue("Background or Overlay Color").as(); byte ruleset = leds.sharedData.read(); uint8_t speed = leds.sharedData.read(); byte lifeChance = leds.sharedData.read(); @@ -1404,7 +1392,7 @@ class GameOfLife: public Effect { byte *setUp = leds.sharedData.readWrite(); // call == 0 not working temp fix CRGB *prevPalette = leds.sharedData.readWrite(); - CRGB bgColor = CRGB::Black; + CRGB bgColor = CRGB(bgC.x, bgC.y, bgC.z); // Overlay color if toggled CRGB color = ColorFromPalette(leds.palette, random8()); // Used if all parents died // Start New Game of Life @@ -1418,10 +1406,13 @@ class GameOfLife: public Effect { memset(cells, 0, dataSize); for (int x = 0; x < leds.size.x; x++) for (int y = 0; y < leds.size.y; y++) for (int z = 0; z < leds.size.z; z++){ if (leds.projectionDimension == _3D && !leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; - if (random8(100) < lifeChance) setBitValue(cells, leds.XYZNoSpin({x,y,z}), true); + if (random8(100) < lifeChance) { + setBitValue(cells, leds.XYZNoSpin({x,y,z}), true); + leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); + } + else leds.setPixelColor({x,y,z}, bgColor, 0); } memcpy(futureCells, cells, dataSize); - drawGrid(leds, cells, bgColor, false, true, true, false); // Change CRCs uint16_t crc = crc16((const unsigned char*)cells, dataSize); @@ -1433,13 +1424,29 @@ class GameOfLife: public Effect { return; } - byte blendVal = leds.fixture->globalBlend; // Used for different blend mode - bool bgBlendMode = blendVal > 200; - if (*prevPalette != ColorFromPalette(leds.palette, 0)) { // Palette changed, redraw grid - drawGrid(leds, cells, bgColor, false, true, true); - *prevPalette = ColorFromPalette(leds.palette, 0); - } - if (*step > sys->now && !bgBlendMode) drawGrid(leds, cells, bgColor, 0, false, true, true); // Blend dead cells while paused + byte blur = leds.fixture->globalBlend; + int fadedBackground = 0; + if (blur > 220) { + fadedBackground = bgColor.r + bgColor.g + bgColor.b + 20 + (blur-220); + blur -= (blur-220); + } + bool blurDead = *step > sys->now && !overlay && !fadedBackground; + bool paletteChanged = *prevPalette != ColorFromPalette(leds.palette, 0); + + if (paletteChanged) *prevPalette = ColorFromPalette(leds.palette, 0); + // Redraw Loop + for (int x = 0; x < leds.size.x; x++) for (int y = 0; y < leds.size.y; y++) for (int z = 0; z < leds.size.z; z++){ + if (!leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; + bool alive = getBitValue(cells, leds.XYZNoSpin({x,y,z})); + // Redraw alive if palette changed or overlay1 + if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed + else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color + // Redraw dead if palette changed or overlay2 or blur paused game + if (!alive && paletteChanged) leds.setPixelColor({x,y,z}, bgColor, 0); // Remove blended dead cells + else if (!alive && overlay == 2) leds.setPixelColor({x,y,z}, bgColor, blur); // Overlay color + else if (!alive && blurDead) leds.setPixelColor({x,y,z}, bgColor, blur); // Blend dead cells while paused + } + if (!speed || *step > sys->now || sys->now - *step < 1000 / speed) return; // Check if enough time has passed for updating @@ -1455,7 +1462,7 @@ class GameOfLife: public Effect { else if (ruleset == 6) ruleString = "B367/S23"; //DrighLife *prevRuleset = ruleset; - memset(birthNumbers, 0, sizeof(bool) * 9); + memset(birthNumbers, 0, sizeof(bool) * 9); memset(surviveNumbers, 0, sizeof(bool) * 9); //Rule String Parsing @@ -1504,21 +1511,27 @@ class GameOfLife: public Effect { // Loneliness or Overpopulation cellChanged = true; setBitValue(futureCells, cIndex, false); - leds.setPixelColor(cPos, bgColor, bgBlendMode ? blendVal - 190 : blendVal); + if (!overlay) leds.setPixelColor(cPos, bgColor, blur); + else if (overlay == 2) leds.setPixelColor(cPos, bgColor, blur); } else if (!cellValue && birthNumbers[neighbors]){ // Reproduction setBitValue(futureCells, cIndex, true); cellChanged = true; + if (overlay == 2) continue; CRGB randomParentColor = color; // last seen color, overwrite if colors are found if (colorCount) randomParentColor = nColors[random8(colorCount)]; if (randomParentColor == bgColor) randomParentColor = ColorFromPalette(leds.palette, random8()); // needed for tilt, pan, roll if (random8(100) < mutation) randomParentColor = ColorFromPalette(leds.palette, random8()); // mutate + if (overlay == 1) randomParentColor = bgColor; leds.setPixelColor(cPos, randomParentColor, 0); } else { // Blending, fade dead cells further causing blurring effect to moving cells - if (!cellValue && !bgBlendMode) leds.setPixelColor(cPos, bgColor); + if (!cellValue && !overlay) { + CRGB val = leds.getPixelColor(cPos); + if (fadedBackground < val.r + val.g + val.b) leds.setPixelColor(cPos, bgColor, blur); + } } } @@ -1542,8 +1555,26 @@ class GameOfLife: public Effect { *step = sys->now; } + //Todo: + // - Allow background blending (option 1) + // - Color based on age? (Start green fade to red using blend on draw loop for alive cells) + // - Infinite Option (track born cells, spawn random glider/exploder) + // - Infinite Option (change ruleset every x generations) + void controls(Leds &leds, JsonObject parentVar) { Effect::controls(leds, parentVar); + ui->initSelect(parentVar, "Overlay", leds.sharedData.write(0), false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { + case f_UIFun: { + JsonArray options = ui->setOptions(var); + options.add("None"); + options.add("Background"); + options.add("Alive Cells"); + return true; + } + default: return false; + }}); + ui->initCoord3D(parentVar, "Background or Overlay Color", leds.sharedData.write({0,0,0}), 0, 255); + // ui->initCoord3D(parentVar, "Background or Overlay Color", {0,0,0}, 0, 255); ui->initSelect(parentVar, "ruleset", leds.sharedData.write(1), false, [](JsonObject var, uint8_t rowNr, uint8_t funType) { switch (funType) { case f_UIFun: { JsonArray options = ui->setOptions(var); From 3e3022a1b640bf486b10d439b34d64fc6cab22d5 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:51:20 -0400 Subject: [PATCH 2/5] GoL - Color by Age test --- src/App/LedEffects.h | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index 79551386..28c4acdb 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1374,6 +1374,7 @@ class GameOfLife: public Effect { uint8_t mutation = leds.sharedData.read(); bool wrap = leds.sharedData.read(); bool disablePause = leds.sharedData.read(); + bool colorByAge = leds.sharedData.read(); //Binding of loop persistent values (pointers) const uint16_t dataSize = ((leds.size.x * leds.size.y * leds.size.z + 7) / 8); @@ -1398,7 +1399,7 @@ class GameOfLife: public Effect { // Start New Game of Life if (call == 0 || *setUp != 123|| (*generation == 0 && *step < sys->now)) { *setUp = 123; // quick fix for effect starting up - *prevPalette = ColorFromPalette(leds.palette, 0); + *prevPalette = colorByAge ? CRGB::Green : ColorFromPalette(leds.palette, 0); *generation = 1; disablePause ? *step = sys->now : *step = sys->now + 1500; @@ -1408,7 +1409,7 @@ class GameOfLife: public Effect { if (leds.projectionDimension == _3D && !leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; if (random8(100) < lifeChance) { setBitValue(cells, leds.XYZNoSpin({x,y,z}), true); - leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); + leds.setPixelColor({x,y,z}, colorByAge ? CRGB::Green : ColorFromPalette(leds.palette, random8()), 0); } else leds.setPixelColor({x,y,z}, bgColor, 0); } @@ -1426,12 +1427,12 @@ class GameOfLife: public Effect { byte blur = leds.fixture->globalBlend; int fadedBackground = 0; - if (blur > 220) { + if (blur > 220 && !colorByAge) { fadedBackground = bgColor.r + bgColor.g + bgColor.b + 20 + (blur-220); blur -= (blur-220); } bool blurDead = *step > sys->now && !overlay && !fadedBackground; - bool paletteChanged = *prevPalette != ColorFromPalette(leds.palette, 0); + bool paletteChanged = *prevPalette != ColorFromPalette(leds.palette, 0) && !colorByAge; if (paletteChanged) *prevPalette = ColorFromPalette(leds.palette, 0); // Redraw Loop @@ -1439,10 +1440,11 @@ class GameOfLife: public Effect { if (!leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; bool alive = getBitValue(cells, leds.XYZNoSpin({x,y,z})); // Redraw alive if palette changed or overlay1 - if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed - else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color + if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed + else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color + else if (alive && colorByAge && !*generation) leds.setPixelColor({x,y,z}, CRGB::Red, 248); // Age alive cells while paused // Redraw dead if palette changed or overlay2 or blur paused game - if (!alive && paletteChanged) leds.setPixelColor({x,y,z}, bgColor, 0); // Remove blended dead cells + if (!alive && paletteChanged) leds.setPixelColor({x,y,z}, bgColor, 0); // Remove blended dead cells else if (!alive && overlay == 2) leds.setPixelColor({x,y,z}, bgColor, blur); // Overlay color else if (!alive && blurDead) leds.setPixelColor({x,y,z}, bgColor, blur); // Blend dead cells while paused } @@ -1524,7 +1526,9 @@ class GameOfLife: public Effect { if (randomParentColor == bgColor) randomParentColor = ColorFromPalette(leds.palette, random8()); // needed for tilt, pan, roll if (random8(100) < mutation) randomParentColor = ColorFromPalette(leds.palette, random8()); // mutate if (overlay == 1) randomParentColor = bgColor; - leds.setPixelColor(cPos, randomParentColor, 0); + // leds.setPixelColor(cPos, randomParentColor, 0); + leds.setPixelColor(cPos, colorByAge ? CRGB::Green : randomParentColor, 0); + } else { // Blending, fade dead cells further causing blurring effect to moving cells @@ -1532,6 +1536,7 @@ class GameOfLife: public Effect { CRGB val = leds.getPixelColor(cPos); if (fadedBackground < val.r + val.g + val.b) leds.setPixelColor(cPos, bgColor, blur); } + if (cellValue && colorByAge) leds.setPixelColor(cPos, CRGB::Red, 248); } } @@ -1555,12 +1560,6 @@ class GameOfLife: public Effect { *step = sys->now; } - //Todo: - // - Allow background blending (option 1) - // - Color based on age? (Start green fade to red using blend on draw loop for alive cells) - // - Infinite Option (track born cells, spawn random glider/exploder) - // - Infinite Option (change ruleset every x generations) - void controls(Leds &leds, JsonObject parentVar) { Effect::controls(leds, parentVar); ui->initSelect(parentVar, "Overlay", leds.sharedData.write(0), false, [](JsonObject var, unsigned8 rowNr, unsigned8 funType) { switch (funType) { @@ -1589,12 +1588,13 @@ class GameOfLife: public Effect { } default: return false; }}); - ui->initText(parentVar, "Custom Rule String", "B/S"); - ui->initSlider(parentVar, "Game Speed (FPS)", leds.sharedData.write(20), 0, 60); - ui->initSlider(parentVar, "Starting Life Density", leds.sharedData.write(32), 10, 90); - ui->initSlider(parentVar, "Mutation Chance", leds.sharedData.write(2), 0, 100); - ui->initCheckBox(parentVar, "Wrap", leds.sharedData.write(true)); - ui->initCheckBox(parentVar, "Disable Pause", leds.sharedData.write(false)); + ui->initText (parentVar, "Custom Rule String", "B/S"); + ui->initSlider (parentVar, "Game Speed (FPS)", leds.sharedData.write(20), 0, 60); + ui->initSlider (parentVar, "Starting Life Density", leds.sharedData.write(32), 10, 90); + ui->initSlider (parentVar, "Mutation Chance", leds.sharedData.write(2), 0, 100); + ui->initCheckBox(parentVar, "Wrap", leds.sharedData.write(true)); + ui->initCheckBox(parentVar, "Disable Pause", leds.sharedData.write(false)); + ui->initCheckBox(parentVar, "Color By Age", leds.sharedData.write(false)); } }; //GameOfLife From 997d95ed04c15211f0b8b51b8537067755411dd2 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:16:07 -0400 Subject: [PATCH 3/5] GoL - Disable wrap for solo gliders Large rectangular matrices can take a long time for a glider to reach its' origin. This turns off wrap so it hits a wall much earlier. --- src/App/LedEffects.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index 28c4acdb..b64dcf92 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1425,6 +1425,7 @@ class GameOfLife: public Effect { return; } + int aliveCount = 0; byte blur = leds.fixture->globalBlend; int fadedBackground = 0; if (blur > 220 && !colorByAge) { @@ -1439,6 +1440,7 @@ class GameOfLife: public Effect { for (int x = 0; x < leds.size.x; x++) for (int y = 0; y < leds.size.y; y++) for (int z = 0; z < leds.size.z; z++){ if (!leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; bool alive = getBitValue(cells, leds.XYZNoSpin({x,y,z})); + if (alive) aliveCount++; // Redraw alive if palette changed or overlay1 if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color @@ -1492,7 +1494,7 @@ class GameOfLife: public Effect { for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) for (int k = -1; k <= 1; k++) { // iterate through 3*3*3 matrix if (i==0 && j==0 && k==0) continue; // ignore itself Coord3D nPos = {x+i, y+j, z+k}; // neighbor position - if (!wrap || leds.size.z > 1 || (*generation) % 1500 == 0) { //no wrap, never wrap 3D, disable wrap every 1500 generations to prevent undetected repeats + if (!wrap || leds.size.z > 1 || (*generation) % 1500 == 0 || aliveCount == 5) { //no wrap, never wrap 3D, disable wrap every 1500 generations to prevent undetected repeats if (nPos.isOutofBounds(leds.size)) continue; } else { // wrap around 2D if (k != 0) continue; //no z axis (wrap around only for x and y From 6b1e8bacaca29a07cc8506113066f519c9d56a71 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:18:09 -0400 Subject: [PATCH 4/5] GoL - Infinite Mode test First attempt at infinite mode. Spawns R pentominoes/Gliders randomly and when repeat detection triggers. --- src/App/LedEffects.h | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index b64dcf92..4aeaa87c 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1363,6 +1363,31 @@ class GameOfLife: public Effect { uint8_t dim() {return _3D;} //supports 3D but also 2D (1D as well?) const char * tags() {return "💡💫";} + void placePentomino(Leds &leds, byte *futureCells) { + byte pattern[5][2] = {{1, 0}, {0, 1}, {1, 1}, {2, 1}, {2, 2}}; // R-pentomino + if (!random8(5)) pattern[0][1] = 3; // 1/5 chance to use glider + for (int attempts = 0; attempts < 100; attempts++) { + int x = random8(1, leds.size.x - 3); + int y = random8(1, leds.size.y - 5); + int z = random8(2) * (leds.size.z - 1); + bool canPlace = true; + for (int i = 0; i < 5; i++) { + int nx = x + pattern[i][0]; + int ny = y + pattern[i][1]; + if (getBitValue(futureCells, leds.XYZNoSpin({nx, ny, z}))) {canPlace = false; break;} + } + if (canPlace || attempts == 99) { + for (int i = 0; i < 5; i++) { + int nx = x + pattern[i][0]; + int ny = y + pattern[i][1]; + setBitValue(futureCells, leds.XYZNoSpin({nx, ny, z}), true); + leds.setPixelColor({nx, ny, z}, ColorFromPalette(leds.palette, random8()), 0); + } + return; + } + } + } + void loop(Leds &leds) { //Binding of controls. Keep before binding of vars and keep in same order as in controls() byte overlay = leds.sharedData.read(); @@ -1375,6 +1400,7 @@ class GameOfLife: public Effect { bool wrap = leds.sharedData.read(); bool disablePause = leds.sharedData.read(); bool colorByAge = leds.sharedData.read(); + bool infinite = leds.sharedData.read(); //Binding of loop persistent values (pointers) const uint16_t dataSize = ((leds.size.x * leds.size.y * leds.size.z + 7) / 8); @@ -1549,6 +1575,11 @@ class GameOfLife: public Effect { bool repetition = false; if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC || crc == *cubeGliderCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values + if ((repetition && infinite) || (infinite && !random8(50))) { + placePentomino(leds, futureCells); // place R-pentomino/Glider if infinite mode is enabled + memcpy(cells, futureCells, dataSize); + repetition = false; + } if (repetition) { *generation = 0; //reset on next call disablePause ? *step = sys->now : *step = sys->now + 1000; @@ -1597,6 +1628,7 @@ class GameOfLife: public Effect { ui->initCheckBox(parentVar, "Wrap", leds.sharedData.write(true)); ui->initCheckBox(parentVar, "Disable Pause", leds.sharedData.write(false)); ui->initCheckBox(parentVar, "Color By Age", leds.sharedData.write(false)); + ui->initCheckBox(parentVar, "Infinite", leds.sharedData.write(false)); } }; //GameOfLife From 96f57ff58460c2ed56b066e53f36cf9536c4e9c3 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:43:08 -0400 Subject: [PATCH 5/5] GoL - Color fix & Infinite Improvement Fixed small color bug when all parents died. Infinite now spawn a pentomino when alive density below 5%. --- src/App/LedEffects.h | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/App/LedEffects.h b/src/App/LedEffects.h index 4aeaa87c..3b158094 100644 --- a/src/App/LedEffects.h +++ b/src/App/LedEffects.h @@ -1363,9 +1363,10 @@ class GameOfLife: public Effect { uint8_t dim() {return _3D;} //supports 3D but also 2D (1D as well?) const char * tags() {return "💡💫";} - void placePentomino(Leds &leds, byte *futureCells) { + void placePentomino(Leds &leds, byte *futureCells, bool colorByAge) { byte pattern[5][2] = {{1, 0}, {0, 1}, {1, 1}, {2, 1}, {2, 2}}; // R-pentomino if (!random8(5)) pattern[0][1] = 3; // 1/5 chance to use glider + CRGB color = ColorFromPalette(leds.palette, random8()); for (int attempts = 0; attempts < 100; attempts++) { int x = random8(1, leds.size.x - 3); int y = random8(1, leds.size.y - 5); @@ -1381,7 +1382,7 @@ class GameOfLife: public Effect { int nx = x + pattern[i][0]; int ny = y + pattern[i][1]; setBitValue(futureCells, leds.XYZNoSpin({nx, ny, z}), true); - leds.setPixelColor({nx, ny, z}, ColorFromPalette(leds.palette, random8()), 0); + leds.setPixelColor({nx, ny, z}, colorByAge ? CRGB::Green : color, 0); } return; } @@ -1392,7 +1393,6 @@ class GameOfLife: public Effect { //Binding of controls. Keep before binding of vars and keep in same order as in controls() byte overlay = leds.sharedData.read(); Coord3D bgC = leds.sharedData.read(); - // Coord3D bgC = mdl->getValue("Background or Overlay Color").as(); byte ruleset = leds.sharedData.read(); uint8_t speed = leds.sharedData.read(); byte lifeChance = leds.sharedData.read(); @@ -1451,7 +1451,8 @@ class GameOfLife: public Effect { return; } - int aliveCount = 0; + int aliveCount = 0; + int deadCount = 0; byte blur = leds.fixture->globalBlend; int fadedBackground = 0; if (blur > 220 && !colorByAge) { @@ -1466,18 +1467,17 @@ class GameOfLife: public Effect { for (int x = 0; x < leds.size.x; x++) for (int y = 0; y < leds.size.y; y++) for (int z = 0; z < leds.size.z; z++){ if (!leds.isMapped(leds.XYZNoSpin({x,y,z}))) continue; bool alive = getBitValue(cells, leds.XYZNoSpin({x,y,z})); - if (alive) aliveCount++; + if (alive) aliveCount++; else deadCount++; // Redraw alive if palette changed or overlay1 - if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed - else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color - else if (alive && colorByAge && !*generation) leds.setPixelColor({x,y,z}, CRGB::Red, 248); // Age alive cells while paused + if (alive && paletteChanged) leds.setPixelColor({x,y,z}, ColorFromPalette(leds.palette, random8()), 0); // Random color if palette changed + else if (alive && overlay == 1) leds.setPixelColor({x,y,z}, bgColor, 0); // Overlay color + else if (alive && colorByAge && !*generation) leds.setPixelColor({x,y,z}, CRGB::Red, 248); // Age alive cells while paused // Redraw dead if palette changed or overlay2 or blur paused game if (!alive && paletteChanged) leds.setPixelColor({x,y,z}, bgColor, 0); // Remove blended dead cells else if (!alive && overlay == 2) leds.setPixelColor({x,y,z}, bgColor, blur); // Overlay color else if (!alive && blurDead) leds.setPixelColor({x,y,z}, bgColor, blur); // Blend dead cells while paused } - if (!speed || *step > sys->now || sys->now - *step < 1000 / speed) return; // Check if enough time has passed for updating //Rule set for game of life @@ -1530,8 +1530,9 @@ class GameOfLife: public Effect { // count neighbors and store up to 9 neighbor colors if (getBitValue(cells, nIndex)) { //if alive neighbors++; - if (!getBitValue(futureCells, nIndex)) continue; //skip if parent died in this loop (color lost or blended) - nColors[colorCount % 9] = leds.getPixelColor(nPos); + if (!getBitValue(futureCells, nIndex) || leds.getPixelColor(nPos) == bgColor) continue; //skip if parent died in this loop (color lost or blended) + color = leds.getPixelColor(nPos); + nColors[colorCount % 9] = color; colorCount++; } } @@ -1551,10 +1552,8 @@ class GameOfLife: public Effect { if (overlay == 2) continue; CRGB randomParentColor = color; // last seen color, overwrite if colors are found if (colorCount) randomParentColor = nColors[random8(colorCount)]; - if (randomParentColor == bgColor) randomParentColor = ColorFromPalette(leds.palette, random8()); // needed for tilt, pan, roll - if (random8(100) < mutation) randomParentColor = ColorFromPalette(leds.palette, random8()); // mutate + if (random8(100) < mutation) randomParentColor = ColorFromPalette(leds.palette, random8()); if (overlay == 1) randomParentColor = bgColor; - // leds.setPixelColor(cPos, randomParentColor, 0); leds.setPixelColor(cPos, colorByAge ? CRGB::Green : randomParentColor, 0); } @@ -1575,8 +1574,8 @@ class GameOfLife: public Effect { bool repetition = false; if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC || crc == *cubeGliderCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values - if ((repetition && infinite) || (infinite && !random8(50))) { - placePentomino(leds, futureCells); // place R-pentomino/Glider if infinite mode is enabled + if ((repetition && infinite) || (infinite && !random8(50)) || (infinite && float(aliveCount)/(aliveCount + deadCount) < 0.05)) { + placePentomino(leds, futureCells, colorByAge); // place R-pentomino/Glider if infinite mode is enabled memcpy(cells, futureCells, dataSize); repetition = false; } @@ -1606,8 +1605,7 @@ class GameOfLife: public Effect { default: return false; }}); ui->initCoord3D(parentVar, "Background or Overlay Color", leds.sharedData.write({0,0,0}), 0, 255); - // ui->initCoord3D(parentVar, "Background or Overlay Color", {0,0,0}, 0, 255); - ui->initSelect(parentVar, "ruleset", leds.sharedData.write(1), false, [](JsonObject var, uint8_t rowNr, uint8_t funType) { switch (funType) { + ui->initSelect (parentVar, "ruleset", leds.sharedData.write(1), false, [](JsonObject var, uint8_t rowNr, uint8_t funType) { switch (funType) { case f_UIFun: { JsonArray options = ui->setOptions(var); options.add("Custom B/S");