GameOfLife working (black white, grayscale, color fade, random color)
This commit is contained in:
parent
9fe334c6a4
commit
14fefed0d3
@ -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);
|
||||||
|
|||||||
34
src/main.cpp
34
src/main.cpp
@ -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() {
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
|
||||||
|
|
||||||
void animate(uint8_t dt) {
|
|
||||||
switch (state) {
|
|
||||||
case GROWING:
|
|
||||||
if (dt < 255 - value) {
|
|
||||||
value += dt;
|
|
||||||
} else {
|
} else {
|
||||||
value = 255;
|
fade = max(0.0, fade - per255Points);
|
||||||
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) {
|
||||||
|
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;
|
return 255;
|
||||||
}
|
}
|
||||||
if (state == DYING) {
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getG() const {
|
uint8_t getG() const {
|
||||||
if (state == ALIVE) {
|
if (alive) {
|
||||||
|
if (fade < 128) {
|
||||||
|
return (uint8_t) (fade * 2.0 + 1);
|
||||||
|
} else {
|
||||||
return 255;
|
return 255;
|
||||||
}
|
}
|
||||||
if (state == GROWING) {
|
} else {
|
||||||
return value;
|
if (fade < 128) {
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (uint8_t) ((fade - 128) * 2.0 + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getB() const {
|
uint8_t getB() const {
|
||||||
if (state == ALIVE) {
|
if (fade < 128) {
|
||||||
return 255;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
|
} else {
|
||||||
|
return (uint8_t) ((fade - 128) * 2.0 + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,20 +91,30 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void spawn(Cell *cell) {
|
||||||
|
cell->color = random(16777216);
|
||||||
|
cell->alive = true;
|
||||||
|
aliveCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kill(Cell *cell) {
|
||||||
|
cell->alive = false;
|
||||||
aliveCount--;
|
aliveCount--;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void nextGeneration() {
|
void nextGeneration() {
|
||||||
memcpy(next, cells, display->pixelCount * sizeof(Cell));
|
memcpy(next, cells, display->pixelCount * sizeof(Cell));
|
||||||
@ -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;
|
||||||
|
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());
|
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) {
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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++) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user