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();
}
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) {
if (x >= width || y >= 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"
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);
ModeId newModeId = GAME_OF_LIFE;
ModeId newModeId = GAME_OF_LIFE_COLOR_FADE;
ModeId currentModeId = NONE;
@ -31,7 +37,7 @@ void setBrightness(int value);
void setup() {
delay(500);
Serial.begin(115200);
Serial.println("\n\n\nStartup!\n");
Serial.println("\n\n\nStartup!");
display.setup();
}
@ -47,6 +53,12 @@ void loop() {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
newModeId = (ModeId) (input - '0');
break;
case '+':
@ -96,21 +108,29 @@ void unloadOldMode() {
void loadNewMode() {
currentModeId = newModeId;
lastMillis = 0;
Serial.printf("Loading mode: #%d\n", currentModeId);
switch (currentModeId) {
case NONE:
break;
case BORDER:
mode = new Border(&display);
break;
case GAME_OF_LIFE:
mode = new GameOfLife(&display);
case GAME_OF_LIFE_BLACK_WHITE:
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;
case PONG:
mode = new Pong(&display);
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() {

View File

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

View File

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

View File

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

View File

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

View File

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