#ifndef GAMEOFLIFE_H #define GAMEOFLIFE_H #include "mode/Mode.h" #include "display/Display.h" #include "Cell.h" enum ColorMode { BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR }; class GameOfLife : public Mode { private: ColorMode colorMode; microseconds_t runtime = 0; uint8_t steps = 0; uint16_t aliveCount = 0; uint16_t lastAliveCount = 0; uint8_t isSteadyCount = 0; Cell *cells; Cell *next; public: explicit GameOfLife(Display *display, ColorMode colorMode) : Mode(display), colorMode(colorMode) { cells = (Cell *) malloc(display->pixelCount * sizeof(Cell)); for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) { kill(cell); } next = (Cell *) malloc(display->pixelCount * sizeof(Cell)); 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 "???"; } void doStep(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(); } } animate(dt); } private: void randomFill() { isSteadyCount = 0; for (Cell *cell = cells; cell < cells + display->pixelCount; 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, display->pixelCount * sizeof(Cell)); Cell *src = cells; Cell *dst = next; for (int y = 0; y < display->height; y++) { for (int x = 0; x < display->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, display->pixelCount * sizeof(Cell)); } void print() { Cell *cell = cells; for (int y = 0; y < display->height; y++) { Serial.print("|"); for (int x = 0; x < display->width; x++) { Serial.print(cell->alive ? "x|" : " |"); cell++; } Serial.println(); Serial.println(); } } void animate(microseconds_t dt) { Cell *cell = cells; for (int y = 0; y < display->height; y++) { for (int x = 0; x < display->width; x++) { cell->animate(dt); 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++; } } } 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 >= display->width || y >= display->height) { return 0; } return get(x, y)->alive ? 1 : 0; } Cell *get(int x, int y) { return cells + display->width * y + x; } }; #endif