GameOfLife working (black white, grayscale, color fade, random color)

This commit is contained in:
Patrick Haßel 2021-12-26 13:12:03 +01:00
parent 9fe334c6a4
commit 14fefed0d3
7 changed files with 156 additions and 94 deletions

View File

@ -33,6 +33,14 @@ public:
clear(); clear();
} }
void set(uint8_t x, uint8_t y, uint32_t color) {
if (x >= width || y >= height) {
Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height);
return;
}
leds.setPixelColor(y * width + x, color);
}
void set(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { void set(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
if (x >= width || y >= height) { if (x >= width || y >= height) {
Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height); Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height);

View File

@ -5,12 +5,18 @@
#include "mode/Test/Border.h" #include "mode/Test/Border.h"
enum ModeId { enum ModeId {
NONE, BORDER, GAME_OF_LIFE, PONG NONE,
BORDER,
GAME_OF_LIFE_BLACK_WHITE,
GAME_OF_LIFE_WHITE_FADE,
GAME_OF_LIFE_COLOR_FADE,
GAME_OF_LIFE_RANDOM_COLOR,
PONG
}; };
Display display(32, 8); Display display(32, 8);
ModeId newModeId = GAME_OF_LIFE; ModeId newModeId = GAME_OF_LIFE_COLOR_FADE;
ModeId currentModeId = NONE; ModeId currentModeId = NONE;
@ -31,7 +37,7 @@ void setBrightness(int value);
void setup() { void setup() {
delay(500); delay(500);
Serial.begin(115200); Serial.begin(115200);
Serial.println("\n\n\nStartup!\n"); Serial.println("\n\n\nStartup!");
display.setup(); display.setup();
} }
@ -47,6 +53,12 @@ void loop() {
case '1': case '1':
case '2': case '2':
case '3': case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
newModeId = (ModeId) (input - '0'); newModeId = (ModeId) (input - '0');
break; break;
case '+': case '+':
@ -96,21 +108,29 @@ void unloadOldMode() {
void loadNewMode() { void loadNewMode() {
currentModeId = newModeId; currentModeId = newModeId;
lastMillis = 0; lastMillis = 0;
Serial.printf("Loading mode: #%d\n", currentModeId);
switch (currentModeId) { switch (currentModeId) {
case NONE: case NONE:
break; break;
case BORDER: case BORDER:
mode = new Border(&display); mode = new Border(&display);
break; break;
case GAME_OF_LIFE: case GAME_OF_LIFE_BLACK_WHITE:
mode = new GameOfLife(&display); mode = new GameOfLife(&display, BLACK_WHITE);
break;
case GAME_OF_LIFE_WHITE_FADE:
mode = new GameOfLife(&display, GRAYSCALE);
break;
case GAME_OF_LIFE_COLOR_FADE:
mode = new GameOfLife(&display, COLOR_FADE);
break;
case GAME_OF_LIFE_RANDOM_COLOR:
mode = new GameOfLife(&display, RANDOM_COLOR);
break; break;
case PONG: case PONG:
mode = new Pong(&display); mode = new Pong(&display);
break; break;
} }
Serial.printf("Loaded mode: %s\n\n", mode == nullptr ? "None" : mode->getName()); Serial.printf("Mode: %s\n", mode == nullptr ? "None" : mode->getName());
} }
void stepMode() { void stepMode() {

View File

@ -3,78 +3,62 @@
#include "BASICS.h" #include "BASICS.h"
#define DEAD 0
#define GROWING 1
#define ALIVE 2
#define DYING 3
class Cell { class Cell {
public: public:
uint8_t state = DEAD; bool alive = false;
uint8_t value = 0;
bool isAlive() const { double fade = 0.0;
return state == GROWING || state == ALIVE;
}
void spawn() { uint32_t color = 0;
state = GROWING;
value = 0;
}
void kill() { void animate(double per255Points) {
state = DYING; if (alive) {
value = 255; fade = min(255.0, fade + per255Points);
} } else {
fade = max(0.0, fade - per255Points);
void animate(uint8_t dt) {
switch (state) {
case GROWING:
if (dt < 255 - value) {
value += dt;
} else {
value = 255;
state = ALIVE;
}
break;
case DYING:
if (dt < value) {
value -= dt;
} else {
value = 0;
state = DEAD;
}
break;
} }
} }
uint8_t getR() const { uint8_t getR() const {
if (state == ALIVE) { if (alive) {
return 255; if (fade < 128) {
return 0;
} else {
return (uint8_t) ((fade - 128) * 2.0 + 1);
}
} else {
if (fade < 128) {
return (uint8_t) (fade * 2.0 + 1);
} else {
return 255;
}
} }
if (state == DYING) {
return value;
}
return 0;
} }
uint8_t getG() const { uint8_t getG() const {
if (state == ALIVE) { if (alive) {
return 255; if (fade < 128) {
return (uint8_t) (fade * 2.0 + 1);
} else {
return 255;
}
} else {
if (fade < 128) {
return 0;
} else {
return (uint8_t) ((fade - 128) * 2.0 + 1);
}
} }
if (state == GROWING) {
return value;
}
return 0;
} }
uint8_t getB() const { uint8_t getB() const {
if (state == ALIVE) { if (fade < 128) {
return 255; return 0;
} else {
return (uint8_t) ((fade - 128) * 2.0 + 1);
} }
return 0;
} }
}; };

View File

@ -5,10 +5,16 @@
#include "display/Display.h" #include "display/Display.h"
#include "Cell.h" #include "Cell.h"
enum ColorMode {
BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR
};
class GameOfLife : public Mode { class GameOfLife : public Mode {
private: private:
ColorMode colorMode;
millis_t runtime = 0; millis_t runtime = 0;
uint8_t steps = 0; uint8_t steps = 0;
@ -17,25 +23,24 @@ private:
uint16_t lastAliveCount = 0; uint16_t lastAliveCount = 0;
uint8_t isSteadyCount = 0;
Cell *cells; Cell *cells;
Cell *last;
Cell *next; Cell *next;
public: public:
explicit GameOfLife(Display *display) : explicit GameOfLife(Display *display, ColorMode colorMode) :
Mode(display, "Game of Life") { Mode(display),
colorMode(colorMode) {
cells = (Cell *) malloc(display->pixelCount * sizeof(Cell)); cells = (Cell *) malloc(display->pixelCount * sizeof(Cell));
last = cells;
for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) { for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) {
cell->state = DEAD; kill(cell);
cell->value = 0;
} }
next = (Cell *) malloc(display->pixelCount * sizeof(Cell)); next = (Cell *) malloc(display->pixelCount * sizeof(Cell));
for (Cell *cell = next; cell < next + display->pixelCount; cell++) { for (Cell *cell = next; cell < next + display->pixelCount; cell++) {
cell->state = DEAD; kill(cell);
cell->value = 0;
} }
} }
@ -50,13 +55,31 @@ public:
} }
} }
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 step(millis_t dt) override { void step(millis_t dt) override {
runtime += dt; runtime += dt;
if (runtime >= 500) { if (runtime >= 500) {
runtime = 0; runtime = 0;
bool isSteady = lastAliveCount == aliveCount; if (lastAliveCount == aliveCount) {
isSteadyCount++;
} else {
isSteadyCount = 0;
}
lastAliveCount = aliveCount; lastAliveCount = aliveCount;
if (steps++ == 0 || aliveCount == 0 || isSteady) { if (steps++ == 0 || aliveCount == 0 || isSteadyCount >= 15) {
randomFill(); randomFill();
} else { } else {
nextGeneration(); nextGeneration();
@ -68,21 +91,31 @@ public:
private: private:
void randomFill() { void randomFill() {
isSteadyCount = 0;
for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) { for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) {
if (random(4) == 0) { if (random(4) == 0) {
if (!cell->isAlive()) { if (!cell->alive) {
cell->spawn(); spawn(cell);
aliveCount++;
} }
} else { } else {
if (cell->isAlive()) { if (cell->alive) {
cell->kill(); kill(cell);
aliveCount--;
} }
} }
} }
} }
void spawn(Cell *cell) {
cell->color = random(16777216);
cell->alive = true;
aliveCount++;
}
void kill(Cell *cell) {
cell->alive = false;
aliveCount--;
}
void nextGeneration() { void nextGeneration() {
memcpy(next, cells, display->pixelCount * sizeof(Cell)); memcpy(next, cells, display->pixelCount * sizeof(Cell));
Cell *src = cells; Cell *src = cells;
@ -90,14 +123,12 @@ private:
for (int y = 0; y < display->height; y++) { for (int y = 0; y < display->height; y++) {
for (int x = 0; x < display->width; x++) { for (int x = 0; x < display->width; x++) {
uint8_t around = countAround(x, y); uint8_t around = countAround(x, y);
if (src->isAlive()) { if (src->alive) {
if (around <= 2 || 4 <= around) { if (around <= 2 || 6 <= around) {
dst->kill(); kill(dst);
aliveCount--;
} }
} else if (around == 3) { } else if (around == 3) {
dst->spawn(); spawn(dst);
aliveCount++;
} }
src++; src++;
dst++; dst++;
@ -111,7 +142,7 @@ private:
for (int y = 0; y < display->height; y++) { for (int y = 0; y < display->height; y++) {
Serial.print("|"); Serial.print("|");
for (int x = 0; x < display->width; x++) { for (int x = 0; x < display->width; x++) {
Serial.print(cell->isAlive() ? "x|" : " |"); Serial.print(cell->alive ? "x|" : " |");
cell++; cell++;
} }
Serial.println(); Serial.println();
@ -125,7 +156,23 @@ private:
for (int x = 0; x < display->width; x++) { for (int x = 0; x < display->width; x++) {
cell->animate(dt); cell->animate(dt);
int xx = (y % 2) == 0 ? display->width - x - 1 : x; int xx = (y % 2) == 0 ? display->width - x - 1 : x;
display->set(xx, y, cell->getR(), cell->getG(), cell->getB()); uint8_t brightness;
switch (colorMode) {
case BLACK_WHITE:
brightness = cell->alive ? 255 : 0;
display->set(xx, y, brightness, brightness, brightness);
break;
case GRAYSCALE:
brightness = (uint8_t) cell->fade;
display->set(xx, y, brightness, brightness, brightness);
break;
case COLOR_FADE:
display->set(xx, y, cell->getR(), cell->getG(), cell->getB());
break;
case RANDOM_COLOR:
display->set(xx, y, cell->alive ? cell->color : 0);
break;
}
cell++; cell++;
} }
} }
@ -141,7 +188,7 @@ private:
if (x < 0 || y < 0 || x >= display->width || y >= display->height) { if (x < 0 || y < 0 || x >= display->width || y >= display->height) {
return 0; return 0;
} }
return get(x, y)->isAlive() ? 1 : 0; return get(x, y)->alive ? 1 : 0;
} }
Cell *get(int x, int y) { Cell *get(int x, int y) {

View File

@ -10,21 +10,16 @@ protected:
Display *display; Display *display;
const char *name;
public: public:
explicit Mode(Display *display, const char *name) : explicit Mode(Display *display) :
display(display), display(display) {
name(name) {
// nothing // nothing
} }
virtual ~Mode() = default; virtual ~Mode() = default;
const char *getName() { virtual const char *getName() = 0;
return name;
}
virtual void step(millis_t dt) = 0; virtual void step(millis_t dt) = 0;
}; };

View File

@ -20,7 +20,7 @@ private:
public: public:
explicit Pong(Display *display) : explicit Pong(Display *display) :
Mode(display, "Pong"), Mode(display),
position(display->width / 2.0, display->height / 2.0), position(display->width / 2.0, display->height / 2.0),
velocity(random(360), exp10(1)) { velocity(random(360), exp10(1)) {
// nothing // nothing
@ -32,6 +32,10 @@ public:
~Pong() override = default; ~Pong() override = default;
const char *getName() override {
return "Pong";
}
void step(millis_t dt) override { void step(millis_t dt) override {
} }

View File

@ -8,12 +8,16 @@ class Border : public Mode {
public: public:
explicit Border(Display *display) : explicit Border(Display *display) :
Mode(display, "Border") { Mode(display) {
// nothing // nothing
} }
~Border() override = default; ~Border() override = default;
const char *getName() override {
return "Border";
}
void step(millis_t dt) override { void step(millis_t dt) override {
for (int y = 0; y < display->height; y++) { for (int y = 0; y < display->height; y++) {
for (int x = 0; x < display->width; x++) { for (int x = 0; x < display->width; x++) {