diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..e8a214f --- /dev/null +++ b/TODO.txt @@ -0,0 +1,16 @@ +Modes: + Tetris + Matrix + Ludo + Pacman + Snake + Strobe + +Improvements: + NewYear => change due date (birthday etc) + +Hardware: + Gamepads + Heatsink + Extend Matrix (double?) + Microphone diff --git a/platformio.ini b/platformio.ini index 36c09d6..c63c4b4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,3 +20,4 @@ upload_protocol = espota ;upload_speed = 921600 monitor_port = /dev/ttyUSB0 monitor_speed = 115200 +monitor_filters = esp32_exception_decoder diff --git a/src/BASICS.h b/src/BASICS.h index 79b74f3..52a7a10 100644 --- a/src/BASICS.h +++ b/src/BASICS.h @@ -9,8 +9,12 @@ #define ____ 0 #define FULL 255 +#define countof(x) (sizeof(x) / sizeof(x[0])) + typedef int64_t microseconds_t; +typedef unsigned long millis_t; + double doStep(double valueCurrent, double valueMin, double valueMax, microseconds_t millisecondsTotal, microseconds_t microsecondsDelta); bool randomBool(int uncertainty); diff --git a/src/config.cpp b/src/config.cpp index fb388a4..b08e2de 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -16,7 +16,7 @@ bool dirty = false; bool notify = false; -unsigned long lastDirtyMillis = 0; +millis_t lastDirtyMillis = 0; void config_setup() { if (!config_load()) { diff --git a/src/display/Display.h b/src/display/Display.h index ee223b0..17b0b7e 100644 --- a/src/display/Display.h +++ b/src/display/Display.h @@ -29,7 +29,7 @@ private: Adafruit_NeoPixel leds; - unsigned long fpsLastMillis = 0; + millis_t fpsLastMillis = 0; int fps = 0; diff --git a/src/mode.cpp b/src/mode.cpp index 5fb99fe..cb60bab 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -8,12 +8,13 @@ #include "mode/NewYear/NewYear.h" #include "mode/Starfield/Starfield.h" #include "display.h" +#include "mode/Matrix/Matrix.h" ModeId currentModeId = NONE; microseconds_t lastMicros = 0; -ModeBase *mode = nullptr; +Mode *mode = nullptr; void unloadOldMode(); @@ -22,6 +23,8 @@ void loadNewMode(); void mode_step(); void mode_loop() { + Serial.print("\n"); + if (currentModeId != config.mode) { unloadOldMode(); loadNewMode(); @@ -49,6 +52,7 @@ void setSpeed(double speed) { void unloadOldMode() { if (mode != nullptr) { + Serial.print("[MODE] unload\n"); delete mode; mode = nullptr; } @@ -58,36 +62,40 @@ void unloadOldMode() { void loadNewMode() { currentModeId = config.mode; lastMicros = 0; + Serial.printf("[MODE] loading %d\n", currentModeId); switch (currentModeId) { case BORDER: - mode = new Border(&display); + mode = new Border(display); break; case CLOCK: - mode = new Clock(&display); + mode = new Clock(display); break; case GAME_OF_LIFE_BLACK_WHITE: - mode = new GameOfLife(&display, BLACK_WHITE); + mode = new GameOfLife(display, BLACK_WHITE); break; case GAME_OF_LIFE_GRAYSCALE: - mode = new GameOfLife(&display, GRAYSCALE); + mode = new GameOfLife(display, GRAYSCALE); break; case GAME_OF_LIFE_COLOR_FADE: - mode = new GameOfLife(&display, COLOR_FADE); + mode = new GameOfLife(display, COLOR_FADE); break; case GAME_OF_LIFE_RANDOM_COLOR: - mode = new GameOfLife(&display, RANDOM_COLOR); + mode = new GameOfLife(display, RANDOM_COLOR); break; case PONG: - mode = new Pong(&display); + mode = new Pong(display); break; case SPACE_INVADERS: - mode = new SpaceInvaders(&display); + mode = new SpaceInvaders(display); break; case NEW_YEAR: - mode = new NewYear(&display); + mode = new NewYear(display); break; case STARFIELD: - mode = new Starfield(&display); + mode = new Starfield(display); + break; + case MATRIX: + mode = new Matrix(display); break; default: Serial.print("No mode loaded.\n"); @@ -104,5 +112,5 @@ void mode_step() { auto currentMicros = (int64_t) micros(); microseconds_t dt = (microseconds_t) min(1000000.0, max(1.0, (double) (currentMicros - lastMicros) * config.speed)); lastMicros = currentMicros; - mode->step(dt); + mode->loop(dt); } diff --git a/src/mode/Border/Border.h b/src/mode/Border/Border.h index 46d8403..bd606ea 100644 --- a/src/mode/Border/Border.h +++ b/src/mode/Border/Border.h @@ -1,30 +1,32 @@ -#ifndef TEST_H -#define TEST_H +#ifndef MODE_BORDER_H +#define MODE_BORDER_H #include "mode/Mode.h" -class Border : public Mode { +class Border : public Mode { public: - explicit Border(Display *display) : - Mode(display) { + explicit Border(Display &display) : + Mode(display) { // nothing } - ~Border() override = default; - const char *getName() override { return "Border"; } - void doStep(microseconds_t dt) override { - for (int y = 0; y < display->height; y++) { - for (int x = 0; x < display->width; x++) { - if (x == 0 || x == display->width - 1 || y == 0 || y == display->height - 1) { - display->set(x, y, WHITE); +protected: + + void draw(Display &display) override { + Serial.print("[BORDER] draw\n"); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Serial.printf("[BORDER] draw %d / %d\n", x, y); + if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { + display.set(x, y, WHITE); } else { - display->set(x, y, BLACK); + display.set(x, y, BLACK); } } } diff --git a/src/mode/Clock/Clock.h b/src/mode/Clock/Clock.h index baf86f6..71beac6 100644 --- a/src/mode/Clock/Clock.h +++ b/src/mode/Clock/Clock.h @@ -1,43 +1,44 @@ -#ifndef CLOCK_H -#define CLOCK_H +#ifndef MODE_CLOCK_H +#define MODE_CLOCK_H #include "mode/Mode.h" -class Clock : public Mode { +class Clock : public Mode { public: - explicit Clock(Display *display) : + explicit Clock(Display &display) : Mode(display) { // nothing } - ~Clock() override = default; - const char *getName() override { return "Clock"; } - void doStep(microseconds_t dt) override { - tm info{}; - time_t now; - time(&now); - localtime_r(&now, &info); - bool ok = info.tm_year >= (2023 - 1900); +protected: + + void step(microseconds_t dt) override { + if (realtimeChanged) { + markDirty(); + } + } + + void draw(Display &display) override { + display.clear(); - display->clear(); uint8_t x = 2; - x += display->print(x, 1, ok ? info.tm_hour / 10 : 13, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_hour / 10 : 13, WHITE); x++; - x += display->print(x, 1, ok ? info.tm_hour % 10 : 13, WHITE); - x += display->print(x, 1, 10, WHITE); - x += display->print(x, 1, ok ? info.tm_min / 10 : 13, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_hour % 10 : 13, WHITE); + x += display.print(x, 1, 10, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_min / 10 : 13, WHITE); x++; - x += display->print(x, 1, ok ? info.tm_min % 10 : 13, WHITE); - x += display->print(x, 1, 10, WHITE); - x += display->print(x, 1, ok ? info.tm_sec / 10 : 13, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_min % 10 : 13, WHITE); + x += display.print(x, 1, 10, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_sec / 10 : 13, WHITE); x++; - x += display->print(x, 1, ok ? info.tm_sec % 10 : 13, WHITE); + x += display.print(x, 1, realtimeOK ? now.tm_sec % 10 : 13, WHITE); } }; diff --git a/src/mode/GameOfLife/Cell.h b/src/mode/GameOfLife/Cell.h index c18e6a3..3b27e8b 100644 --- a/src/mode/GameOfLife/Cell.h +++ b/src/mode/GameOfLife/Cell.h @@ -14,7 +14,7 @@ public: Color color = BLACK; void animate(microseconds_t dt) { - // TODO "doStep" does not work as expected + // TODO fading does not work as expected if (alive) { fade = doStep(fade, 0.0, 255.0, 200, +dt); } else { diff --git a/src/mode/GameOfLife/GameOfLife.h b/src/mode/GameOfLife/GameOfLife.h index a354fe0..9761c2e 100644 --- a/src/mode/GameOfLife/GameOfLife.h +++ b/src/mode/GameOfLife/GameOfLife.h @@ -1,20 +1,27 @@ -#ifndef GAMEOFLIFE_H -#define GAMEOFLIFE_H +#ifndef MODE_GAME_OF_LIFE_H +#define MODE_GAME_OF_LIFE_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 { +class GameOfLife : public Mode { private: ColorMode colorMode; + size_t cellsSize; + + Cell *cells; + + Cell *cellsEnd; + + Cell *next; + microseconds_t runtime = 0; uint8_t steps = 0; @@ -25,21 +32,19 @@ private: uint8_t isSteadyCount = 0; - Cell *cells; - - Cell *next; - public: - explicit GameOfLife(Display *display, ColorMode colorMode) : + 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++) { + 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(display->pixelCount * sizeof(Cell)); - for (Cell *cell = next; cell < next + display->pixelCount; cell++) { + next = (Cell *) malloc(cellsSize); + for (Cell *cell = next; cell < next + display.pixelCount; cell++) { kill(cell); } } @@ -69,7 +74,9 @@ public: return "???"; } - void doStep(microseconds_t dt) override { +protected: + + void step(microseconds_t dt) override { runtime += dt; if (runtime >= 500000) { runtime = 0; @@ -85,14 +92,45 @@ public: nextGeneration(); } } - animate(dt); + 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 < cells + display->pixelCount; cell++) { + for (Cell *cell = cells; cell < cellsEnd; cell++) { if (random(4) == 0) { if (!cell->alive) { spawn(cell); @@ -117,11 +155,11 @@ private: } void nextGeneration() { - memcpy(next, cells, display->pixelCount * sizeof(Cell)); + memcpy(next, cells, cellsSize); Cell *src = cells; Cell *dst = next; - for (int y = 0; y < display->height; y++) { - for (int x = 0; x < display->width; x++) { + 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) { @@ -134,14 +172,14 @@ private: dst++; } } - memcpy(cells, next, display->pixelCount * sizeof(Cell)); + memcpy(cells, next, cellsSize); } void print() { Cell *cell = cells; - for (int y = 0; y < display->height; y++) { + for (int y = 0; y < height; y++) { Serial.print("|"); - for (int x = 0; x < display->width; x++) { + for (int x = 0; x < width; x++) { Serial.print(cell->alive ? "x|" : " |"); cell++; } @@ -150,33 +188,6 @@ private: } } - 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) + @@ -184,15 +195,16 @@ private: } uint8_t countIfAlive(int x, int y) { - if (x < 0 || y < 0 || x >= display->width || y >= display->height) { + 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 + display->width * y + x; + return cells + width * y + x; } + }; #endif diff --git a/src/mode/Mode.h b/src/mode/Mode.h index 349e158..09f5ec5 100644 --- a/src/mode/Mode.h +++ b/src/mode/Mode.h @@ -1,12 +1,8 @@ #ifndef MODE_H #define MODE_H -#define TIMER_COUNT 10 - #include "BASICS.h" #include "display/Display.h" -#include "Timer.h" -#include "ModeBase.h" enum ModeId { NONE, @@ -20,25 +16,121 @@ enum ModeId { SPACE_INVADERS, NEW_YEAR, STARFIELD, + MATRIX, }; -template -class Mode : public ModeBase { +class Mode { + +private: + + struct Timer { + millis_t interval; + millis_t last; + }; + + Display &_display; + + bool dirty = true; + + Timer timers[2] = { + {0, 0}, + {0, 0}, + }; protected: - static T *instance; + const uint8_t width; + + const uint8_t height; + + bool realtimeOK = false; + + bool realtimeChanged = false; + + tm now = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + + time_t epoch = 0; + + virtual void tick(uint8_t index, millis_t dt) {}; + + virtual void step(microseconds_t dt) {}; + + virtual void draw(Display &display) {}; + + void timer(uint8_t index, millis_t interval) { + if (index >= countof(timers)) { + return; + } + timers[index].interval = interval; + timers[index].last = millis(); + } + + void markDirty() { + dirty = true; + } public: - explicit Mode(Display *display) : - ModeBase(display) { - instance = (T *) this; + explicit Mode(Display &display) : + _display(display), + width(display.width), + height(display.height) { + // nothing + } + + virtual ~Mode() = default; + + virtual const char *getName() = 0; + + void loop(microseconds_t dt) { + Serial.print("[MODE] realtime\n"); + realtime(); + + Serial.print("[MODE] handleTimers\n"); + handleTimers(); + + Serial.print("[MODE] step\n"); + step(dt); + + if (dirty) { + dirty = false; + Serial.print("[MODE] draw\n"); + draw(_display); + } + } + +private: + + void realtime() { + time_t tmp; + time(&tmp); + realtimeOK = tmp > 1600000000; + if (realtimeOK) { + realtimeChanged = epoch != tmp; + if (realtimeChanged) { + epoch = tmp; + localtime_r(&tmp, &now); + now.tm_year += 1900; + now.tm_mon += 1; + } + } else { + realtimeChanged = false; + } + } + + void handleTimers() { + millis_t ms = millis(); + for (Timer *timer = timers; timer < timers + sizeof(timers); timer++) { + if (timer->interval > 0) { + millis_t dt = ms - timer->last; + if (dt >= timer->interval) { + timer->last = ms; + tick(timer - timers, dt); + } + } + } } }; -template -T *Mode::instance = nullptr; - #endif diff --git a/src/mode/ModeBase.h b/src/mode/ModeBase.h deleted file mode 100644 index 6623fcc..0000000 --- a/src/mode/ModeBase.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef MODE_BASE_H -#define MODE_BASE_H - -#define TIMER_COUNT 10 - -#include "BASICS.h" -#include "display/Display.h" -#include "Timer.h" - -class ModeBase { - -private: - - Timer *timers[TIMER_COUNT]{}; - - uint8_t timerCount = 0; - -protected: - - Display *display; - -public: - - explicit ModeBase(Display *display) : - display(display) { - for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { - *timer = nullptr; - } - } - - virtual ~ModeBase() { - destroyAllTimers(); - }; - - virtual const char *getName() = 0; - - void step(microseconds_t dt) { - for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { - if (*timer != nullptr) { - (*timer)->step(dt); - } - } - doStep(dt); - } - -protected: - - virtual void doStep(microseconds_t dt) {}; - - Timer *createTimer(long long millisecondsInterval, Timer::Callback callback) { - for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { - if (*timer == nullptr) { - *timer = new Timer(millisecondsInterval * 1000, callback); - timerCount++; - return *timer; - } - } - Serial.printf("ERROR: Cannot create more than %d timers!\n", TIMER_COUNT); - return nullptr; - } - - bool destroyTimer(Timer *t) { - for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { - if (*timer == t) { - _destroyTimer(timer); - return true; - } - } - Serial.printf("ERROR: No such timer.\n"); - return false; - } - - void destroyAllTimers() { - for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { - if (*timer != nullptr) { - _destroyTimer(timer); - } - } - } - -private: - - void _destroyTimer(Timer **timer) { - timerCount--; - free(*timer); - *timer = nullptr; - } - -}; - -#endif diff --git a/src/mode/NewYear/Firework.h b/src/mode/NewYear/Firework.h index 97f8ed3..38865df 100644 --- a/src/mode/NewYear/Firework.h +++ b/src/mode/NewYear/Firework.h @@ -11,7 +11,9 @@ class Firework { INITIAL, RISE, EXPLODE, SPARKLE }; - Display *display{}; + uint8_t width = 0; + + uint8_t height = 0; Color color = MAGENTA; @@ -31,17 +33,18 @@ class Firework { public: - void init(Display *_display) { - this->display = _display; + void init(Display &display) { + width = display.width; + height = display.height; reset(); } void reset() { - position = Vector((double) random(display->width), display->height); + position = Vector((double) random(width), height); color = randomColor(); state = INITIAL; - destinationHeight = display->height / 2.0 + (double) random(5) - 2; + destinationHeight = height / 2.0 + (double) random(5) - 2; explosionRadius = (double) random(3) + 1; sparkleMax = 100; @@ -49,12 +52,12 @@ public: sparkle = 0.0; } - void launch() { + bool launch() { if (state != INITIAL) { - Serial.println("ERROR: Cannot start Firework. Already started."); - return; + return false; } state = RISE; + return true; } void step(microseconds_t dt) { @@ -81,7 +84,7 @@ public: case INITIAL: break; case RISE: - position.y = doStep(position.y, 0.0, display->height, 1000, -dt); + position.y = doStep(position.y, 0.0, height, 1000, -dt); break; case EXPLODE: explosion = doStep(explosion, 0.0, explosionRadius, 500, +dt); @@ -92,42 +95,40 @@ public: } } - void draw() { + void draw(Display display) { switch (state) { case INITIAL: break; case RISE: - display->set(position, YELLOW); + display.set(position, YELLOW); break; case EXPLODE: - drawParticle(+0.0, +1.0); - drawParticle(+0.7, +0.7); - drawParticle(+1.0, +0.0); - drawParticle(+0.7, -0.7); - drawParticle(+0.0, -1.0); - drawParticle(-0.7, -0.7); - drawParticle(-1.0, +0.0); - drawParticle(-0.7, +0.7); + drawParticle(display, +0.0, +1.0); + drawParticle(display, +0.7, +0.7); + drawParticle(display, +1.0, +0.0); + drawParticle(display, +0.7, -0.7); + drawParticle(display, +0.0, -1.0); + drawParticle(display, -0.7, -0.7); + drawParticle(display, -1.0, +0.0); + drawParticle(display, -0.7, +0.7); break; case SPARKLE: - if (randomBool(2)) drawParticle(+0.0, +1.0); - if (randomBool(2)) drawParticle(+0.7, +0.7); - if (randomBool(2)) drawParticle(+1.0, +0.0); - if (randomBool(2)) drawParticle(+0.7, -0.7); - if (randomBool(2)) drawParticle(+0.0, -1.0); - if (randomBool(2)) drawParticle(-0.7, -0.7); - if (randomBool(2)) drawParticle(-1.0, +0.0); - if (randomBool(2)) drawParticle(-0.7, +0.7); + if (randomBool(2)) drawParticle(display, +0.0, +1.0); + if (randomBool(2)) drawParticle(display, +0.7, +0.7); + if (randomBool(2)) drawParticle(display, +1.0, +0.0); + if (randomBool(2)) drawParticle(display, +0.7, -0.7); + if (randomBool(2)) drawParticle(display, +0.0, -1.0); + if (randomBool(2)) drawParticle(display, -0.7, -0.7); + if (randomBool(2)) drawParticle(display, -1.0, +0.0); + if (randomBool(2)) drawParticle(display, -0.7, +0.7); break; } } - void drawParticle(double x, double y) { - display->set(position.plus(x * explosion, y * explosion), color); - } +private: - bool isAlive() { - return state != INITIAL; + void drawParticle(Display display, double x, double y) { + display.set(position.plus(x * explosion, y * explosion), color); } }; diff --git a/src/mode/NewYear/NewYear.h b/src/mode/NewYear/NewYear.h index 1e357ec..cda6af5 100644 --- a/src/mode/NewYear/NewYear.h +++ b/src/mode/NewYear/NewYear.h @@ -1,145 +1,225 @@ -#ifndef NEWYEAR_H -#define NEWYEAR_H +#ifndef MODE_NEW_YEAR_H +#define MODE_NEW_YEAR_H #define MAX_FIREWORKS 10 #include "mode/Mode.h" #include "Firework.h" -class NewYear : public Mode { +class NewYear : public Mode { - Firework fireworksBegin[MAX_FIREWORKS]; - Firework *fireworksEnd = fireworksBegin + MAX_FIREWORKS; +private: - void launch(Timer *timer, uint32_t counter, uint32_t currentCount) { - for (Firework *firework = fireworksBegin; firework < fireworksEnd; firework++) { - if (!firework->isAlive()) { - firework->launch(); + Firework fireworks[MAX_FIREWORKS]; + + int8_t lastSecond = -1; + + millis_t lastSecondMillis = 0; + + uint8_t days = 0; + + uint8_t level = 0; + +public: + + explicit NewYear(Display &display) : + Mode(display) { + timer(0, 500); + for (auto &firework: fireworks) { + firework.init(display); + } + } + + const char *getName() override { + return "NewYear"; + } + +protected: + + void step(microseconds_t dt) override { + if (!realtimeOK) { + setMode(NO_TIME); + } else if (now.tm_mon != 1 || now.tm_mday != 1 || now.tm_hour != 0) { + days = getDayCountForYear(now.tm_year) - now.tm_yday - 1; + if (days == 0) { + loopLastDay(); + } else { + loopMultipleDays(); + } + } else { + setMode(FIREWORK); + for (auto &firework: fireworks) { + firework.step(dt); + } + } + } + + void loopLastDay() { + setMode(LAST_DAY); + int levelTmp = getLevel(); + if (level != levelTmp) { + level = levelTmp; + markDirty(); + } + } + + void loopMultipleDays() { + setMode(MULTIPLE_DAYS); + if (realtimeChanged) { + markDirty(); + } + } + + void tick(uint8_t index, millis_t dt) override { + launch(); + } + + void draw(Display &display) override { + display.clear(); + if (realtimeOK) { + drawNoTime(display); + } else if (now.tm_mon == 1 && now.tm_mday == 1 && now.tm_hour == 0) { + drawYear(display, now.tm_year + 1900); + drawFirework(display); + } else { + drawCountdown(display, now); + } + } + +private: + + enum State { + NO_TIME, + MULTIPLE_DAYS, + LAST_DAY, + FIREWORK, + }; + + State _state = NO_TIME; + + void setMode(State state) { + if (_state != state) { + _state = state; + markDirty(); + timer(0, 0); + switch (state) { + case LAST_DAY: + lastSecond = -1; + break; + case FIREWORK: + timer(0, 500); + break; + default: + break; + } + } + } + + int getLevel() { + if (lastSecond < 0 || lastSecond != now.tm_sec) { + lastSecond = (int8_t) now.tm_sec; + lastSecondMillis = millis(); + } + millis_t mils = millis() - lastSecondMillis; + int levelTmp = (int) round(32 * mils / 1000.0); + return levelTmp; + } + + void launch() { + for (auto &firework: fireworks) { + if (firework.launch()) { return; } } } - int lastSecond = -1; - - unsigned long lastSecondMillis = 0; - - void step(Timer *timer, uint32_t counter, uint32_t currentCount) { - tm info{}; - time_t now; - time(&now); - localtime_r(&now, &info); - info.tm_year += 1900; - info.tm_mon += 1; - bool ok = info.tm_year >= 2023; - - display->clear(); - if (!ok) { - drawNoTime(); - } else if (info.tm_mon == 1 && info.tm_mday == 1 && info.tm_hour == 0) { - drawYear(counter, info.tm_year + 1900); - drawFirework(timer->interval); - } else { - drawCountdown(info); - } - } - - void drawNoTime() { - uint8_t x = 2; - x += display->print(x, 1, 13, WHITE); - x++; - x += display->print(x, 1, 13, WHITE); - x += display->print(x, 1, 10, WHITE); - x += display->print(x, 1, 13, WHITE); - x++; - x += display->print(x, 1, 13, WHITE); - x += display->print(x, 1, 10, WHITE); - x += display->print(x, 1, 13, WHITE); - x++; - x += display->print(x, 1, 13, WHITE); - } - - void drawCountdown(const tm &time) { - int days = getDayCountForYear(time.tm_year) - time.tm_yday - 1; - - int hours = (24 - time.tm_hour - (time.tm_min > 0 || time.tm_sec > 0 ? 1 : 0)); - int minutes = (60 - time.tm_min - (time.tm_sec > 0 ? 1 : 0)) % 60; - int seconds = (60 - time.tm_sec) % 60; + void drawCountdown(Display &display, const tm &now) { + uint8_t hours = (24 - now.tm_hour - (now.tm_min > 0 || now.tm_sec > 0 ? 1 : 0)); + uint8_t minutes = (60 - now.tm_min - (now.tm_sec > 0 ? 1 : 0)) % 60; + uint8_t seconds = (60 - now.tm_sec) % 60; uint8_t x = 0; - if (days > 0) { - drawDay(days, &x); - x += display->print(x, 1, 10, WHITE); + drawDay(display, days, &x); + x += display.print(x, 1, 10, WHITE); } else { x += 2; } - drawHour(days, hours, &x); - x += display->print(x, 1, 10, WHITE); - draw2Digit(minutes, &x); + drawHour(display, days, hours, &x); + x += display.print(x, 1, 10, WHITE); + draw2Digit(display, minutes, &x); if (days <= 0) { - x += display->print(x, 1, 10, WHITE); - draw2Digit(seconds, &x); - drawSubSecondsBar(time.tm_sec); + x += display.print(x, 1, 10, WHITE); + draw2Digit(display, seconds, &x); + drawSubSecondsBar(display); } else { - drawSecondsBar(seconds); + drawSecondsBar(display, seconds); } } - void drawDay(int days, uint8_t *x) { + static void drawNoTime(Display &display) { + uint8_t x = 2; + x += display.print(x, 1, 13, WHITE); + x++; + x += display.print(x, 1, 13, WHITE); + x += display.print(x, 1, 10, WHITE); + x += display.print(x, 1, 13, WHITE); + x++; + x += display.print(x, 1, 13, WHITE); + x += display.print(x, 1, 10, WHITE); + x += display.print(x, 1, 13, WHITE); + x++; + x += display.print(x, 1, 13, WHITE); + } + + static void drawDay(Display &display, int days, uint8_t *x) { if (days > 100) { - *x += display->print(*x, 1, days / 100, WHITE); + *x += display.print(*x, 1, days / 100, WHITE); } else { *x += 3; } (*x)++; if (days > 10) { - *x += display->print(*x, 1, days / 10 % 10, WHITE); + *x += display.print(*x, 1, days / 10 % 10, WHITE); } else { *x += 3; } (*x)++; - *x += display->print(*x, 1, days % 10, WHITE); + *x += display.print(*x, 1, days % 10, WHITE); } - void drawHour(int days, int hours, uint8_t *x) { + static void drawHour(Display &display, int days, int hours, uint8_t *x) { if (days > 0 || hours >= 10) { - *x += display->print(*x, 1, hours / 10, WHITE); + *x += display.print(*x, 1, hours / 10, WHITE); } else { *x += 3; } (*x)++; - *x += display->print(*x, 1, hours % 10, WHITE); + *x += display.print(*x, 1, hours % 10, WHITE); } - void draw2Digit(int value, uint8_t *x) { - *x += display->print(*x, 1, value / 10, WHITE); + static void draw2Digit(Display &display, int value, uint8_t *x) { + *x += display.print(*x, 1, value / 10, WHITE); (*x)++; - *x += display->print(*x, 1, value % 10, WHITE); + *x += display.print(*x, 1, value % 10, WHITE); } - void drawSecondsBar(int seconds) { + static void drawSecondsBar(Display &display, int seconds) { for (int pos = 0; pos < 30; pos++) { if (pos <= seconds - 30) { - display->set(pos + 1, 7, GREEN); + display.set(pos + 1, 7, GREEN); } else if (pos <= seconds) { - display->set(pos + 1, 7, RED); + display.set(pos + 1, 7, RED); } } } - void drawSubSecondsBar(const int second) { - if (lastSecond < 0 || lastSecond != second) { - lastSecond = second; - lastSecondMillis = millis(); - } - unsigned long mils = millis() - lastSecondMillis; - int level = (int) round(32 * mils / 1000.0); + void drawSubSecondsBar(Display &display) const { for (int pos = 0; pos < 32; pos++) { if (pos < 32 - level) { - display->set(pos, 7, GREEN); + display.set(pos, 7, GREEN); } } } @@ -152,43 +232,23 @@ class NewYear : public Mode { return 365; } - void drawYear(uint32_t counter, int year) { + static void drawYear(Display &display, int year) { uint8_t x = 8; - x += display->print(x, 1, year / 1000 % 10, counter % 64 != 0 ? WHITE : BLACK); + x += display.print(x, 1, year / 1000 % 10, WHITE); x++; - x += display->print(x, 1, year / 100 % 10, counter % 64 != 1 ? WHITE : BLACK); + x += display.print(x, 1, year / 100 % 10, WHITE); x++; - x += display->print(x, 1, year / 10 % 10, counter % 64 != 2 ? WHITE : BLACK); + x += display.print(x, 1, year / 10 % 10, WHITE); x++; - x += display->print(x, 1, year / 1 % 10, counter % 64 != 3 ? WHITE : BLACK); + x += display.print(x, 1, year / 1 % 10, WHITE); } - void drawFirework(microseconds_t dt) const { - for (auto *firework = (Firework *) fireworksBegin; firework < fireworksEnd; firework++) { - if (firework->isAlive()) { - firework->step(dt); - firework->draw(); - } + void drawFirework(Display &display) { + for (auto &firework: fireworks) { + firework.draw(display); } } -public: - - explicit NewYear(Display *display) : - Mode(display) { - createTimer(500, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->launch(timer, counter, currentCount); }); - createTimer(50, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->step(timer, counter, currentCount); }); - for (Firework *firework = fireworksBegin; firework < fireworksEnd; firework++) { - firework->init(display); - } - } - - ~NewYear() override = default; - - const char *getName() override { - return "NewYear"; - } - }; #endif diff --git a/src/mode/Pong/Pong.h b/src/mode/Pong/Pong.h index 21e8708..3cbc53d 100644 --- a/src/mode/Pong/Pong.h +++ b/src/mode/Pong/Pong.h @@ -1,11 +1,11 @@ -#ifndef PONG_H -#define PONG_H +#ifndef MODE_PONG_H +#define MODE_PONG_H #include "mode/Mode.h" #include "Player.h" #include "display/Vector.h" -class Pong : public Mode { +class Pong : public Mode { private: @@ -25,34 +25,45 @@ private: microseconds_t timeout = 0; - void resetPlayer() { - player0.size = 3; - player0.score = 0; - player0.position = (display->height - player0.size) / 2; - player1.size = 3; - player1.score = 0; - player1.position = (display->height - player1.size) / 2; +public: + + explicit Pong(Display &display) : + Mode(display), + position(width / 2.0, height / 2.0), + velocity(Vector::polar(random(360), exp10(1))) { + timer(0, 100); + spawnBall(random(2) == 0 ? -1 : +1); + resetPlayer(); } - void tick(Timer *timer, uint32_t totalCount, uint32_t currentCount) { + const char *getName() override { + return "Pong"; + } + +protected: + + void tick(uint8_t index, millis_t dt) override { switch (status) { case SCORE: - timeout -= currentCount * timer->interval; + timeout -= dt; if (timeout <= 0) { status = PLAY; } break; case PLAY: position = position.plus(velocity); - player0.randomMove(display->height); - player1.randomMove(display->height); + player0.randomMove(height); + player1.randomMove(height); paddleBounce(); checkScoring(); topBottomBounce(); - draw(); + + // TODO don't always markDirty. Be more efficient + markDirty(); + break; case OVER: - timeout -= currentCount * timer->interval; + timeout -= dt; if (timeout <= 0) { resetPlayer(); status = SCORE; @@ -62,15 +73,54 @@ private: } } + void draw(Display &display) override { + display.clear(); + switch (status) { + case SCORE: + display.print(1, 1, player0.score, GREEN); + display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED); + break; + case PLAY: + for (int i = 0; i < player0.size; ++i) { + display.set(1, (uint8_t) round(player0.position) + i, GREEN); + } + for (int i = 0; i < player1.size; ++i) { + display.set(width - 2, (uint8_t) round(player1.position) + i, RED); + } + display.set((uint8_t) round(position.x), (uint8_t) round(position.y), WHITE); + break; + case OVER: + if (player0.score > player1.score) { + display.print(1, 1, 11, GREEN); + display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED); + } else if (player0.score < player1.score) { + display.print(1, 1, 12, RED); + display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN); + } + break; + } + } + +private: + + void resetPlayer() { + player0.size = 3; + player0.score = 0; + player0.position = (height - player0.size) / 2; + player1.size = 3; + player1.score = 0; + player1.position = (height - player1.size) / 2; + } + void topBottomBounce() { - position = position.bounceInBox(display->width, display->height); + position = position.bounceInBox(width, height); } void checkScoring() { if (position.x < 0) { player1.score++; spawnBall(+1); - } else if (position.x >= display->width) { + } else if (position.x >= width) { player0.score++; spawnBall(-1); } @@ -84,67 +134,21 @@ private: if (position.x == 1 && player0.position <= position.y && position.y < player0.position + player0.size) { velocity.x = -velocity.x; position.x = 3; - } else if (position.x == display->width - 2 && player1.position <= position.y && position.y < player1.position + player1.size) { + } else if (position.x == width - 2 && player1.position <= position.y && position.y < player1.position + player1.size) { velocity.x = -velocity.x; - position.x = display->width - 4; + position.x = width - 4; } } void spawnBall(int direction) { - position.x = (double) display->width / 2.0; - position.y = (double) display->height / 2.0; + position.x = (double) width / 2.0; + position.y = (double) height / 2.0; velocity.x = direction; velocity.y = 0; status = SCORE; timeout = 2000000; } - void draw() { - display->clear(); - switch (status) { - case SCORE: - display->print(1, 1, player0.score, GREEN); - display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED); - break; - case PLAY: - for (int i = 0; i < player0.size; ++i) { - display->set(1, (uint8_t) round(player0.position) + i, GREEN); - } - for (int i = 0; i < player1.size; ++i) { - display->set(display->width - 2, (uint8_t) round(player1.position) + i, RED); - } - display->set((uint8_t) round(position.x), (uint8_t) round(position.y), WHITE); - break; - case OVER: - if (player0.score > player1.score) { - display->print(1, 1, 11, GREEN); - display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED); - } else if (player0.score < player1.score) { - display->print(1, 1, 12, RED); - display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN); - } - break; - } - } - -public: - - explicit Pong(Display *display) : - Mode(display), - position(display->width / 2.0, display->height / 2.0), - velocity(Vector::polar(random(360), exp10(1))) { - instance = this; - createTimer(100, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->tick(timer, counter, currentCount); }); - spawnBall(random(2) == 0 ? -1 : +1); - resetPlayer(); - } - - ~Pong() override = default; - - const char *getName() override { - return "Pong"; - } - }; #endif diff --git a/src/mode/SpaceInvaders/SpaceInvaders.h b/src/mode/SpaceInvaders/SpaceInvaders.h index ec004a9..d4f76c9 100644 --- a/src/mode/SpaceInvaders/SpaceInvaders.h +++ b/src/mode/SpaceInvaders/SpaceInvaders.h @@ -1,5 +1,5 @@ -#ifndef SPACEINVADERS_H -#define SPACEINVADERS_H +#ifndef MODE_SPACE_INVADERS_H +#define MODE_SPACE_INVADERS_H #define ROCKET_MAX 20 @@ -18,7 +18,7 @@ struct Invader { uint8_t y; }; -class SpaceInvaders : public Mode { +class SpaceInvaders : public Mode { private: @@ -45,10 +45,10 @@ private: public: - explicit SpaceInvaders(Display *display) : + explicit SpaceInvaders(Display &display) : Mode(display), - invadersCountX(display->width / 3), - invadersCountY(display->height / 4) { + invadersCountX(width / 3), + invadersCountY(height / 4) { swarmBegin = (Invader *) malloc(sizeof(Invader) * invadersCountX * invadersCountY); swarmEnd = swarmBegin + invadersCountX * invadersCountY; @@ -68,24 +68,37 @@ public: return "Space Invaders"; } - void doStep(microseconds_t dt) override { +protected: + + void step(microseconds_t dt) override { stepRockets(dt); stepInvaders(dt); stepHero(dt); - draw(); - collide(); if (invadersAlive == 0) { Serial.println("WINNER!"); reset(); } - if (swarmY + (invadersCountY - 1) * 2 >= display->height - 1) { // TODO this is only correct if there still are any invaders in the last row (otherwise we "Game Over" too early) + // TODO this is only correct if there still are any invaders in the last row (otherwise we "Game Over" too early) + if (swarmY + (invadersCountY - 1) * 2 >= height - 1) { Serial.println("GAME OVER"); reset(); } + + // TODO don't always markDirty. Be more efficient + markDirty(); } + void draw(Display &display) override { + display.clear(); + drawInvaders(display); + drawRockets(display); + drawHero(display); + } + +private: + void stepRockets(microseconds_t dt) { rocketRuntime += dt; if (rocketRuntime > 200000) { @@ -146,7 +159,7 @@ public: } } else { heroX++; - if (heroX >= display->width - 2 || randomBool(20)) { + if (heroX >= width - 2 || randomBool(20)) { heroLeft = true; } } @@ -164,7 +177,7 @@ public: if (!rocket->alive && rocket->flash == 0) { rocket->alive = true; rocket->x = heroX; - rocket->y = display->height - 2; + rocket->y = height - 2; } } } @@ -195,47 +208,40 @@ public: && swarmX + invader->x * 3 + 1 >= rocket->x; } - void draw() { - display->clear(); - drawInvaders(); - drawRockets(); - drawHero(); - } - - void drawInvaders() { + void drawInvaders(Display &display) { for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) { if (invader->alive) { - display->set(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, RED); - display->set(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, RED); + display.set(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, RED); + display.set(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, RED); } } } - void drawRockets() { + void drawRockets(Display &display) { for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { if (rocket->alive) { - display->set(rocket->x, rocket->y, YELLOW); + display.set(rocket->x, rocket->y, YELLOW); } else if (rocket->flash > 0) { - display->set(rocket->x - 1, rocket->y - 1, gray(rocket->flash)); - display->set(rocket->x - 1, rocket->y + 1, gray(rocket->flash)); - display->set(rocket->x + 0, rocket->y + 0, gray(rocket->flash)); - display->set(rocket->x + 1, rocket->y + 1, gray(rocket->flash)); - display->set(rocket->x + 1, rocket->y - 1, gray(rocket->flash)); + display.set(rocket->x - 1, rocket->y - 1, gray(rocket->flash)); + display.set(rocket->x - 1, rocket->y + 1, gray(rocket->flash)); + display.set(rocket->x + 0, rocket->y + 0, gray(rocket->flash)); + display.set(rocket->x + 1, rocket->y + 1, gray(rocket->flash)); + display.set(rocket->x + 1, rocket->y - 1, gray(rocket->flash)); } } } - void drawHero() { - display->set(heroX - 1, display->height - 1, BLUE); - display->set(heroX + 0, display->height - 1, BLUE); - display->set(heroX + 1, display->height - 1, BLUE); + void drawHero(Display &display) { + display.set(heroX - 1, height - 1, BLUE); + display.set(heroX + 0, height - 1, BLUE); + display.set(heroX + 1, height - 1, BLUE); } void reset() { heroRuntime = 0; heroLeft = false; heroShoot = 0; - heroX = display->width / 2; + heroX = width / 2; rocketRuntime = 0; for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { diff --git a/src/mode/Starfield/Starfield.h b/src/mode/Starfield/Starfield.h index da409bc..735655e 100644 --- a/src/mode/Starfield/Starfield.h +++ b/src/mode/Starfield/Starfield.h @@ -1,11 +1,11 @@ -#ifndef MEDIATABLE_STARFIELD_H -#define MEDIATABLE_STARFIELD_H +#ifndef MODE_STARFIELD_H +#define MODE_STARFIELD_H #include "mode/Mode.h" #define STAR_COUNT 20 -class Starfield : public Mode { +class Starfield : public Mode { private: @@ -15,12 +15,12 @@ private: public: - explicit Starfield(Display *display) : + explicit Starfield(Display &display) : Mode(display), - center(display->width / 2.0, display->height / 2.0) { + center(width / 2.0, height / 2.0) { for (auto &star: stars) { - star.x = random(display->width); - star.y = random(display->height); + star.x = random(width); + star.y = random(height); } } @@ -28,16 +28,26 @@ public: return "Starfield"; } - void doStep(microseconds_t dt) override { - display->clear(); +protected: + + void step(microseconds_t dt) override { for (auto &star: stars) { const Vector velocity = star.minus(center).multiply((double) dt / 200000.0); star = star.plus(velocity); - if (star.x < 0 || star.x >= display->width || star.y < 0 || star.y >= display->height) { + if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) { star = center.plus(Vector::polar(random(360), 1)); } - uint8_t brightness = (uint8_t) round(255.0 * star.minus(center).length / (display->width / 2.0)); - display->set(star, gray(brightness)); + } + + // TODO don't always markDirty. Be more efficient + markDirty(); + } + + void draw(Display &display) override { + display.clear(); + for (auto &star: stars) { + uint8_t brightness = (uint8_t) round(255.0 * star.minus(center).length / (width / 2.0)); + display.set(star, gray(brightness)); } } diff --git a/src/mode/Timer.h b/src/mode/Timer.h deleted file mode 100644 index 37bf9f2..0000000 --- a/src/mode/Timer.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef TIMER_H -#define TIMER_H - -#include "BASICS.h" - -class Timer { - -public: - - typedef void (*Callback)(Timer *timer, uint32_t counter, uint32_t currentCount); - -private: - - Callback callback; - - microseconds_t accu = 0; - - uint32_t totalCount = 0; - -public: - - microseconds_t interval; - - Timer(microseconds_t interval, Callback callback) : - callback(callback), - interval(interval) { - // nothing - } - - void step(microseconds_t dt) { - accu += dt; - if (accu >= interval) { - uint32_t currentCount = accu / interval; - totalCount += currentCount; - accu = accu % interval; - callback(this, totalCount, currentCount); - } - } - -}; - -#endif diff --git a/src/server.cpp b/src/server.cpp index d95ca3f..c89d329 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -113,5 +113,5 @@ void web_fps_off() { void redirect() { server.sendHeader("location", "/"); - server.send(302, "text/plain", "ok"); + server.send(302, "text/plain", "realtimeOK"); }