From 0b891d9604bd7b3c52ba76d6f75b799c3dce657c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ha=C3=9Fel?= Date: Thu, 23 Jan 2025 22:29:27 +0100 Subject: [PATCH] FIXES after firsts tests on hardware (basically working now) --- src/BASICS.cpp | 13 + src/BASICS.h | 22 ++ src/Node.h | 109 ++++++- src/mode.cpp | 156 +++++++++ src/mode.h | 20 ++ src/mode/Border/Border.h | 33 +- src/mode/Clock/Clock.h | 49 ++- src/mode/CountDown/CountDown.h | 171 +++------- src/mode/CountDown/CountDownFirework.h | 218 ++++++------- src/mode/Energy/Energy.h | 118 ++++--- src/mode/GameOfLife/Cell.h | 35 +- src/mode/GameOfLife/GameOfLife.h | 310 +++++++++--------- src/mode/Matrix/Matrix.h | 77 +++-- src/mode/Mode.h | 223 ++++++------- src/mode/Pong/Player.h | 2 +- src/mode/Pong/Pong.h | 312 +++++++++--------- src/mode/Power/Power.h | 71 +++-- src/mode/SpaceInvaders/SpaceInvaders.h | 423 ++++++++++++------------- src/mode/Starfield/Starfield.h | 108 ++++--- src/mode/Timer/Timer.h | 74 +---- 20 files changed, 1373 insertions(+), 1171 deletions(-) create mode 100644 src/BASICS.cpp create mode 100644 src/BASICS.h create mode 100644 src/mode.cpp create mode 100644 src/mode.h diff --git a/src/BASICS.cpp b/src/BASICS.cpp new file mode 100644 index 0000000..6284bfd --- /dev/null +++ b/src/BASICS.cpp @@ -0,0 +1,13 @@ +#include "BASICS.h" + +double doStep(const double valueCurrent, const double valueMin, const double valueMax, const long long millisecondsTotal, const microseconds_t microsecondsDelta) { + const auto valueRange = valueMax - valueMin; + const auto timeRatio = static_cast(microsecondsDelta) / (static_cast(millisecondsTotal) * 1000.0); + const auto valueStep = valueRange * timeRatio; + const auto valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep)); + return valueNew; +} + +bool randomBool(const int uncertainty) { + return random(uncertainty) == 0; +} diff --git a/src/BASICS.h b/src/BASICS.h new file mode 100644 index 0000000..75bac6d --- /dev/null +++ b/src/BASICS.h @@ -0,0 +1,22 @@ +#ifndef BASICS_H +#define BASICS_H + +#include + +#define X true +#define _ false + +#define ____ 0 +#define QUAR 64 +#define HALF 128 +#define FULL 255 + +typedef int64_t microseconds_t; + +typedef unsigned long milliseconds_t; + +double doStep(double valueCurrent, double valueMin, double valueMax, microseconds_t millisecondsTotal, microseconds_t microsecondsDelta); + +bool randomBool(int uncertainty); + +#endif diff --git a/src/Node.h b/src/Node.h index 7318586..d96d365 100644 --- a/src/Node.h +++ b/src/Node.h @@ -1,13 +1,102 @@ #ifndef NODE_H #define NODE_H -#include #include #include -Config config("/test.json"); +#include +#include -DisplayMatrix<32, 8> display(27); +DisplayMatrix<32, 8> display(13); + +static const auto style = R"( + +)"; + +static const auto script = R"( + +)"; + +inline void httpMode(AsyncWebServerRequest *request) { + if (!request->hasParam("mode")) { + request->send(400); + } + setMode(static_cast(request->getParam("mode")->value().toInt())); + request->send(200); +} + +inline void httpIndex(AsyncWebServerRequest *request) { + auto *response = request->beginResponseStream("text/html"); + response->print(style); + response->print(script); + + response->print(R"(

)"); + response->print(R"(Player 0
)"); + response->print(R"(Player 1
)"); + response->print(R"(

)"); + + response->print(R"(

)"); + response->print(R"(NONE
)"); + response->print(R"(BORDER
)"); + response->print(R"(CLOCK
)"); + response->print(R"(GAME_OF_LIFE_BLACK_WHITE
)"); + response->print(R"(GAME_OF_LIFE_GRAYSCALE
)"); + response->print(R"(GAME_OF_LIFE_COLOR_FADE
)"); + response->print(R"(GAME_OF_LIFE_RANDOM_COLOR
)"); + response->print(R"(PONG
)"); + response->print(R"(SPACE_INVADERS
)"); + response->print(R"(COUNT_DOWN
)"); + response->print(R"(COUNT_DOWN_BARS
)"); + response->print(R"(COUNT_DOWN_SLEEP
)"); + response->print(R"(STARFIELD
)"); + response->print(R"(MATRIX
)"); + response->print(R"(POWER
)"); + response->print(R"(ENERGY
)"); + response->print(R"(TIMER
)"); + response->print(R"(

)"); + + response->print(R"(

)"); + response->print(R"(Helligkeit: + / -
)"); + response->print(R"(Geschwindigkeit: + / -
)"); + response->print(R"(FPS: EIN / AUS
)"); + response->print(R"(

)"); + + response->print(R"(

)"); + response->print(R"()"); + response->print(R"()"); + response->print(R"()"); + response->print(R"()"); + response->print(R"(

)"); + + response->print(R"(

)"); + response->print(R"()"); + response->print(R"(

)"); + + request->send(response); +} class Node final : public PatrixNode { @@ -18,19 +107,27 @@ public: } void setup() override { - config.read(); + modeSetup(); + + server.on("/", httpIndex); + server.on("/mode", httpMode); + display.setup(); - display.setBrightness(6); + display.setBrightness(10); display.clear(); display.foreground = Blue; display.printf("Test"); } void loop() override { - config.loop(); + modeLoop(display); display.loop(); } + void mqttMessage(char *topic, char *message) override { + modeMqttMessage(topic, message); + } + }; #endif diff --git a/src/mode.cpp b/src/mode.cpp new file mode 100644 index 0000000..85651dd --- /dev/null +++ b/src/mode.cpp @@ -0,0 +1,156 @@ +#include "mode.h" + +#include "mode/Border/Border.h" +#include "mode/Clock/Clock.h" +#include "mode/GameOfLife/GameOfLife.h" +#include "mode/Pong/Pong.h" +#include "mode/SpaceInvaders/SpaceInvaders.h" +#include "mode/CountDown/CountDown.h" +#include "mode/Starfield/Starfield.h" +#include "mode/Matrix/Matrix.h" +#include "mode/Power/Power.h" +#include "mode/Energy/Energy.h" +#include "mode/Timer/Timer.h" + +#include + +Config config("/main.json"); + +auto current = NONE; + +auto wanted = NONE; + +microseconds_t modeStepLastMicros = 0; + +Mode *mode = nullptr; + +auto modeSpeed = 1.0; + +void unloadOldMode(Display& display); + +void loadNewMode(Display& display); + +void modeStep(); + +void modeSetup() { + wanted = config.get("mode", GAME_OF_LIFE_RANDOM_COLOR); + modeSpeed = config.get("mode_speed", 1.0); +} + +void modeLoop(Display& display) { + if (current != wanted) { + unloadOldMode(display); + loadNewMode(display); + } + modeStep(); +} + +void modeMqttMessage(const char *topic, const char *message) { + if (mode != nullptr) { + mode->mqttMessage(topic, message); + } +} + +void setMode(const ModeId newMode) { + config.setIfNot("mode", newMode); + wanted = newMode; +} + +void setSpeed(const double newSpeed) { + modeSpeed = min(max(0.01, newSpeed), 10000.0); + config.setIfNot("speed", modeSpeed); +} + +void modeMove(const int index, const int x, const int y) { + if (mode != nullptr) { + mode->move(index, x, y); + } +} + +void modeFire(const int index) { + if (mode != nullptr) { + mode->fire(index); + } +} + +void unloadOldMode(Display& display) { + if (mode != nullptr) { + info("Unloading mode: %s", mode->getName()); + mode->stop(); + delete mode; + mode = nullptr; + display.clear(); + } +} + +void loadNewMode(Display& display) { + switch (wanted) { + case BORDER: + mode = new Border(display); + break; + case CLOCK: + mode = new Clock(display); + break; + case GAME_OF_LIFE_BLACK_WHITE: + mode = new GameOfLife(display, BLACK_WHITE); + break; + case GAME_OF_LIFE_GRAYSCALE: + 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; + case SPACE_INVADERS: + mode = new SpaceInvaders(display); + break; + case COUNT_DOWN: + mode = new CountDown(display, false, false); + break; + case COUNT_DOWN_BARS: + mode = new CountDown(display, true, false); + break; + case COUNT_DOWN_SLEEP: + mode = new CountDown(display, false, true); + break; + case STARFIELD: + mode = new Starfield(display); + break; + case MATRIX: + mode = new Matrix(display); + break; + case POWER: + mode = new Power(display); + break; + case ENERGY: + mode = new Energy(display); + break; + case TIMER: + mode = new Timer2(display); + break; + default: + info("No such mode: %d", wanted); + break; + } + if (mode != nullptr) { + info("Loaded mode: %s", mode->getName()); + mode->start(); + } + modeStepLastMicros = 0; + current = wanted; +} + +void modeStep() { + if (mode == nullptr) { + return; + } + const auto currentMicros = static_cast(micros()); + const auto deltaMicros = static_cast(min(1000000.0, max(1.0, static_cast(currentMicros - modeStepLastMicros) * modeSpeed))); + modeStepLastMicros = currentMicros; + mode->loop(deltaMicros); +} diff --git a/src/mode.h b/src/mode.h new file mode 100644 index 0000000..5f9292b --- /dev/null +++ b/src/mode.h @@ -0,0 +1,20 @@ +#ifndef RGB_MATRIX_DISPLAY_MODE_H +#define RGB_MATRIX_DISPLAY_MODE_H + +#include "mode/Mode.h" + +void modeSetup(); + +void modeLoop(Display& display); + +void setMode(ModeId newMode); + +void setSpeed(double newSpeed); + +void modeMove(int index, int x, int y); + +void modeFire(int index); + +void modeMqttMessage(const char *topic, const char *message); + +#endif diff --git a/src/mode/Border/Border.h b/src/mode/Border/Border.h index ec4c8cb..8b32929 100644 --- a/src/mode/Border/Border.h +++ b/src/mode/Border/Border.h @@ -3,32 +3,27 @@ #include "mode/Mode.h" -class Border : public Mode { +class Border final : public Mode { public: - explicit Border(Display &display) : - Mode(display) { - // nothing - } + explicit Border(Display& display) : Mode(display) { + // nothing + } - const char *getName() override { - return "Border"; - } + const char *getName() override { + return "Border"; + } protected: - void draw(Display &display) override { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { - display.set(x, y, WHITE); - } else { - display.set(x, y, BLACK); - } - } - } - } + void draw(Display& display) override { + display.foreground = White; + display.drawLineWH(0, 0, display.width, 0, 1); + display.drawLineWH(0, 0, 0, display.height, 1); + display.drawLineWH(display.width - 1, display.height - 1, -display.width, 0, 1); + display.drawLineWH(display.width - 1, display.height - 1, 0, -display.height, 1); + } }; diff --git a/src/mode/Clock/Clock.h b/src/mode/Clock/Clock.h index ac9c18c..6738587 100644 --- a/src/mode/Clock/Clock.h +++ b/src/mode/Clock/Clock.h @@ -3,43 +3,36 @@ #include "mode/Mode.h" -class Clock : public Mode { +class Clock final : public Mode { public: - explicit Clock(Display &display) : - Mode(display) { - // nothing - } + explicit Clock(Display& display) : Mode(display) { + // + } - const char *getName() override { - return "Clock"; - } + ~Clock() override = default; + + const char *getName() override { + return "Clock"; + } protected: - void step(microseconds_t microseconds) override { - if (realtimeChanged) { - markDirty(); - } + void step(microseconds_t microseconds) override { + if (realtimeChanged) { + markDirty(); } + } - void draw(Display &display) override { - display.clear(); - - uint8_t x = 2; - x += display.print(x, 1, realtimeOK ? now.tm_hour / 10 : SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, realtimeOK ? now.tm_hour % 10 : SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, realtimeOK ? now.tm_min / 10 : SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, realtimeOK ? now.tm_min % 10 : SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, realtimeOK ? now.tm_sec / 10 : SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, realtimeOK ? now.tm_sec % 10 : SYMBOL_DASH, WHITE, true); - } + void draw(Display& display) override { + display.clear(); + display.foreground = White; + display.background = Transparent; + display.cursorX = 2; + display.cursorY = 1; + display.printf("%2d:%02d:%02d", now.tm_hour, now.tm_min, now.tm_sec); + } }; diff --git a/src/mode/CountDown/CountDown.h b/src/mode/CountDown/CountDown.h index 72a7ff9..41d80f3 100644 --- a/src/mode/CountDown/CountDown.h +++ b/src/mode/CountDown/CountDown.h @@ -3,15 +3,17 @@ #define MAX_FIREWORKS 6 +#include + #include "mode/Mode.h" #include "CountDownFirework.h" class CountDown : public Mode { -private: - Firework fireworks[MAX_FIREWORKS]; + tm target{}; + uint16_t days = 0; uint16_t hours = 0; @@ -28,7 +30,7 @@ private: public: - CountDown(Display& display, bool bars, bool plus1DayForSleepingCount) : Mode(display), bars(bars), plus1DayForSleepingCount(plus1DayForSleepingCount) { + CountDown(Display& display, const bool bars, const bool plus1DayForSleepingCount) : Mode(display), bars(bars), plus1DayForSleepingCount(plus1DayForSleepingCount) { for (auto& firework: fireworks) { firework.init(display); } @@ -37,14 +39,13 @@ public: const char *getName() override { if (bars) { return "CountDown (Bars)"; - } else { - return "CountDown (Numbers)"; } + return "CountDown (Numbers)"; } protected: - void step(microseconds_t microseconds) override { + void step(const microseconds_t microseconds) override { if (!realtimeOK) { setMode(NO_TIME); return; @@ -59,21 +60,21 @@ protected: } // GRRRRRRR... - config.date.tm_year -= 1900; - config.date.tm_mon -= 1; - const time_t dateEpochSeconds = mktime(&config.date); - config.date.tm_year += 1900; - config.date.tm_mon += 1; + target.tm_year -= 1900; + target.tm_mon -= 1; + const auto dateEpochSeconds = mktime(&target); + target.tm_year += 1900; + target.tm_mon += 1; // --- - const double diffSeconds = difftime(dateEpochSeconds, nowEpochSeconds); - days = (int) floor(diffSeconds / (24 * 60 * 60)); - hours = (int) floor(diffSeconds / (60 * 60)) % 24; - minutes = (int) floor(diffSeconds / 60) % 60; - seconds = (int) diffSeconds % 60; + const auto diffSeconds = difftime(dateEpochSeconds, nowEpochSeconds); + days = static_cast(floor(diffSeconds / (24 * 60 * 60))); + hours = static_cast(floor(diffSeconds / (60 * 60))) % 24; + minutes = static_cast(floor(diffSeconds / 60)) % 60; + seconds = static_cast(diffSeconds) % 60; // Serial.printf("now=%4d.%02d.%02d (%ld), conf=%4d.%02d.%02d (%ld), diff=%f, %dd %2d:%02d:%02d\n", // now.tm_year, now.tm_mon, now.tm_mday, nowEpochSeconds, - // config.date.tm_year, config.date.tm_mon, config.date.tm_mday, dateEpochSeconds, + // target.tm_year, target.tm_mon, target.tm_mday, dateEpochSeconds, // diffSeconds, // days, hours, minutes, seconds); setMode(COUNTDOWN); @@ -85,7 +86,7 @@ protected: } void loopLastDay() { - int levelTmp = (int) round(32 * realtimeMilliseconds / 1000.0); + const auto levelTmp = static_cast(round(32 * realtimeMilliseconds / 1000.0)); if (level != levelTmp) { level = levelTmp; markDirty(); @@ -98,8 +99,8 @@ protected: } } - bool dateReached() { - return now.tm_year == config.date.tm_year && now.tm_mon == config.date.tm_mon && now.tm_mday == config.date.tm_mday; + bool dateReached() const { + return now.tm_year == target.tm_year && now.tm_mon == target.tm_mon && now.tm_mday == target.tm_mday; } void draw(Display& display) override { @@ -126,14 +127,14 @@ private: State _state = NO_TIME; - void setMode(State state) { + void setMode(const State state) { if (_state != state) { _state = state; markDirty(); } } - void drawCountdown(Display& display) { + void drawCountdown(Display& display) const { if (plus1DayForSleepingCount) { drawSleepingCount(display); } else if (days <= 0 && bars) { @@ -144,26 +145,18 @@ private: } void drawSleepingCount(Display& display) const { - int sleepCount = days + 1; - int y = 1; - uint8_t x = display.print2(3 * (DISPLAY_CHAR_WIDTH + 1), y, sleepCount, WHITE); - x += 2; - x += display.print(x, y, SYMBOL_T, WHITE, true) + 1; - x += display.print(x, y, SYMBOL_A, WHITE, true) + 1; - x += display.print(x, y, SYMBOL_G, WHITE, true) + 1; - if (sleepCount != 1) { - display.print(x, y, SYMBOL_E, WHITE, true); - } + const auto sleepCount = days + 1; + display.printf("%02d Tag%s", sleepCount, sleepCount == 1 ? "" : "e"); } void drawCountdownBars(Display& display) const { - drawBar(display, 0, 24, 1, 24, hours, RED, MAGENTA, 0); - drawBar(display, 2, 30, 2, 60, minutes, BLUE, VIOLET, 5); - drawBar(display, 5, 30, 2, 60, seconds, GREEN, YELLOW, 5); + drawBar(display, 0, 24, 1, 24, hours, Red, Magenta, 0); + drawBar(display, 2, 30, 2, 60, minutes, Blue, Violet, 5); + drawBar(display, 5, 30, 2, 60, seconds, Green, Yellow, 5); } - static void drawBar(Display& display, uint8_t _y, uint8_t _w, uint8_t _h, uint8_t max, uint8_t value, const Color& color, const Color& tickColor, uint8_t ticks) { - auto totalOnCount = (uint8_t) round((double) value / max * _w * _h); + static void drawBar(Display& display, const uint8_t _y, const uint8_t _w, const uint8_t _h, const uint8_t max, const uint8_t value, const RGBA& color, const RGBA& tickColor, const uint8_t ticks) { + const auto totalOnCount = static_cast(round(static_cast(value) / max * _w * _h)); uint8_t doneOnCount = 0; for (uint8_t y = 0; y < _h; y++) { for (uint8_t x = 0; x < _w; x++) { @@ -171,127 +164,59 @@ private: return; } doneOnCount++; - Color c = color; + auto c = color; if (ticks != 0) { if (doneOnCount % ticks == 0) { c = tickColor; } } - display.set(x, _y + y, c); + display.setPixel(x, _y + y, c); } } } void drawCountdownNumbers(Display& display) const { - uint8_t x = 0; if (days > 0) { - drawDay(display, days, &x); - x += display.print(x, 1, 10, WHITE, true); - } else { - x += 2; - } - drawHour(display, days, hours, &x); - x += display.print(x, 1, 10, WHITE, true); - draw2Digit(display, minutes, &x); - if (days <= 0) { - x += display.print(x, 1, 10, WHITE, true); - draw2Digit(display, seconds, &x); - drawSubSecondsBar(display); - } else { + display.printf("%02d. %2d:%02d", days, hours, minutes); drawSecondsBar(display, seconds); + } else { + display.printf("%2d:%02d:%02d", hours, minutes, seconds); + drawSubSecondsBar(display); } } static void drawNoTime(Display& display) { - uint8_t x = 2; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - display.print(x, 1, SYMBOL_DASH, WHITE, true); + display.print("--:--:--"); } - static void drawDay(Display& display, int days, uint8_t *x) { - if (days >= 100) { - *x += display.print(*x, 1, days / 100, WHITE, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - - if (days >= 10) { - *x += display.print(*x, 1, days / 10 % 10, WHITE, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - - *x += display.print(*x, 1, days % 10, WHITE, true); - } - - 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, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - *x += display.print(*x, 1, hours % 10, WHITE, true); - } - - static void draw2Digit(Display& display, int value, uint8_t *x) { - *x += display.print(*x, 1, value / 10, WHITE, true) + 1; - *x += display.print(*x, 1, value % 10, WHITE, true); - } - - static void drawSecondsBar(Display& display, int seconds) { - for (int pos = 0; pos < 30; pos++) { + static void drawSecondsBar(Display& display, const int seconds) { + for (auto pos = 0; pos < 30; pos++) { if (pos <= seconds - 30) { - display.set(pos + 1, 7, GREEN); + display.setPixel(pos + 1, 7, Green); } else if (pos <= seconds) { - display.set(pos + 1, 7, RED); + display.setPixel(pos + 1, 7, Red); } } } void drawSubSecondsBar(Display& display) const { - for (int pos = 0; pos < 32; pos++) { + for (auto pos = 0; pos < 32; pos++) { if (pos < 32 - level) { - display.set(pos, 7, GREEN); + display.setPixel(pos, 7, Green); } } } - // ReSharper disable CppDFAUnusedValue - - void drawYear(Display& display, int year) const { + void drawYear(Display& display, const int year) const { if (plus1DayForSleepingCount) { - uint8_t x = 0; - x += display.print(x, 1,SYMBOL_E, WHITE, true); - x += 1; - x += display.printM(x, 1, WHITE); - x += 1; - x += display.printI(x, 1, WHITE); - x += 1; - x += display.print(x, 1,SYMBOL_L, WHITE, true); - x += 3; - x += display.print(x, 1, 5, WHITE, true); - x += 3; - display.printCreeper(x, 0); + display.printf("Emil 5"); + display.cursorX = 32 - 8; + display.cursorY = 0; } else { - uint8_t x = 8; - x += display.print(x, 1, year / 1000 % 10, WHITE, true) + 1; - x += display.print(x, 1, year / 100 % 10, WHITE, true) + 1; - x += display.print(x, 1, year / 10 % 10, WHITE, true) + 1; - x += display.print(x, 1, year / 1 % 10, WHITE, true); + display.printf("%5d", year); } } - // ReSharper restore CppDFAUnusedValue - }; #endif diff --git a/src/mode/CountDown/CountDownFirework.h b/src/mode/CountDown/CountDownFirework.h index 5fba7da..226955f 100644 --- a/src/mode/CountDown/CountDownFirework.h +++ b/src/mode/CountDown/CountDownFirework.h @@ -2,146 +2,138 @@ #define COUNT_DOWN_FIREWORK_H #include "BASICS.h" -#include "display/Vector.h" -#include "display/Display.h" +#include "Vector.h" #define DARKER_FACTOR 0.75 #define SLOWER_DIVISOR 1 class Firework { - enum State { - INITIAL, RISE, EXPLODE, SPARKLE - }; + enum State { + INITIAL, RISE, EXPLODE, SPARKLE + }; - uint8_t width = 0; + uint8_t width = 0; - uint8_t height = 0; + uint8_t height = 0; - Color color = MAGENTA; + RGBA color = Magenta; - State state = RISE; + State state = RISE; - Vector position; + Vector position; - double destinationHeight = 0; + double destinationHeight = 0; - double explosionRadius = 0; + double explosionRadius = 0; - double sparkleMax = 0; + double sparkleMax = 0; - double explosion{}; + double explosion{}; - double sparkle{}; + double sparkle{}; public: - void init(Display &display) { - width = display.width; - height = display.height; - reset(); + void init(const Display& display) { + width = display.width; + height = display.height; + reset(); + } + + void reset() { + position = Vector(random(width), height); + color = RGBA::rnd(); + state = INITIAL; + + destinationHeight = height / 2.0 + static_cast(random(5)) - 2; + explosionRadius = static_cast(random(3)) + 1; + sparkleMax = 100; + + explosion = 0.0; + sparkle = 0.0; + } + + void step(microseconds_t microseconds) { + microseconds = microseconds / SLOWER_DIVISOR; + switch (state) { + case INITIAL: + state = RISE; + break; + case RISE: + if (position.y > destinationHeight) { + position.y = doStep(position.y, 0.0, height, 1000, -microseconds); + } else { + state = EXPLODE; + } + break; + case EXPLODE: + if (explosion < explosionRadius) { + explosion = doStep(explosion, 0.0, explosionRadius, 500, +microseconds); + } else { + state = SPARKLE; + } + break; + case SPARKLE: + if (sparkle < sparkleMax) { + sparkle = doStep(sparkle, 0.0, sparkleMax, 1000, +microseconds); + } else { + reset(); + } + break; } + } - void reset() { - position = Vector((double) random(width), height); - color = randomColor(); - state = INITIAL; - - destinationHeight = height / 2.0 + (double) random(5) - 2; - explosionRadius = (double) random(3) + 1; - sparkleMax = 100; - - explosion = 0.0; - sparkle = 0.0; + void draw(Display& display) const { + switch (state) { + case INITIAL: + break; + case RISE: + display.setPixel(static_cast(position.x), static_cast(position.y), Yellow.factor(DARKER_FACTOR)); + break; + case EXPLODE: + 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(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 step(microseconds_t microseconds) { - microseconds = microseconds / SLOWER_DIVISOR; - switch (state) { - case INITIAL: - state = RISE; - break; - case RISE: - if (position.y > destinationHeight) { - position.y = doStep(position.y, 0.0, height, 1000, -microseconds); - } else { - state = EXPLODE; - } - break; - case EXPLODE: - if (explosion < explosionRadius) { - explosion = doStep(explosion, 0.0, explosionRadius, 500, +microseconds); - } else { - state = SPARKLE; - } - break; - case SPARKLE: - if (sparkle < sparkleMax) { - sparkle = doStep(sparkle, 0.0, sparkleMax, 1000, +microseconds); - } else { - reset(); - } - break; - } - } - - void draw(Display &display) { - switch (state) { - case INITIAL: - break; - case RISE: - display.set(position, factor(YELLOW, DARKER_FACTOR)); - break; - case EXPLODE: - 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(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; - } - } - - const char *getStateName() const { - switch (state) { - case INITIAL: - return "INITIAL"; - case RISE: - return "RISE"; - case EXPLODE: - return "EXPLODE"; - case SPARKLE: - return "SPARKLE"; - } - return "[???]"; + const char *getStateName() const { + switch (state) { + case INITIAL: + return "INITIAL"; + case RISE: + return "RISE"; + case EXPLODE: + return "EXPLODE"; + case SPARKLE: + return "SPARKLE"; } + return "[???]"; + } private: - static Color factor(Color color, double factor) { - return { - (uint8_t) round(color.r * factor), - (uint8_t) round(color.g * factor), - (uint8_t) round(color.b * factor), - }; - } - - void drawParticle(Display &display, double x, double y) { - display.set(position.plus(x * explosion, y * explosion), factor(color, DARKER_FACTOR)); - } + void drawParticle(Display& display, const double x, const double y) const { + const auto p = position.plus(x * explosion, y * explosion); + display.setPixel(static_cast(p.x), static_cast(p.y), color.factor(DARKER_FACTOR)); + } }; diff --git a/src/mode/Energy/Energy.h b/src/mode/Energy/Energy.h index 7d35cb4..70c2e0f 100644 --- a/src/mode/Energy/Energy.h +++ b/src/mode/Energy/Energy.h @@ -1,70 +1,96 @@ #ifndef MODE_ENERGY_H #define MODE_ENERGY_H +#include #include "mode/Mode.h" -#include "mqtt.h" -#define POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE 287.995 +#define PHOTOVOLTAIC_ENERGY_KWH "openDTU/pv/ac/yieldtotal" +#define GRID_IMPORT_WH "electricity/grid/energy/import/wh" +#define GRID_EXPORT_WH "electricity/grid/energy/export/wh" + +#define POWER_PHOTOVOLTAIC_photovoltaicEnergyKWh_BEFORE_METER_CHANGE 287.995 #define PV_COST_TOTAL_EURO 576.52 #define GRID_KWH_EURO 0.33 #define PV_COST_AMORTISATION_KWH ( PV_COST_TOTAL_EURO / GRID_KWH_EURO ) -class Energy : public Mode { +class Energy final : public Mode { -private: + double photovoltaicEnergyKWh = NAN; - int page = 0; + unsigned long photovoltaicEnergyKWhLast = 0; + + double gridImportKWh = NAN; + + unsigned long gridImportKWhLast = 0; + + double gridExportKWh = NAN; + + unsigned long gridExportKWhLast = 0; + + int page = 0; public: - explicit Energy(Display &display) : - Mode(display) { - timer(0, 7000); - } + explicit Energy(Display& display) : Mode(display) { + timer(0, 7000); + } - const char *getName() override { - return "Energy"; - } + const char *getName() override { + return "Energy"; + } + + void start() override { + mqttSubscribe(PHOTOVOLTAIC_ENERGY_KWH); + mqttSubscribe(GRID_IMPORT_WH); + mqttSubscribe(GRID_EXPORT_WH); + } + + void stop() override { + mqttUnsubscribe(PHOTOVOLTAIC_ENERGY_KWH); + mqttUnsubscribe(GRID_IMPORT_WH); + mqttUnsubscribe(GRID_EXPORT_WH); + } protected: - void tick(uint8_t index, microseconds_t microseconds) override { - page = (page + 1) % 3; + void tick(uint8_t index, microseconds_t microseconds) override { + page = (page + 1) % 3; + } + + void step(microseconds_t microseconds) override { + if (realtimeChanged) { + markDirty(); } + } - void step(microseconds_t microseconds) override { - if (realtimeChanged) { - markDirty(); - } - } - - void draw(Display &display) override { - const double produced = getPhotovoltaicEnergyKWh(); - const double imported = getGridImportKWh(); - const double exported = getGridExportKWh(); - const double producedAfterMeterChange = produced - POWER_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE; - const double selfAfterMeterChange = producedAfterMeterChange - exported; - const double selfRatio = selfAfterMeterChange / producedAfterMeterChange; - const double selfConsumedKWh = selfRatio * produced; - const double costSaved = selfConsumedKWh * GRID_KWH_EURO; - const double amortisationPercent = selfConsumedKWh / PV_COST_AMORTISATION_KWH * 100; - - const uint8_t l = (DISPLAY_CHAR_WIDTH + 1) * 4 - 1; - const uint8_t r = width; - - display.clear(); - if (page == 0) { - display.print2(l, 0, costSaved, GREEN); - uint8_t x = display.print2(r - DISPLAY_CHAR_WIDTH - 1, 0, amortisationPercent, WHITE); - display.print(x, 0, SYMBOL_PERCENT, WHITE, true); - } else if (page == 1) { - display.print2(l, 0, getPhotovoltaicEnergyKWh(), BLUE); - display.print2(r, 0, selfConsumedKWh, GREEN); - } else { - display.print2(l, 0, imported, ORANGE); - display.print2(r, 0, exported, MAGENTA); - } + void draw(Display& display) override { + const auto photovoltaicEnergyKWhAfterMeterChange = photovoltaicEnergyKWh - POWER_PHOTOVOLTAIC_photovoltaicEnergyKWh_BEFORE_METER_CHANGE; + const auto selfAfterMeterChange = photovoltaicEnergyKWhAfterMeterChange - gridExportKWh; + const auto selfRatio = selfAfterMeterChange / photovoltaicEnergyKWhAfterMeterChange; + const auto selfConsumedKWh = selfRatio * photovoltaicEnergyKWh; + const auto costSaved = selfConsumedKWh * GRID_KWH_EURO; + const auto amortisationPercent = selfConsumedKWh / PV_COST_AMORTISATION_KWH * 100; + + display.clear(); + display.cursorX = 0; + display.cursorY = 1; + if (page == 0) { + display.foreground = Green; + display.printf("%3.0f€", costSaved); + display.foreground = White; + display.printf(" %3.0f%%", amortisationPercent); + } else if (page == 1) { + display.foreground = Blue; + display.printf("%3.0f", photovoltaicEnergyKWh); + display.foreground = Green; + display.printf(" %3.0f", selfConsumedKWh); + } else { + display.foreground = Yellow; + display.printf("%4.0f", gridImportKWh); + display.foreground = Magenta; + display.printf(" %3.0f", gridExportKWh); } + } }; diff --git a/src/mode/GameOfLife/Cell.h b/src/mode/GameOfLife/Cell.h index 3c48a33..0c90df3 100644 --- a/src/mode/GameOfLife/Cell.h +++ b/src/mode/GameOfLife/Cell.h @@ -11,9 +11,9 @@ public: double fade = 0.0; - Color color = BLACK; + RGBA color = Black; - void animate(microseconds_t microseconds) { + void animate(const microseconds_t microseconds) { // TODO fading does not work as expected if (alive) { fade = doStep(fade, 0.0, 255.0, 200, +microseconds); @@ -26,40 +26,33 @@ public: 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 static_cast((fade - 128) * 2.0 + 1); } + if (fade < 128) { + return static_cast(fade * 2.0 + 1); + } + return 255; } uint8_t getG() const { if (alive) { 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); + return static_cast(fade * 2.0 + 1); } + return 255; } + if (fade < 128) { + return 0; + } + return static_cast((fade - 128) * 2.0 + 1); } uint8_t getB() const { if (fade < 128) { return 0; - } else { - return (uint8_t) ((fade - 128) * 2.0 + 1); } + return static_cast((fade - 128) * 2.0 + 1); } }; diff --git a/src/mode/GameOfLife/GameOfLife.h b/src/mode/GameOfLife/GameOfLife.h index cfa4cec..5e64c27 100644 --- a/src/mode/GameOfLife/GameOfLife.h +++ b/src/mode/GameOfLife/GameOfLife.h @@ -1,209 +1,207 @@ #ifndef MODE_GAME_OF_LIFE_H #define MODE_GAME_OF_LIFE_H -#include "mode/Mode.h" #include "Cell.h" +#include "mode/Mode.h" enum ColorMode { - BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR + BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR }; -class GameOfLife : public Mode { +class GameOfLife final : public Mode { -private: + ColorMode colorMode; - ColorMode colorMode; + size_t cellsSize; - size_t cellsSize; + Cell *cells; - Cell *cells; + Cell *cellsEnd; - Cell *cellsEnd; + Cell *next; - Cell *next; + microseconds_t runtime = 0; - microseconds_t runtime = 0; + uint8_t steps = 0; - uint8_t steps = 0; + uint16_t aliveCount = 0; - uint16_t aliveCount = 0; + uint16_t lastAliveCount = 0; - uint16_t lastAliveCount = 0; - - uint8_t isSteadyCount = 0; + uint8_t isSteadyCount = 0; public: - explicit GameOfLife(Display &display, ColorMode colorMode) : - Mode(display), - colorMode(colorMode), - cellsSize(display.pixelCount * sizeof(Cell)) { - cells = (Cell *) malloc(cellsSize); - cellsEnd = cells + display.pixelCount; - for (Cell *cell = cells; cell < cells + display.pixelCount; cell++) { - kill(cell); - } - next = (Cell *) malloc(cellsSize); - for (Cell *cell = next; cell < next + display.pixelCount; cell++) { - kill(cell); - } + explicit GameOfLife(Display& display, const ColorMode colorMode) : Mode(display), + colorMode(colorMode), + cellsSize(display.pixelCount * sizeof(Cell)) { + cells = static_cast(malloc(cellsSize)); + cellsEnd = cells + display.pixelCount; + for (auto cell = cells; cell < cells + display.pixelCount; cell++) { + kill(cell); } + next = static_cast(malloc(cellsSize)); + for (auto 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; - } + ~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 "???"; + 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 "???"; + } protected: - void step(microseconds_t microseconds) override { - runtime += microseconds; - if (runtime >= 500000) { - runtime = 0; - if (lastAliveCount == aliveCount) { - isSteadyCount++; - } else { - isSteadyCount = 0; - } - lastAliveCount = aliveCount; - if (steps++ == 0 || aliveCount == 0 || isSteadyCount >= 15) { - randomFill(); - } else { - nextGeneration(); - } + void step(const microseconds_t microseconds) override { + runtime += microseconds; + if (runtime >= 500000) { + runtime = 0; + if (lastAliveCount == aliveCount) { + isSteadyCount++; + } else { + isSteadyCount = 0; } - for (Cell *cell = cells; cell < cellsEnd; cell++) { - cell->animate(microseconds); - } - - // 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++; - } + lastAliveCount = aliveCount; + if (steps++ == 0 || aliveCount == 0 || isSteadyCount >= 15) { + randomFill(); + } else { + nextGeneration(); } } + for (auto cell = cells; cell < cellsEnd; cell++) { + cell->animate(microseconds); + } + + // TODO don't always markDirty. Be more efficient + markDirty(); + } + + void draw(Display& display) override { + auto cell = cells; + display.clear(); + for (auto y = 0; y < height; y++) { + for (auto x = 0; x < width; x++) { + uint8_t brightness; + switch (colorMode) { + case BLACK_WHITE: + brightness = cell->alive ? 255 : 0; + display.setPixel(x, y, RGBA::gray(brightness)); + break; + case GRAYSCALE: + brightness = static_cast(cell->fade); + display.setPixel(x, y, RGBA::gray(brightness)); + break; + case COLOR_FADE: + display.setPixel(x, y, {cell->getR(), cell->getG(), cell->getB()}); + break; + case RANDOM_COLOR: + display.setPixel(x, y, cell->alive ? cell->color : Black); + break; + } + cell++; + } + } + } private: - void randomFill() { - isSteadyCount = 0; - for (Cell *cell = cells; cell < cellsEnd; cell++) { - if (random(4) == 0) { - if (!cell->alive) { - spawn(cell); - } - } else { - if (cell->alive) { - kill(cell); - } + void randomFill() { + isSteadyCount = 0; + for (auto cell = cells; cell < cellsEnd; 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 spawn(Cell *cell) { + cell->color = RGBA::rnd(255); + cell->alive = true; + aliveCount++; + } - void kill(Cell *cell) { - cell->alive = false; - aliveCount--; - } + void kill(Cell *cell) { + cell->alive = false; + aliveCount--; + } - void nextGeneration() { - memcpy(next, cells, cellsSize); - Cell *src = cells; - Cell *dst = next; - 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) { - kill(dst); - } - } else if (around == 3) { - spawn(dst); + void nextGeneration() { + memcpy(next, cells, cellsSize); + auto src = cells; + auto dst = next; + for (auto y = 0; y < height; y++) { + for (auto x = 0; x < width; x++) { + const auto around = countAround(x, y); + if (src->alive) { + if (around <= 2 || 6 <= around) { + kill(dst); } - src++; - dst++; + } else if (around == 3) { + spawn(dst); } - } - memcpy(cells, next, cellsSize); - } - - void print() { - Cell *cell = cells; - for (int y = 0; y < height; y++) { - Serial.print("|"); - for (int x = 0; x < width; x++) { - Serial.print(cell->alive ? "x|" : " |"); - cell++; - } - Serial.println(); - Serial.println(); + src++; + dst++; } } + memcpy(cells, next, cellsSize); + } - 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 >= width || y >= height) { - return 0; + void print() const { + auto cell = cells; + for (auto y = 0; y < height; y++) { + Serial.print("|"); + for (auto x = 0; x < width; x++) { + Serial.print(cell->alive ? "x|" : " |"); + cell++; } - return get(x, y)->alive ? 1 : 0; + Serial.println(); + Serial.println(); } + } - Cell *get(int x, int y) { - return cells + width * y + x; + uint8_t countAround(const int x, const int y) const { + 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(const int x, const int y) const { + if (x < 0 || y < 0 || x >= width || y >= height) { + return 0; } + return get(x, y)->alive ? 1 : 0; + } + + Cell *get(const int x, const int y) const { + return cells + width * y + x; + } }; diff --git a/src/mode/Matrix/Matrix.h b/src/mode/Matrix/Matrix.h index b2af1a4..2767565 100644 --- a/src/mode/Matrix/Matrix.h +++ b/src/mode/Matrix/Matrix.h @@ -3,62 +3,59 @@ #include "mode/Mode.h" -class Matrix : public Mode { +class Matrix final : public Mode { -private: + struct Glyph { + double x = 0; + double y = 0; + double velocity = 0; + uint8_t length = 0; + }; - struct Glyph { - double x; - double y; - double velocity = 0; - uint8_t length = 0; - }; - - Glyph glyphs[10]; + Glyph glyphs[10]; public: - explicit Matrix(Display &display) : - Mode(display) { - for (auto &glyph: glyphs) { - resetGlyph(glyph); - } + explicit Matrix(Display& display) : Mode(display) { + for (auto& glyph: glyphs) { + resetGlyph(glyph); } + } - void resetGlyph(Glyph &glyph) const { - glyph.x = random(width); - glyph.y = 0; - glyph.velocity = (random(20) + 5); - glyph.length = random(8) + 2; - } + void resetGlyph(Glyph& glyph) const { + glyph.x = random(width); + glyph.y = 0; + glyph.velocity = random(20) + 5; + glyph.length = random(8) + 2; + } - const char *getName() override { - return "Matrix"; - } + const char *getName() override { + return "Matrix"; + } protected: - void step(microseconds_t microseconds) override { - for (auto &glyph: glyphs) { - glyph.y += glyph.velocity * (double) microseconds / 1000000.0; - if (glyph.y - glyph.length >= height) { - resetGlyph(glyph); - } + void step(const microseconds_t microseconds) override { + for (auto& glyph: glyphs) { + glyph.y += glyph.velocity * static_cast(microseconds) / 1000000.0; + if (glyph.y - glyph.length >= height) { + resetGlyph(glyph); } - markDirty(); } + markDirty(); + } - void draw(Display &display) override { - display.clear(); - for (auto &glyph: glyphs) { - for (int i = 0; i < glyph.length; ++i) { - display.set((int) round(glyph.x), (int) round(glyph.y - i), {64, 128, 64}); - } - if (((int) round(glyph.y) + glyph.length) % 2 == 0) { - display.set((int) round(glyph.x), (int) round(glyph.y), {0, 255, 0}); - } + void draw(Display& display) override { + display.clear(); + for (const auto& glyph: glyphs) { + for (auto i = 0; i < glyph.length; ++i) { + display.setPixel(static_cast(round(glyph.x)), static_cast(round(glyph.y - i)), {64, 128, 64}); + } + if ((static_cast(round(glyph.y)) + glyph.length) % 2 == 0) { + display.setPixel(static_cast(round(glyph.x)), static_cast(round(glyph.y)), {0, 255, 0}); } } + } }; diff --git a/src/mode/Mode.h b/src/mode/Mode.h index 8ae8ca3..31b16d4 100644 --- a/src/mode/Mode.h +++ b/src/mode/Mode.h @@ -1,8 +1,8 @@ #ifndef MODE_H #define MODE_H -#include "BASICS.h" -#include "display/Display.h" +#include +#include #define FAKE_DAYS 0 #define FAKE_HOURS 0 @@ -10,155 +10,156 @@ #define FAKE_SECONDS 0 enum ModeId { - NONE, - BORDER, - CLOCK, - GAME_OF_LIFE_BLACK_WHITE, - GAME_OF_LIFE_GRAYSCALE, - GAME_OF_LIFE_COLOR_FADE, - GAME_OF_LIFE_RANDOM_COLOR, - PONG, - SPACE_INVADERS, - COUNT_DOWN, - COUNT_DOWN_BARS, - COUNT_DOWN_SLEEP, - STARFIELD, - MATRIX, - POWER, - ENERGY, - TIMER, + NONE, + BORDER, + CLOCK, + GAME_OF_LIFE_BLACK_WHITE, + GAME_OF_LIFE_GRAYSCALE, + GAME_OF_LIFE_COLOR_FADE, + GAME_OF_LIFE_RANDOM_COLOR, + PONG, + SPACE_INVADERS, + COUNT_DOWN, + COUNT_DOWN_BARS, + COUNT_DOWN_SLEEP, + STARFIELD, + MATRIX, + POWER, + ENERGY, + TIMER, }; class Mode { - struct Timer { - microseconds_t interval; - microseconds_t rest; - }; + struct Timer { + microseconds_t interval; + microseconds_t rest; + }; - Display &_display; + Display& _display; - bool dirty = true; + bool dirty = true; - Timer timers[2] = { - {0, 0}, - {0, 0}, - }; + Timer timers[2] = { + {0, 0}, + {0, 0}, + }; - int8_t lastSecond = -1; + int8_t lastSecond = -1; - milliseconds_t lastSecondChange_Milliseconds = 0; + milliseconds_t lastSecondChange_Milliseconds = 0; protected: - const uint8_t width; + const uint8_t width; - const uint8_t height; + const uint8_t height; - bool realtimeOK = false; + bool realtimeOK = false; - bool realtimeChanged = false; + bool realtimeChanged = false; - tm now = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + tm now = {0, 0, 0, 0, 0, 0, 0, 0, 0}; - milliseconds_t realtimeMilliseconds = 0; + milliseconds_t realtimeMilliseconds = 0; - time_t nowEpochSeconds = 0; + time_t nowEpochSeconds = 0; - virtual void tick(uint8_t index, microseconds_t microseconds) {}; + virtual void tick(uint8_t index, microseconds_t microseconds) {} - virtual void step(microseconds_t microseconds) {}; + virtual void step(const microseconds_t microseconds) {} - virtual void draw(Display &display) {}; + virtual void draw(Display& display) {} - void timer(uint8_t index, milliseconds_t milliseconds) { - if (index >= countof(timers)) { - return; - } - timers[index].interval = milliseconds * 1000; - timers[index].rest = milliseconds * 1000; + void timer(const uint8_t index, const milliseconds_t milliseconds) { + if (index >= countof(timers)) { + return; } + timers[index].interval = milliseconds * 1000; + timers[index].rest = milliseconds * 1000; + } - void markDirty() { - dirty = true; - } + void markDirty() { + dirty = true; + } public: - explicit Mode(Display &display) : - _display(display), - width(display.width), - height(display.height) { - // nothing - } - - virtual ~Mode() = default; - - virtual const char *getName() = 0; - - virtual void move(int index, int x, int y) { - // - }; - - virtual void fire(int index) { - // - }; - - void loop(microseconds_t microseconds) { - handleRealtime(); - handleTimers(microseconds); - step(microseconds); - if (dirty) { - dirty = false; - draw(_display); - } + explicit Mode(Display& display) : _display(display), + width(display.width), + height(display.height) { + // + } + + virtual ~Mode() = default; + + virtual const char *getName() = 0; + + virtual void start() {} + + virtual void stop() {} + + virtual void move(int index, int x, int y) {} + + virtual void fire(int index) {} + + virtual void mqttMessage(const String& topic, const String& message) {} + + void loop(const microseconds_t microseconds) { + handleRealtime(); + handleTimers(microseconds); + step(microseconds); + if (dirty) { + dirty = false; + draw(_display); } + } private: - void handleRealtime() { - realtimeUpdate(); - realtimeMillisecondsUpdate(); - } + void handleRealtime() { + realtimeUpdate(); + realtimeMillisecondsUpdate(); + } - void realtimeUpdate() { - time_t tmp; - time(&tmp); - tmp += ((FAKE_DAYS * 24 + FAKE_HOURS) * 60 + FAKE_MINUTES) * 60 + FAKE_SECONDS; - realtimeOK = tmp > 1600000000; - if (realtimeOK) { - realtimeChanged = nowEpochSeconds != tmp; - if (realtimeChanged) { - nowEpochSeconds = tmp; - localtime_r(&tmp, &now); - now.tm_year += 1900; - now.tm_mon += 1; - } - } else { - realtimeChanged = false; + void realtimeUpdate() { + time_t tmp; + time(&tmp); + tmp += ((FAKE_DAYS * 24 + FAKE_HOURS) * 60 + FAKE_MINUTES) * 60 + FAKE_SECONDS; + realtimeOK = tmp > 1600000000; + if (realtimeOK) { + realtimeChanged = nowEpochSeconds != tmp; + if (realtimeChanged) { + nowEpochSeconds = tmp; + localtime_r(&tmp, &now); + now.tm_year += 1900; + now.tm_mon += 1; } + } else { + realtimeChanged = false; } + } - void realtimeMillisecondsUpdate() { - if (lastSecond < 0 || lastSecond != now.tm_sec) { - lastSecond = (int8_t) now.tm_sec; - lastSecondChange_Milliseconds = millis(); - } - realtimeMilliseconds = millis() - lastSecondChange_Milliseconds; + void realtimeMillisecondsUpdate() { + if (lastSecond < 0 || lastSecond != now.tm_sec) { + lastSecond = (int8_t) now.tm_sec; + lastSecondChange_Milliseconds = millis(); } + realtimeMilliseconds = millis() - lastSecondChange_Milliseconds; + } - void handleTimers(microseconds_t microseconds) { - for (Timer *timer = timers; timer < timers + countof(timers); timer++) { - if (timer->interval > 0) { - if (microseconds >= timer->rest) { - timer->rest = timer->interval; - tick(timer - timers, timer->interval); - } else { - timer->rest -= microseconds; - } + void handleTimers(const microseconds_t microseconds) { + for (Timer *timer = timers; timer < timers + countof(timers); timer++) { + if (timer->interval > 0) { + if (microseconds >= timer->rest) { + timer->rest = timer->interval; + tick(timer - timers, timer->interval); + } else { + timer->rest -= microseconds; } } } + } }; diff --git a/src/mode/Pong/Player.h b/src/mode/Pong/Player.h index 6687d2c..43266d2 100644 --- a/src/mode/Pong/Player.h +++ b/src/mode/Pong/Player.h @@ -17,7 +17,7 @@ public: bool random = true; - void randomMove(uint8_t height) { + void randomMove(const uint8_t height) { if (moveUp) { y--; if (y <= 0 || randomBool(20)) { diff --git a/src/mode/Pong/Pong.h b/src/mode/Pong/Pong.h index cae5325..523a9ca 100644 --- a/src/mode/Pong/Pong.h +++ b/src/mode/Pong/Pong.h @@ -1,193 +1,199 @@ #ifndef MODE_PONG_H #define MODE_PONG_H -#include "mode/Mode.h" #include "Player.h" -#include "display/Vector.h" +#include "Vector.h" +#include "mode/Mode.h" -class Pong : public Mode { +class Pong final : public Mode { -private: + enum Status { + SCORE, PLAY, OVER + }; - enum Status { - SCORE, PLAY, OVER - }; + Player player0; - Player player0; + Player player1; - Player player1; + Vector ball; - Vector ball; + Vector velocity; - Vector velocity; + Status status = PLAY; - Status status = PLAY; - - microseconds_t timeoutMicroseconds = 0; + microseconds_t timeoutMicroseconds = 0; public: - explicit Pong(Display &display) : - Mode(display), - ball(width / 2.0, height / 2.0), - velocity(Vector::polar(random(360), exp10(1))) { - timer(0, 100); - spawnBall(random(2) == 0 ? -1 : +1); - resetPlayer(); - } + explicit Pong(Display& display) : Mode(display), + ball(width / 2.0, height / 2.0), + velocity(Vector::polar(random(360), exp10(1))) { + timer(0, 100); + spawnBall(random(2) == 0 ? -1 : +1); + resetPlayer(); + } - const char *getName() override { - return "Pong"; - } + const char *getName() override { + return "Pong"; + } - void move(int index, int x, int y) override { - if (index == 0) { - player0.random = false; - player0.y = min(height - player0.size, max(0, player0.y + y)); - } else if (index == 1) { - player1.random = false; - player1.y = min(height - player1.size, max(0, player1.y + y)); - } + void move(const int index, int x, const int y) override { + if (index == 0) { + player0.random = false; + player0.y = min(height - player0.size, max(0, player0.y + y)); + } else if (index == 1) { + player1.random = false; + player1.y = min(height - player1.size, max(0, player1.y + y)); } + } - void fire(int index) override { - if (index == 0) { - player0.random = false; - } else if (index == 1) { - player1.random = false; - } + void fire(const int index) override { + if (index == 0) { + player0.random = false; + } else if (index == 1) { + player1.random = false; } + } protected: - void tick(uint8_t index, microseconds_t microseconds) override { - switch (status) { - case SCORE: - timeoutMicroseconds -= microseconds; - if (timeoutMicroseconds <= 0) { - status = PLAY; - } - break; - case PLAY: - ball = ball.plus(velocity); - if (player0.random) { - player0.randomMove(height); - } - if (player1.random) { - player1.randomMove(height); - } - topBottomBounce(); - paddleBounce(); - checkScoring(); - markDirty(); + void tick(uint8_t index, const microseconds_t microseconds) override { + switch (status) { + case SCORE: + timeoutMicroseconds -= microseconds; + if (timeoutMicroseconds <= 0) { + status = PLAY; + } + break; + case PLAY: + ball = ball.plus(velocity); + if (player0.random) { + player0.randomMove(height); + } + if (player1.random) { + player1.randomMove(height); + } + topBottomBounce(); + paddleBounce(); + checkScoring(); + markDirty(); - break; - case OVER: - timeoutMicroseconds -= microseconds; - if (timeoutMicroseconds <= 0) { - resetPlayer(); - status = PLAY; - timeoutMicroseconds = 0; - } - break; - } + break; + case OVER: + timeoutMicroseconds -= microseconds; + if (timeoutMicroseconds <= 0) { + resetPlayer(); + status = PLAY; + timeoutMicroseconds = 0; + } + break; } + } - void draw(Display &display) override { - display.clear(); - switch (status) { - case SCORE: - display.print(1, 1, player0.score, GREEN, true); - display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED, true); - break; - case PLAY: - for (int i = 0; i < player0.size; ++i) { - display.set(1, (uint8_t) round(player0.y) + i, GREEN); - } - for (int i = 0; i < player1.size; ++i) { - display.set(width - 2, (uint8_t) round(player1.y) + i, RED); - } - display.set((uint8_t) round(ball.x), (uint8_t) round(ball.y), WHITE); - break; - case OVER: - if (player0.score > player1.score) { - display.print(1, 1, 11, GREEN, true); - display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED, true); - } else if (player0.score < player1.score) { - display.print(1, 1, 12, RED, true); - display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN, true); - } - break; - } + void draw(Display& display) override { + display.clear(); + switch (status) { + case SCORE: + display.foreground = Green; + display.printf("%d", player0.score); + + display.foreground = Red; + display.printf("%5d", player1.score); + break; + case PLAY: + for (auto i = 0; i < player0.size; ++i) { + display.setPixel(1, static_cast(round(player0.y)) + i, Green); + } + for (auto i = 0; i < player1.size; ++i) { + display.setPixel(width - 2, static_cast(round(player1.y)) + i, Red); + } + display.setPixel(static_cast(round(ball.x)), static_cast(round(ball.y)), White); + break; + case OVER: + if (player0.score > player1.score) { + display.foreground = Green; + display.printf("W", player0.score); + + display.foreground = Red; + display.printf(" L", player1.score); + } else if (player0.score < player1.score) { + display.foreground = Red; + display.printf("L", player0.score); + + display.foreground = Green; + display.printf(" W", player1.score); + } + break; } + } private: - void resetPlayer() { - player0.size = 3; - player0.score = 0; - player0.y = (height - player0.size) / 2; - player0.random = true; - player1.size = 3; - player1.score = 0; - player1.y = (height - player1.size) / 2; - player1.random = true; - } + void resetPlayer() { + player0.size = 3; + player0.score = 0; + player0.y = (height - player0.size) / 2; + player0.random = true; + player1.size = 3; + player1.score = 0; + player1.y = (height - player1.size) / 2; + player1.random = true; + } - void topBottomBounce() { - while (ball.y < 0 || ball.y >= height) { - if (ball.y < 0) { - ball.y = -ball.y; - velocity.y = -velocity.y; - } else if (ball.y >= height) { - ball.y = 2 * height - ball.y - 1; - velocity.y = -velocity.y; - } + void topBottomBounce() { + while (ball.y < 0 || ball.y >= height) { + if (ball.y < 0) { + ball.y = -ball.y; + velocity.y = -velocity.y; + } else if (ball.y >= height) { + ball.y = 2 * height - ball.y - 1; + velocity.y = -velocity.y; } } + } - void checkScoring() { - if (ball.x < 0) { - player1.score++; - Serial.println("Player 1 scored"); - spawnBall(+1); - } else if (ball.x >= width) { - player0.score++; - Serial.println("Player 0 scored"); - spawnBall(-1); - } - if (player0.score >= 10 || player1.score >= 10) { - status = OVER; - timeoutMicroseconds = 2000 * 1000; - } + void checkScoring() { + if (ball.x < 0) { + player1.score++; + Serial.println("Player 1 scored"); + spawnBall(+1); + } else if (ball.x >= width) { + player0.score++; + Serial.println("Player 0 scored"); + spawnBall(-1); } - - void paddleBounce() { - double paddleHitPosition0 = ball.y - player0.y; - if (ball.x >= 1 && ball.x < 2 && paddleHitPosition0 >= 0 && paddleHitPosition0 < player0.size) { - Serial.printf("Player 0 hit: paddleHitPosition0=%.2f\n", paddleHitPosition0); - velocity.x = -velocity.x; - velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition0 - 1)); - ball.x = 3; - return; - } - double paddleHitPosition1 = ball.y - player1.y; - if (ball.x >= width - 2 && ball.x < width - 1 && paddleHitPosition1 >= 0 && paddleHitPosition1 < player1.size) { - Serial.printf("Player 1 hit: paddleHitPosition1=%.2f\n", paddleHitPosition1); - velocity.x = -velocity.x; - velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition1 - 1)); - ball.x = width - 4; - } - } - - void spawnBall(int direction) { - ball.x = (double) width / 2.0; - ball.y = (double) height / 2.0; - velocity.x = direction; - velocity.y = 0; - status = SCORE; + if (player0.score >= 10 || player1.score >= 10) { + status = OVER; timeoutMicroseconds = 2000 * 1000; } + } + + void paddleBounce() { + const auto paddleHitPosition0 = ball.y - player0.y; + if (ball.x >= 1 && ball.x < 2 && paddleHitPosition0 >= 0 && paddleHitPosition0 < player0.size) { + Serial.printf("Player 0 hit: paddleHitPosition0=%.2f\n", paddleHitPosition0); + velocity.x = -velocity.x; + velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition0 - 1)); + ball.x = 3; + return; + } + const auto paddleHitPosition1 = ball.y - player1.y; + if (ball.x >= width - 2 && ball.x < width - 1 && paddleHitPosition1 >= 0 && paddleHitPosition1 < player1.size) { + Serial.printf("Player 1 hit: paddleHitPosition1=%.2f\n", paddleHitPosition1); + velocity.x = -velocity.x; + velocity.y = max(-2.0, min(+2.0, velocity.y + paddleHitPosition1 - 1)); + ball.x = width - 4; + } + } + + void spawnBall(const int direction) { + ball.x = static_cast(width) / 2.0; + ball.y = static_cast(height) / 2.0; + velocity.x = direction; + velocity.y = 0; + status = SCORE; + timeoutMicroseconds = 2000 * 1000; + } }; diff --git a/src/mode/Power/Power.h b/src/mode/Power/Power.h index cd0dc1c..368a3a1 100644 --- a/src/mode/Power/Power.h +++ b/src/mode/Power/Power.h @@ -1,41 +1,70 @@ #ifndef MODE_POWER_H #define MODE_POWER_H +#include #include "mode/Mode.h" -#include "mqtt.h" -#pragma clang diagnostic push -#pragma ide diagnostic ignored "UnusedValue" +#define PHOTOVOLTAIC_POWER_W "openDTU/pv/ac/power" +#define GRID_POWER_W "electricity/grid/power/signed/w" -class Power : public Mode { +class Power final : public Mode { + + double photovoltaicPowerW = NAN; + + unsigned long photovoltaicPowerWLast = 0; + + double gridPowerW = NAN; + + unsigned long gridPowerWLast = 0; public: - explicit Power(Display &display) : - Mode(display) { - // nothing - } + explicit Power(Display& display) : Mode(display) { + // nothing + } - const char *getName() override { - return "Power"; - } + const char *getName() override { + return "Power"; + } + + void start() override { + mqttSubscribe(PHOTOVOLTAIC_POWER_W); + mqttSubscribe(GRID_POWER_W); + } + + void stop() override { + mqttUnsubscribe(PHOTOVOLTAIC_POWER_W); + mqttUnsubscribe(GRID_POWER_W); + } protected: - void step(microseconds_t microseconds) override { - if (realtimeChanged) { - markDirty(); - } + void mqttMessage(const String& topic, const String& message) override { + if (topic.equals(PHOTOVOLTAIC_POWER_W)) { + photovoltaicPowerW = message.toDouble(); + photovoltaicPowerWLast = millis(); + } else if (topic.equals(GRID_POWER_W)) { + gridPowerW = message.toDouble(); + gridPowerWLast = millis(); } + } - void draw(Display &display) override { - display.clear(); - display.print2((DISPLAY_CHAR_WIDTH + 1) * 3 - 1, 0, getPhotovoltaicPowerW(), GREEN); - display.print2(width, 0, getGridPowerW(), ORANGE, WHITE, MAGENTA); + void step(microseconds_t microseconds) override { + if (realtimeChanged) { + markDirty(); } + } + + void draw(Display& display) override { + display.clear(); + + display.foreground = photovoltaicPowerW >= 100 ? Green : photovoltaicPowerW >= 20 ? Yellow : Red; + display.printf("%3.0f", photovoltaicPowerW); + + display.foreground = gridPowerW >= 20 ? Yellow : gridPowerW >= -20 ? Green : Magenta; + display.printf(" %4.0f", gridPowerW); + } }; -#pragma clang diagnostic pop - #endif diff --git a/src/mode/SpaceInvaders/SpaceInvaders.h b/src/mode/SpaceInvaders/SpaceInvaders.h index b42f574..08a44d9 100644 --- a/src/mode/SpaceInvaders/SpaceInvaders.h +++ b/src/mode/SpaceInvaders/SpaceInvaders.h @@ -6,279 +6,276 @@ #include "mode/Mode.h" struct Rocket { - bool alive; - uint16_t flash; - uint8_t x; - uint8_t y; + bool alive; + uint16_t flash; + uint8_t x; + uint8_t y; }; struct Invader { - bool alive; - uint8_t x; - uint8_t y; + bool alive; + uint8_t x; + uint8_t y; }; -class SpaceInvaders : public Mode { +class SpaceInvaders final : public Mode { -private: + microseconds_t heroRuntime = 0; + uint8_t heroX = 0; + bool heroLeft = false; + uint8_t heroShoot = 0; + bool randomEnabled = true; - microseconds_t heroRuntime = 0; - uint8_t heroX = 0; - bool heroLeft = false; - uint8_t heroShoot = 0; - bool randomEnabled = true; + uint8_t invadersCountX; + uint8_t invadersCountY; + uint8_t invadersAlive = 0; - uint8_t invadersCountX; - uint8_t invadersCountY; - uint8_t invadersAlive = 0; + microseconds_t swarmRuntime = 0; + uint8_t swarmY = 0; + bool swarmLeft = false; + bool swarmDown = false; + uint8_t swarmX = 0; + Invader *swarmBegin; + Invader *swarmEnd; - microseconds_t swarmRuntime = 0; - uint8_t swarmY = 0; - bool swarmLeft = false; - bool swarmDown = false; - uint8_t swarmX = 0; - Invader *swarmBegin; - Invader *swarmEnd; - - microseconds_t rocketRuntime = 0; - Rocket *rocketsBegin; - Rocket *rocketsEnd; + microseconds_t rocketRuntime = 0; + Rocket *rocketsBegin; + Rocket *rocketsEnd; public: - explicit SpaceInvaders(Display &display) : - Mode(display), - invadersCountX(width / 3), - invadersCountY(height / 4) { + explicit SpaceInvaders(Display& display) : Mode(display), + invadersCountX(width / 3), + invadersCountY(height / 4) { - swarmBegin = (Invader *) malloc(sizeof(Invader) * invadersCountX * invadersCountY); - swarmEnd = swarmBegin + invadersCountX * invadersCountY; + swarmBegin = static_cast(malloc(sizeof(Invader) * invadersCountX * invadersCountY)); + swarmEnd = swarmBegin + invadersCountX * invadersCountY; - rocketsBegin = (Rocket *) malloc(sizeof(Rocket) * ROCKET_MAX); - rocketsEnd = rocketsBegin + ROCKET_MAX; + rocketsBegin = static_cast(malloc(sizeof(Rocket) * ROCKET_MAX)); + rocketsEnd = rocketsBegin + ROCKET_MAX; - reset(); - } + reset(); + } - ~SpaceInvaders() override { - free(swarmBegin); - free(rocketsBegin); - }; + ~SpaceInvaders() override { + free(swarmBegin); + free(rocketsBegin); + }; - const char *getName() override { - return "Space Invaders"; - } + const char *getName() override { + return "Space Invaders"; + } - void move(int index, int x, int y) override { - randomEnabled = false; - heroX = max(1, min(width - 2, heroX + x)); - } + void move(int index, int x, int y) override { + randomEnabled = false; + heroX = max(1, min(width - 2, heroX + x)); + } - void fire(int index) override { - randomEnabled = false; - shoot(); - } + void fire(int index) override { + randomEnabled = false; + shoot(); + } protected: - void step(microseconds_t microseconds) override { - stepRockets(microseconds); - stepInvaders(microseconds); - if (randomEnabled) { - randomStepHero(microseconds); - } - - collide(); - if (invadersAlive == 0) { - Serial.println("WINNER!"); - reset(); - } - // 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 step(microseconds_t microseconds) override { + stepRockets(microseconds); + stepInvaders(microseconds); + if (randomEnabled) { + randomStepHero(microseconds); } - void draw(Display &display) override { - display.clear(); - drawInvaders(display); - drawRockets(display); - drawHero(display); + collide(); + if (invadersAlive == 0) { + Serial.println("WINNER!"); + reset(); } + // 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 microseconds) { - rocketRuntime += microseconds; - if (rocketRuntime > 200000) { - rocketRuntime = rocketRuntime % 200000; - for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { - if (rocket->alive) { - if (rocket->y == 0) { - rocket->alive = false; - } else { - rocket->y -= 1; - } - } else if (rocket->flash > 0) { - if (rocket->flash < microseconds) { - rocket->flash = 0; - } else { - rocket->flash -= microseconds; - } + void stepRockets(microseconds_t microseconds) { + rocketRuntime += microseconds; + if (rocketRuntime > 200000) { + rocketRuntime = rocketRuntime % 200000; + for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { + if (rocket->alive) { + if (rocket->y == 0) { + rocket->alive = false; + } else { + rocket->y -= 1; + } + } else if (rocket->flash > 0) { + if (rocket->flash < microseconds) { + rocket->flash = 0; + } else { + rocket->flash -= microseconds; } } } } + } - void stepInvaders(microseconds_t microseconds) { - swarmRuntime += microseconds; + void stepInvaders(microseconds_t microseconds) { + swarmRuntime += microseconds; - if (swarmDown && swarmRuntime > 500000) { - swarmDown = false; - swarmY++; - } + if (swarmDown && swarmRuntime > 500000) { + swarmDown = false; + swarmY++; + } - if (swarmRuntime >= 1000000) { - swarmRuntime = swarmRuntime % 1000000; - if (swarmLeft) { - swarmX--; - if (swarmX == 0) { - swarmLeft = false; - } - } else { - swarmX++; - if (swarmX == 3) { - swarmLeft = true; - } - } + if (swarmRuntime >= 1000000) { + swarmRuntime = swarmRuntime % 1000000; + if (swarmLeft) { + swarmX--; if (swarmX == 0) { - swarmDown = true; + swarmLeft = false; + } + } else { + swarmX++; + if (swarmX == 3) { + swarmLeft = true; } } - } - - void randomStepHero(microseconds_t microseconds) { - heroRuntime += microseconds; - if (heroRuntime >= 50000) { - heroRuntime = heroRuntime % 50000; - if (heroLeft) { - heroX--; - if (heroX <= 1 || randomBool(20)) { - heroLeft = false; - } - } else { - heroX++; - if (heroX >= width - 2 || randomBool(20)) { - heroLeft = true; - } - } - - heroShoot++; - if (heroShoot >= 20 || randomBool(5)) { - heroShoot = 0; - shoot(); - } + if (swarmX == 0) { + swarmDown = true; } } + } - void shoot() { - for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { - if (!rocket->alive && rocket->flash == 0) { - rocket->alive = true; - rocket->x = heroX; - rocket->y = height - 2; + void randomStepHero(microseconds_t microseconds) { + heroRuntime += microseconds; + if (heroRuntime >= 50000) { + heroRuntime = heroRuntime % 50000; + if (heroLeft) { + heroX--; + if (heroX <= 1 || randomBool(20)) { + heroLeft = false; + } + } else { + heroX++; + if (heroX >= width - 2 || randomBool(20)) { + heroLeft = true; } } - } - void collide() { - for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { - if (!rocket->alive) { + heroShoot++; + if (heroShoot >= 20 || randomBool(5)) { + heroShoot = 0; + shoot(); + } + } + } + + void shoot() { + for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { + if (!rocket->alive && rocket->flash == 0) { + rocket->alive = true; + rocket->x = heroX; + rocket->y = height - 2; + } + } + } + + void collide() { + for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { + if (!rocket->alive) { + continue; + } + for (auto invader = swarmBegin; invader < swarmEnd; invader++) { + if (!invader->alive) { continue; } - for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) { - if (!invader->alive) { - continue; - } - if (collide(rocket, invader)) { - rocket->alive = false; - rocket->flash = 1000; - invader->alive = false; - invadersAlive--; - break; - } + if (collide(rocket, invader)) { + rocket->alive = false; + rocket->flash = 1000; + invader->alive = false; + invadersAlive--; + break; } } } + } - bool collide(const Rocket *rocket, const Invader *invader) const { - return swarmY + invader->y * 2 == rocket->y - && swarmX + invader->x * 3 <= rocket->x - && swarmX + invader->x * 3 + 1 >= rocket->x; - } + bool collide(const Rocket *rocket, const Invader *invader) const { + return swarmY + invader->y * 2 == rocket->y + && swarmX + invader->x * 3 <= rocket->x + && swarmX + invader->x * 3 + 1 >= rocket->x; + } - 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); - } + void drawInvaders(Display& display) { + for (auto invader = swarmBegin; invader < swarmEnd; invader++) { + if (invader->alive) { + display.setPixel(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, Red); + display.setPixel(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, Red); } } + } - void drawRockets(Display &display) { - for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { - if (rocket->alive) { - 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)); - } + void drawRockets(Display& display) { + for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { + if (rocket->alive) { + display.setPixel(rocket->x, rocket->y, Yellow); + } else if (rocket->flash > 0) { + display.setPixel(rocket->x - 1, rocket->y - 1, RGBA::gray(rocket->flash)); + display.setPixel(rocket->x - 1, rocket->y + 1, RGBA::gray(rocket->flash)); + display.setPixel(rocket->x + 0, rocket->y + 0, RGBA::gray(rocket->flash)); + display.setPixel(rocket->x + 1, rocket->y + 1, RGBA::gray(rocket->flash)); + display.setPixel(rocket->x + 1, rocket->y - 1, RGBA::gray(rocket->flash)); } } + } - 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 drawHero(Display& display) { + display.setPixel(heroX - 1, height - 1, Blue); + display.setPixel(heroX + 0, height - 1, Blue); + display.setPixel(heroX + 1, height - 1, Blue); + } + + void reset() { + heroRuntime = 0; + heroLeft = false; + heroShoot = 0; + heroX = width / 2; + randomEnabled = true; + + rocketRuntime = 0; + for (auto rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { + rocket->alive = false; + rocket->flash = 0; + rocket->x = 0; + rocket->y = 0; } - void reset() { - heroRuntime = 0; - heroLeft = false; - heroShoot = 0; - heroX = width / 2; - randomEnabled = true; - - rocketRuntime = 0; - for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) { - rocket->alive = false; - rocket->flash = 0; - rocket->x = 0; - rocket->y = 0; - } - - swarmRuntime = 0; - invadersAlive = invadersCountX * invadersCountY; - swarmX = 0; - swarmY = 0; - swarmLeft = false; - swarmDown = false; - uint8_t n = 0; - for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) { - invader->alive = true; - invader->x = n % invadersCountX; - invader->y = n / invadersCountX; - n++; - } + swarmRuntime = 0; + invadersAlive = invadersCountX * invadersCountY; + swarmX = 0; + swarmY = 0; + swarmLeft = false; + swarmDown = false; + uint8_t n = 0; + for (auto invader = swarmBegin; invader < swarmEnd; invader++) { + invader->alive = true; + invader->x = n % invadersCountX; + invader->y = n / invadersCountX; + n++; } + } }; diff --git a/src/mode/Starfield/Starfield.h b/src/mode/Starfield/Starfield.h index a538262..70fd28c 100644 --- a/src/mode/Starfield/Starfield.h +++ b/src/mode/Starfield/Starfield.h @@ -3,75 +3,73 @@ #include "mode/Mode.h" -class Starfield : public Mode { +class Starfield final : public Mode { -private: + Vector center; - Vector center; + Vector centerNext; - Vector centerNext; - - Vector stars[20]; + Vector stars[20]; public: - explicit Starfield(Display &display) : - Mode(display), - center(width / 2.0, height / 2.0), - centerNext(center.x, center.y) { - for (auto &star: stars) { - star.x = random(width); - star.y = random(height); - } + explicit Starfield(Display& display) : Mode(display), + center(width / 2.0, height / 2.0), + centerNext(center.x, center.y) { + for (auto& star: stars) { + star.x = random(width); + star.y = random(height); } + } - const char *getName() override { - return "Starfield"; - } + const char *getName() override { + return "Starfield"; + } protected: - void step(microseconds_t microseconds) override { - stepCenter(); - for (auto &star: stars) { - const Vector velocity = star.minus(center).multiply((double) microseconds / 200000.0); - star = star.plus(velocity); - if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) { - star = center.plus(Vector::polar(random(360), 1)); - } - } - - // TODO don't always markDirty. Be more efficient - markDirty(); - } - - void stepCenter() { - // TODO moving center overtakes moving stars (less stars in direction of moving center) -// Vector diff = centerNext.minus(center); -// if (diff.length < 0.01) { -// centerNext = Vector(random(width), random(height)); -// } else { -// if (diff.x >= 0) { -// center.x += min(0.1, diff.x); -// } else { -// center.x += max(-0.1, diff.x); -// } -// if (diff.y >= 0) { -// center.y += min(0.1, diff.y); -// } else { -// center.y += max(-0.1, diff.y); -// } -// } - } - - 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)); + void step(const microseconds_t microseconds) override { + stepCenter(); + for (auto& star: stars) { + const auto velocity = star.minus(center).multiply(static_cast(microseconds) / 200000.0); + star = star.plus(velocity); + if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) { + star = center.plus(Vector::polar(random(360), 1)); } } + // TODO don't always markDirty. Be more efficient + markDirty(); + } + + // ReSharper disable once CppMemberFunctionMayBeStatic + void stepCenter() { + // TODO moving center overtakes moving stars (less stars in direction of moving center) + // Vector diff = centerNext.minus(center); + // if (diff.length < 0.01) { + // centerNext = Vector(random(width), random(height)); + // } else { + // if (diff.x >= 0) { + // center.x += min(0.1, diff.x); + // } else { + // center.x += max(-0.1, diff.x); + // } + // if (diff.y >= 0) { + // center.y += min(0.1, diff.y); + // } else { + // center.y += max(-0.1, diff.y); + // } + // } + } + + void draw(Display& display) override { + display.clear(); + for (auto& star: stars) { + const auto brightness = static_cast(round(255.0 * star.minus(center).length / (width / 2.0))); + display.setPixel(star.x, star.y, RGBA::gray(brightness)); + } + } + }; #endif diff --git a/src/mode/Timer/Timer.h b/src/mode/Timer/Timer.h index 7af69a3..60dbb3d 100644 --- a/src/mode/Timer/Timer.h +++ b/src/mode/Timer/Timer.h @@ -41,73 +41,17 @@ protected: void draw(Display& display) override { display.clear(); - - if (diffSeconds == 0) { - drawNoTime(display); - return; - } - - uint8_t x = 0; - if (days > 0) { - drawDay(display, days, &x); - x += display.print(x, 1, 10, WHITE, true); + if (days > 1) { + display.printf("%4d Tage", days); + } else if (days > 0) { + display.printf("%2d. %02d:%02d", days, hours, minutes); + } else if (hours > 0) { + display.printf("%2d:%02d:%02d", hours, minutes, seconds); + } else if (minutes > 0) { + display.printf("%2d:%02d", minutes, seconds); } else { - x += 2; + display.printf("%2d", seconds); } - drawHour(display, days, hours, &x); - x += display.print(x, 1, 10, WHITE, true); - draw2Digit(display, minutes, &x); - if (days <= 0) { - x += display.print(x, 1, 10, WHITE, true); - draw2Digit(display, seconds, &x); - } - } - -private: - - static void drawNoTime(Display& display) { - uint8_t x = 2; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x += display.print(x, 1, 10, WHITE, true); - x += display.print(x, 1, SYMBOL_DASH, WHITE, true); - x++; - display.print(x, 1, SYMBOL_DASH, WHITE, true); - } - - static void drawDay(Display& display, int days, uint8_t *x) { - if (days >= 100) { - *x += display.print(*x, 1, days / 100, WHITE, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - - if (days >= 10) { - *x += display.print(*x, 1, days / 10 % 10, WHITE, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - - *x += display.print(*x, 1, days % 10, WHITE, true); - } - - 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, true) + 1; - } else { - *x += DISPLAY_CHAR_WIDTH + 1; - } - *x += display.print(*x, 1, hours % 10, WHITE, true); - } - - static void draw2Digit(Display& display, int value, uint8_t *x) { - *x += display.print(*x, 1, value / 10, WHITE, true) + 1; - *x += display.print(*x, 1, value % 10, WHITE, true); } };