RGBMatrixDisplay/src/mode/GameOfLife/GameOfLife.h
2023-01-06 15:16:12 +01:00

211 lines
4.9 KiB
C++

#ifndef MODE_GAME_OF_LIFE_H
#define MODE_GAME_OF_LIFE_H
#include "mode/Mode.h"
#include "Cell.h"
enum ColorMode {
BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR
};
class GameOfLife : public Mode {
private:
ColorMode colorMode;
size_t cellsSize;
Cell *cells;
Cell *cellsEnd;
Cell *next;
microseconds_t runtime = 0;
uint8_t steps = 0;
uint16_t aliveCount = 0;
uint16_t lastAliveCount = 0;
uint8_t isSteadyCount = 0;
public:
explicit GameOfLife(Display &display, ColorMode colorMode) :
Mode(display),
colorMode(colorMode),
cellsSize(display.pixelCount * sizeof(Cell)) {
cells = (Cell *) malloc(cellsSize);
cellsEnd = cells + cellsSize;
for (Cell *cell = cells; cell < cells + display.pixelCount; cell++) {
kill(cell);
}
next = (Cell *) malloc(cellsSize);
for (Cell *cell = next; cell < next + display.pixelCount; cell++) {
kill(cell);
}
}
~GameOfLife() override {
if (cells != nullptr) {
free(cells);
cells = nullptr;
}
if (next != nullptr) {
free(next);
next = nullptr;
}
}
const char *getName() override {
switch (colorMode) {
case BLACK_WHITE:
return "Game of Life (black white)";
case GRAYSCALE:
return "Game of Life (grayscale)";
case COLOR_FADE:
return "Game of Life (color fade)";
case RANDOM_COLOR:
return "Game of Life (random color)";
}
return "???";
}
protected:
void step(microseconds_t dt) override {
runtime += dt;
if (runtime >= 500000) {
runtime = 0;
if (lastAliveCount == aliveCount) {
isSteadyCount++;
} else {
isSteadyCount = 0;
}
lastAliveCount = aliveCount;
if (steps++ == 0 || aliveCount == 0 || isSteadyCount >= 15) {
randomFill();
} else {
nextGeneration();
}
}
for (Cell *cell = cells; cell < cellsEnd; cell++) {
cell->animate(dt);
}
// TODO don't always markDirty. Be more efficient
markDirty();
}
void draw(Display &display) override {
Cell *cell = cells;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t brightness;
switch (colorMode) {
case BLACK_WHITE:
brightness = cell->alive ? 255 : 0;
display.set(x, y, gray(brightness));
break;
case GRAYSCALE:
brightness = (uint8_t) cell->fade;
display.set(x, y, gray(brightness));
break;
case COLOR_FADE:
display.set(x, y, {cell->getR(), cell->getG(), cell->getB()});
break;
case RANDOM_COLOR:
display.set(x, y, cell->alive ? cell->color : BLACK);
break;
}
cell++;
}
}
}
private:
void randomFill() {
isSteadyCount = 0;
for (Cell *cell = cells; cell < cellsEnd; cell++) {
if (random(4) == 0) {
if (!cell->alive) {
spawn(cell);
}
} else {
if (cell->alive) {
kill(cell);
}
}
}
}
void spawn(Cell *cell) {
cell->color = randomColor();
cell->alive = true;
aliveCount++;
}
void kill(Cell *cell) {
cell->alive = false;
aliveCount--;
}
void nextGeneration() {
memcpy(next, cells, cellsSize);
Cell *src = cells;
Cell *dst = next;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t around = countAround(x, y);
if (src->alive) {
if (around <= 2 || 6 <= around) {
kill(dst);
}
} else if (around == 3) {
spawn(dst);
}
src++;
dst++;
}
}
memcpy(cells, next, cellsSize);
}
void print() {
Cell *cell = cells;
for (int y = 0; y < height; y++) {
Serial.print("|");
for (int x = 0; x < width; x++) {
Serial.print(cell->alive ? "x|" : " |");
cell++;
}
Serial.println();
Serial.println();
}
}
uint8_t countAround(int x, int y) {
return countIfAlive(x - 1, y - 1) + countIfAlive(x + 0, y - 1) + countIfAlive(x + 1, y - 1) +
countIfAlive(x - 1, y + 0) + /* */ countIfAlive(x + 1, y + 0) +
countIfAlive(x - 1, y + 1) + countIfAlive(x + 0, y + 1) + countIfAlive(x + 1, y + 1);
}
uint8_t countIfAlive(int x, int y) {
if (x < 0 || y < 0 || x >= width || y >= height) {
return 0;
}
return get(x, y)->alive ? 1 : 0;
}
Cell *get(int x, int y) {
return cells + width * y + x;
}
};
#endif