Game of Life Optimizations
Uses more memory to achieve much higher framerates on large setups. Neighbor counts are stored instead of constantly recalculated. CRC is no longer used for repeat detection so false positives are no longer possible.
This commit is contained in:
463
wled00/FX.cpp
463
wled00/FX.cpp
@@ -5228,8 +5228,276 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
|
||||
|
||||
|
||||
///////////////////////////////////////////
|
||||
// 2D Cellular Automata Game of life //
|
||||
// 2D Cellular Automata Game of Life //
|
||||
///////////////////////////////////////////
|
||||
typedef struct Cell {
|
||||
uint8_t currentStatus : 1, currentNeighborCount : 4, oscillatorCheck : 1, spaceshipCheck : 1, unused : 1; // current data + repeated detection
|
||||
uint8_t futureStatus : 1, futureNeighborCount : 4, edgeCell : 1, superDead : 1, hasColor : 1; // future data + opimizations
|
||||
} Cell;
|
||||
|
||||
class GameOfLifeGrid {
|
||||
private:
|
||||
Cell* cells;
|
||||
const int cols, rows, maxIndex;
|
||||
const int nOffsets[8] = {-cols-1, -cols, -cols+1, -1, 1, cols-1, cols, cols+1}; // Neighbor offsets
|
||||
const int offsetX[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
|
||||
const int offsetY[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
|
||||
public:
|
||||
GameOfLifeGrid(Cell* data, int c, int r) : cells(data), cols(c), rows(r), maxIndex(r * c) {}
|
||||
void getNeighborIndexes(unsigned neighbors[9], unsigned cIndex, unsigned x, unsigned y, bool wrap) {
|
||||
bool edgeCell = cells[cIndex].edgeCell;
|
||||
unsigned neighborCount = 0;
|
||||
for (unsigned i = 0; i < 8; ++i) {
|
||||
unsigned nIndex = cIndex + nOffsets[i];
|
||||
if (edgeCell) {
|
||||
int nX = x + offsetX[i], nY = y + offsetY[i];
|
||||
if (nX < 0) {if (!wrap) continue; nIndex += cols;}
|
||||
else if (nX >= cols) {if (!wrap) continue; nIndex -= cols;}
|
||||
if (nY < 0) {if (!wrap) continue; nIndex += maxIndex;}
|
||||
else if (nY >= rows) {if (!wrap) continue; nIndex -= maxIndex;}
|
||||
}
|
||||
neighbors[++neighborCount] = nIndex;
|
||||
}
|
||||
neighbors[0] = neighborCount;
|
||||
}
|
||||
void setCell(unsigned cIndex, unsigned x, unsigned y, bool alive, bool wrap) {
|
||||
Cell* cell = &cells[cIndex];
|
||||
cell->futureStatus = alive;
|
||||
if (alive == cell->currentStatus) return; // No change
|
||||
unsigned neighbors[9];
|
||||
getNeighborIndexes(neighbors, cIndex, x, y, wrap);
|
||||
int val = alive ? 1 : -1;
|
||||
for (unsigned i = 1; i <= neighbors[0]; ++i) cells[neighbors[i]].futureNeighborCount += val;
|
||||
}
|
||||
void recalculateEdgeNeighbors(bool wrap) {
|
||||
unsigned cIndex = 0;
|
||||
for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) {
|
||||
Cell* cell = &cells[cIndex];
|
||||
if (cell->edgeCell) {
|
||||
cell->futureNeighborCount = 0;
|
||||
cell->currentNeighborCount = 0;
|
||||
cell->superDead = 0;
|
||||
|
||||
unsigned neighbors[9];
|
||||
getNeighborIndexes(neighbors, cIndex, x, y, wrap);
|
||||
|
||||
for (unsigned i = 1; i <= neighbors[0]; ++i) {
|
||||
if (cells[neighbors[i]].currentStatus) { ++cell->futureNeighborCount; ++cell->currentNeighborCount; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void shiftFutureToCurrent() {
|
||||
for (unsigned i = 0; i < maxIndex; ++i) {
|
||||
cells[i].currentStatus = cells[i].futureStatus;
|
||||
cells[i].currentNeighborCount = cells[i].futureNeighborCount;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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) return mode_static(); // not a 2D set-up
|
||||
|
||||
const uint16_t cols = SEGMENT.virtualWidth();
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
const size_t dataSize = SEGMENT.length() * sizeof(Cell); // Cell = 2 bytes
|
||||
const size_t totalSize = dataSize + 5; // 5 bytes for prevRows, prevCols, prevPalette, prevWrap, soloGlider
|
||||
|
||||
if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed
|
||||
uint8_t *prevRows = reinterpret_cast<uint8_t*>(SEGENV.data);
|
||||
uint8_t *prevCols = reinterpret_cast<uint8_t*>(SEGENV.data + 1);
|
||||
uint8_t *prevPalette = reinterpret_cast<uint8_t*>(SEGENV.data + 2);
|
||||
bool *prevWrap = reinterpret_cast<bool*> (SEGENV.data + 3);
|
||||
bool *soloGlider = reinterpret_cast<bool*> (SEGENV.data + 4);
|
||||
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data + 5);
|
||||
|
||||
uint16_t& generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability
|
||||
uint16_t& gliderLength = SEGENV.aux1;
|
||||
bool allColors = SEGMENT.check1;
|
||||
bool overlayBG = SEGMENT.check2;
|
||||
bool wrap = SEGMENT.check3;
|
||||
bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode
|
||||
byte blur = overlayBG ? 255 : bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 220, 255, 10);
|
||||
uint32_t bgColor = SEGCOLOR(1);
|
||||
|
||||
GameOfLifeGrid grid(cells, cols, rows);
|
||||
|
||||
// If rows or cols change due to mirror/transpose, edges and neighbor counts need to be recalculated. Just reset the game.
|
||||
bool setup = SEGENV.call == 0 || rows != *prevRows || cols != *prevCols;
|
||||
|
||||
if (setup) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(bgColor); // to make sure that segment buffer and physical leds are aligned initially
|
||||
SEGENV.step = 0;
|
||||
*prevRows = rows;
|
||||
*prevCols = cols;
|
||||
|
||||
// Calculate glider length LCM(rows,cols)*4 once
|
||||
uint8_t a = rows;
|
||||
uint8_t b = cols;
|
||||
while (b) {
|
||||
uint8_t t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
gliderLength = cols * rows / a * 4;
|
||||
}
|
||||
|
||||
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
|
||||
paused = true;
|
||||
generation = 1;
|
||||
*prevWrap = wrap;
|
||||
*prevPalette = SEGMENT.palette;
|
||||
|
||||
//Setup Grid
|
||||
memset(cells, 0, dataSize);
|
||||
#if !ESP32
|
||||
random16_set_seed(strip.now>>2); //seed the random generator
|
||||
#else
|
||||
const uint32_t chance = UINT32_MAX * 0.32;
|
||||
#endif
|
||||
unsigned cIndex = 0;
|
||||
for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) {
|
||||
if (x == 0 || x == cols - 1 || y == 0 || y == rows - 1) cells[cIndex].edgeCell = 1;
|
||||
#if ESP32
|
||||
if (esp_random() < chance) grid.setCell(cIndex, x, y, true, wrap); // ~32% chance of being alive
|
||||
#else
|
||||
if (random16(100) < 32) grid.setCell(cIndex, x, y, true, wrap);
|
||||
#endif
|
||||
else { cells[cIndex].hasColor = 1; cells[cIndex].superDead = 1; }
|
||||
}
|
||||
grid.shiftFutureToCurrent(); // Shift future states to current states
|
||||
}
|
||||
|
||||
bool palChanged = SEGMENT.palette != *prevPalette && !allColors;
|
||||
if (palChanged) *prevPalette = SEGMENT.palette;
|
||||
|
||||
// Enter redraw loop if not updating or palette changed.
|
||||
if (palChanged || paused || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,1,60))) { //(1 - 60) updates/sec 255 is uncapped
|
||||
// Redraw if paused (remove blur), palette changed, overlaying background if not max speed (avoid flicker)
|
||||
// Generation 1 draws alive cells randomly and fades dead cells
|
||||
bool newGame = generation == 1;
|
||||
if (paused || palChanged || overlayBG) {
|
||||
unsigned cIndex = 0;
|
||||
for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) {
|
||||
Cell& cell = cells[cIndex];
|
||||
if (!newGame && cell.superDead) continue; // Skip super dead cells unless new game
|
||||
bool alive = cell.currentStatus;
|
||||
uint32_t cellColor = SEGMENT.getPixelColorXY(x,y);
|
||||
if (alive) {
|
||||
if ((!cell.hasColor && !random(10)) || palChanged) {
|
||||
uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
|
||||
SEGMENT.setPixelColorXY(x,y, randomColor); // Palette changed or needs initial color
|
||||
cells[cIndex].hasColor = 1;
|
||||
}
|
||||
else if (overlayBG && cell.hasColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG
|
||||
} // Dead
|
||||
else if (paused && !overlayBG) {
|
||||
uint32_t blended = color_blend(cellColor, bgColor, bgBlendMode ? 16 : blur);
|
||||
if (blended == cellColor) blended = bgColor; // color_blend fix
|
||||
if ((bgBlendMode && newGame) || !bgBlendMode) SEGMENT.setPixelColorXY(x, y, blended); // Blur dead cells when paused
|
||||
}
|
||||
}
|
||||
}
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); // Backup color
|
||||
if (generation <= 8) blur = 255 - (((generation-1) * (255 - blur)) >> 3); // Ramp up blur for first 8 generations
|
||||
|
||||
//Update Game of Life
|
||||
bool disableWrap = !wrap || generation % 1500 == 0 || *soloGlider; // Disable wrap every 1500 generations to prevent undetected repeats
|
||||
if (*prevWrap != !disableWrap) { grid.recalculateEdgeNeighbors(!disableWrap); *prevWrap = !disableWrap; }
|
||||
// Repeat detection
|
||||
unsigned aliveCount = 0; // Detects empty grids and solo gliders (for smaller grids)
|
||||
bool updateOscillator = generation % 16 == 0;
|
||||
bool updateSpaceship = gliderLength && generation % gliderLength == 0;
|
||||
bool repeatingOscillator = true, repeatingSpaceship = true;
|
||||
|
||||
//Loop through all cells. Apply rules, setPixel
|
||||
unsigned cIndex = 0;
|
||||
for (unsigned y = 0; y < rows; ++y) for (unsigned x = 0; x < cols; ++x, ++cIndex) {
|
||||
Cell& cell = cells[cIndex];
|
||||
if (repeatingOscillator && cell.oscillatorCheck != cell.currentStatus) repeatingOscillator = false;
|
||||
if (repeatingSpaceship && cell.spaceshipCheck != cell.currentStatus) repeatingSpaceship = false;
|
||||
if (updateOscillator) cell.oscillatorCheck = cell.currentStatus;
|
||||
if (updateSpaceship) cell.spaceshipCheck = cell.currentStatus;
|
||||
|
||||
unsigned neighbors = cell.currentNeighborCount;
|
||||
if (cell.superDead && neighbors != 3) continue; // Skip super dead cells (bgColor dead cells)
|
||||
|
||||
bool cellValue = cell.currentStatus;
|
||||
uint32_t cellColor = SEGMENT.getPixelColorXY(x, y);
|
||||
|
||||
if (cellValue) {
|
||||
++aliveCount;
|
||||
if (cellColor != bgColor) color = cellColor; // Update last seen color
|
||||
if (neighbors < 2 || neighbors > 3) {
|
||||
// Loneliness or Overpopulation
|
||||
grid.setCell(cIndex, x, y, false, !disableWrap);
|
||||
if (!overlayBG) SEGMENT.setPixelColorXY(x,y, blur == 255 ? bgColor : color_blend(cellColor, bgColor, blur));
|
||||
if (blur == 255) cell.superDead = 1;
|
||||
}
|
||||
else SEGMENT.setPixelColorXY(x, y, cellColor == bgColor ? color : cellColor); // Redraw alive
|
||||
}
|
||||
else if (neighbors == 3 && !cellValue) {
|
||||
// Reproduction
|
||||
grid.setCell(cIndex, x, y, true, !disableWrap);
|
||||
cell.superDead = 0;
|
||||
uint32_t birthColor = color;
|
||||
if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
|
||||
else {
|
||||
// Get Colors
|
||||
uint32_t nColors[8];
|
||||
unsigned colorCount = 0;
|
||||
unsigned neighbors[9];
|
||||
grid.getNeighborIndexes(neighbors, cIndex, x, y, !disableWrap);
|
||||
|
||||
for (unsigned i = 1; i <= neighbors[0]; ++i) {
|
||||
unsigned nIndex = neighbors[i];
|
||||
if (cells[nIndex].futureStatus) {
|
||||
uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols);
|
||||
if (nColor == bgColor) continue;
|
||||
nColors[colorCount++] = nColor;
|
||||
}
|
||||
}
|
||||
if (colorCount) { birthColor = nColors[random8(colorCount)]; color = birthColor; }
|
||||
}
|
||||
SEGMENT.setPixelColorXY(x,y, birthColor);
|
||||
}
|
||||
else { // Already dead
|
||||
if (blur != 255 && !overlayBG && !bgBlendMode) {
|
||||
uint32_t blended = color_blend(cellColor, bgColor, blur); // color_blend doesn't always converge to bgColor (this fix needed for fast fps with custom bgColor)
|
||||
if (blended == cellColor) { blended = bgColor; cell.superDead = 1; }
|
||||
SEGMENT.setPixelColorXY(x, y, blended);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grid.shiftFutureToCurrent();
|
||||
|
||||
if (aliveCount == 5) *soloGlider = true; else *soloGlider = false;
|
||||
if (repeatingOscillator || repeatingSpaceship || !aliveCount) {
|
||||
generation = 0; // reset on next call
|
||||
SEGENV.step += 1000; // pause final generation for 1 second
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
++generation;
|
||||
SEGENV.step = strip.now;
|
||||
return FRAMETIME;
|
||||
} // mode_2Dgameoflife()
|
||||
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";
|
||||
|
||||
/////////////////////////
|
||||
// 2D SnowFall //
|
||||
/////////////////////////
|
||||
static bool getBitValue(const uint8_t* byteArray, size_t n) {
|
||||
size_t byteIndex = n / 8;
|
||||
size_t bitIndex = n % 8;
|
||||
@@ -5244,199 +5512,8 @@ static void setBitValue(uint8_t* byteArray, size_t n, bool value) {
|
||||
else
|
||||
byteArray[byteIndex] &= ~(1 << bitIndex);
|
||||
}
|
||||
|
||||
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) return mode_static(); // not a 2D set-up
|
||||
|
||||
const uint16_t cols = SEGMENT.virtualWidth();
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte
|
||||
const size_t detectionSize = sizeof(uint16_t) * 3 + 1; // 2 CRCs, gliderLength, soloGlider boolean
|
||||
const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); // detectionSize + prevPalette
|
||||
|
||||
if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed
|
||||
byte *cells = reinterpret_cast<byte*>(SEGENV.data);
|
||||
byte *futureCells = reinterpret_cast<byte*>(SEGENV.data + dataSize);
|
||||
uint16_t *gliderLength = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2);
|
||||
uint16_t *oscillatorCRC = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2 + sizeof(uint16_t));
|
||||
uint16_t *spaceshipCRC = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 2);
|
||||
bool *soloGlider = reinterpret_cast<bool*>(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 3);
|
||||
uint8_t *prevPalette = reinterpret_cast<uint8_t*>(SEGENV.data + dataSize * 2 + detectionSize);
|
||||
|
||||
uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability
|
||||
bool allColors = SEGMENT.check1;
|
||||
bool overlayBG = SEGMENT.check2;
|
||||
bool wrap = SEGMENT.check3;
|
||||
bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode
|
||||
byte blur = bgBlendMode ? map2(SEGMENT.custom1 - 220, 0, 35, 255, 128) : map2(SEGMENT.custom1, 0, 255, 255, 0);
|
||||
uint32_t bgColor = SEGCOLOR(1);
|
||||
uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0);
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially
|
||||
SEGENV.step = 0;
|
||||
}
|
||||
// fix SEGENV.step in case that timebase jumps
|
||||
if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0;
|
||||
|
||||
// Setup New Game of Life
|
||||
if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) {
|
||||
SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds
|
||||
generation = 1;
|
||||
*prevPalette = SEGMENT.palette;
|
||||
random16_set_seed(strip.now>>2); //seed the random generator
|
||||
//Setup Grid
|
||||
memset(cells, 0, dataSize);
|
||||
for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) {
|
||||
if (random8(100) < 32) { // ~32% chance of being alive
|
||||
setBitValue(cells, y * cols + x, true);
|
||||
if (overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0));
|
||||
else SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop
|
||||
}
|
||||
}
|
||||
memcpy(futureCells, cells, dataSize);
|
||||
|
||||
//Set CRCs
|
||||
uint16_t crc = crc16((const unsigned char*)cells, dataSize);
|
||||
*oscillatorCRC = crc;
|
||||
*spaceshipCRC = crc;
|
||||
|
||||
//Calculate glider length LCM(rows,cols)*4
|
||||
uint8_t a = rows;
|
||||
uint8_t b = cols;
|
||||
while (b) {
|
||||
uint8_t t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
*gliderLength = cols * rows / a * 4;
|
||||
}
|
||||
|
||||
bool blurDead = SEGENV.step > strip.now && blur !=255 && !bgBlendMode && !overlayBG;
|
||||
bool palChanged = SEGMENT.palette != *prevPalette && !allColors;
|
||||
bool newGame = generation == 1;
|
||||
if (palChanged) *prevPalette = SEGMENT.palette;
|
||||
|
||||
// Redraw Loop
|
||||
// Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker)
|
||||
// Generation 1 draws alive cells randomly and fades dead cells
|
||||
if (blurDead || newGame || palChanged || overlayBG) {
|
||||
for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) {
|
||||
unsigned cIndex = y * cols + x;
|
||||
uint32_t cellColor = SEGMENT.getPixelColorXY(x,y);
|
||||
bool alive = getBitValue(cells, cIndex);
|
||||
bool aliveBgColor = (!overlayBG && alive && newGame && cellColor == bgColor );
|
||||
|
||||
if ( alive && (palChanged || (aliveBgColor && !random(12)))) { // Palette change or spawn initial colors randomly
|
||||
uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
|
||||
SEGMENT.setPixelColorXY(x,y, randomColor); // Recolor alive cells
|
||||
}
|
||||
else if ( alive && overlayBG && !aliveBgColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG
|
||||
if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette
|
||||
else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));// Blur dead cells (paused)
|
||||
else if (!alive && !overlayBG && generation == 1) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells on generation 1
|
||||
}
|
||||
}
|
||||
|
||||
if (!SEGMENT.speed || SEGENV.step > strip.now || (SEGMENT.speed != 255 && strip.now - SEGENV.step < 1000 / map2(SEGMENT.speed,0,254,0,60))) return FRAMETIME; //(0 - 60) updates/sec 255 is uncapped
|
||||
|
||||
//Update Game of Life
|
||||
unsigned aliveCount = 0; // Detects dead grids and solo gliders
|
||||
bool disableWrap = !wrap || (generation % 1500 == 0 || *soloGlider); // Disable wrap every 1500 generations to prevent undetected repeats
|
||||
//Loop through all cells. Count neighbors, apply rules, setPixel
|
||||
for (unsigned x = 0; x < cols; x++) for (unsigned y = 0; y < rows; y++) {
|
||||
unsigned cIndex = y * cols + x;
|
||||
bool cellValue = getBitValue(cells, cIndex);
|
||||
uint32_t cellColor = SEGMENT.getPixelColorXY(x, y);
|
||||
if (cellValue) aliveCount++;
|
||||
|
||||
unsigned neighbors = 0, colorCount = 0;
|
||||
unsigned neighborIndexes[3];
|
||||
|
||||
// Count neighbors and store indexes, get neighbor colors later if needed
|
||||
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // Iterate through all neighbors
|
||||
if (i == 0 && j == 0) continue; // Ignore self
|
||||
if (i == 1 && j == 0 && !cellValue && !neighbors) break; // Cell can't be born with no neighbors and 2 remaining checks
|
||||
int nX = x + i;
|
||||
int nY = y + j;
|
||||
if (nX < 0) {if (disableWrap) continue; nX = cols - 1;}
|
||||
else if (nX >= cols) {if (disableWrap) continue; nX = 0;}
|
||||
if (nY < 0) {if (disableWrap) continue; nY = rows - 1;}
|
||||
else if (nY >= rows) {if (disableWrap) continue; nY = 0;}
|
||||
|
||||
unsigned nIndex = nY * cols + nX; // Neighbor cell index
|
||||
if (getBitValue(cells, nIndex)) {
|
||||
++neighbors;
|
||||
if (neighbors > 3) break; // Cell dies, stop neighbor loop
|
||||
neighborIndexes[neighbors - 1] = nIndex; // Store alive neighbor index
|
||||
}
|
||||
}
|
||||
|
||||
if (!cellValue && neighbors != 3 && cellColor == bgColor) continue; // Skip dead cells with no neighbors and no color
|
||||
|
||||
// Rules of Life
|
||||
if (cellValue && (neighbors < 2 || neighbors > 3)) {
|
||||
// Loneliness or Overpopulation
|
||||
setBitValue(futureCells, cIndex, false);
|
||||
if (!overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));
|
||||
}
|
||||
else if (neighbors == 3 && !cellValue) {
|
||||
// Reproduction
|
||||
// Get Colors
|
||||
uint32_t nColors[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
unsigned nIndex = neighborIndexes[i];
|
||||
if (!getBitValue(futureCells, nIndex)) continue; // Parent just died, color lost or blended
|
||||
uint32_t nColor = SEGMENT.getPixelColorXY(nIndex % cols, nIndex / cols);
|
||||
if (nColor == bgColor) continue;
|
||||
color = nColor; // Update last seen color
|
||||
nColors[colorCount++] = nColor;
|
||||
|
||||
}
|
||||
setBitValue(futureCells, cIndex, true);
|
||||
uint32_t birthColor = colorCount ? nColors[random8(colorCount)] : color; // Uses last seen color if no surviving neighbors
|
||||
// Mutate color chance
|
||||
if (random8() < SEGMENT.intensity) birthColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0);
|
||||
SEGMENT.setPixelColorXY(x,y, birthColor);
|
||||
}
|
||||
else { // Blur dead cells and redraw alive cells
|
||||
if (cellValue) SEGMENT.setPixelColorXY(x, y, cellColor == bgColor ? color : cellColor); // Redraw alive, fixes fading cells
|
||||
else if (blur != 255 && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x, y, color_blend(cellColor, bgColor, blur));
|
||||
}
|
||||
|
||||
}
|
||||
//update cell values
|
||||
memcpy(cells, futureCells, dataSize);
|
||||
|
||||
// Get current crc value
|
||||
uint16_t crc = crc16((const unsigned char*)cells, dataSize);
|
||||
|
||||
bool repetition = false;
|
||||
if (!aliveCount || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values
|
||||
if (repetition) {
|
||||
generation = 0; // reset on next call
|
||||
SEGENV.step += 1000; // pause final generation for 1 second
|
||||
return FRAMETIME;
|
||||
}
|
||||
// Update CRC values
|
||||
if (generation % 16 == 0) *oscillatorCRC = crc;
|
||||
if (*gliderLength && generation % *gliderLength == 0) *spaceshipCRC = crc;
|
||||
if (aliveCount == 5) *soloGlider = true; else *soloGlider = false;
|
||||
|
||||
generation++;
|
||||
SEGENV.step = strip.now;
|
||||
return FRAMETIME;
|
||||
} // mode_2Dgameoflife()
|
||||
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";
|
||||
|
||||
/////////////////////////
|
||||
// 2D SnowFall //
|
||||
/////////////////////////
|
||||
|
||||
uint16_t mode_2DSnowFall(void) { // By: Brandon Butler
|
||||
// Uses Game of Life style bit array to track snow/particles
|
||||
// Uses bit array to track snow/particles
|
||||
if (!strip.isMatrix) return mode_static(); // Not a 2D set-up
|
||||
const uint16_t cols = SEGMENT.virtualWidth();
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
Reference in New Issue
Block a user