diff --git a/CMakeLists.txt b/CMakeLists.txt index 53dc743..87ef223 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,4 +30,4 @@ add_custom_target( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) -add_executable(Z_DUMMY_TARGET ${SRC_LIST} src/mode/Test/Border.h src/mode/Clock/Clock.h src/mode/SpaceInvaders/SpaceInvaders.h) +add_executable(Z_DUMMY_TARGET ${SRC_LIST} src/mode/Test/Border.h src/mode/Clock/Clock.h src/mode/SpaceInvaders/SpaceInvaders.h src/mode/Timer.h src/mode/Pong/Pong.cpp src/BASICS.cpp src/display/Display.cpp) diff --git a/platformio.ini b/platformio.ini index f046059..f8f6fcf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,7 +13,9 @@ platform = espressif32 board = esp32dev framework = arduino lib_deps = https://github.com/adafruit/Adafruit_NeoPixel -upload_port = /dev/ttyUSB0 -upload_speed = 921600 +upload_port = 10.0.0.120 +upload_protocol = espota +;upload_port = /dev/ttyUSB0 +;upload_speed = 921600 monitor_port = /dev/ttyUSB0 monitor_speed = 115200 diff --git a/src/BASICS.cpp b/src/BASICS.cpp new file mode 100644 index 0000000..d87542d --- /dev/null +++ b/src/BASICS.cpp @@ -0,0 +1,23 @@ +#include "BASICS.h" + +double step(double valueCurrent, double valueMin, double valueMax, microseconds_t timeTotal, microseconds_t timeDelta) { + double valueRange = valueMax - valueMin; + double timeRatio = (double) timeDelta / (double) timeTotal; + double valueStep = valueRange * timeRatio; + double valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep)); +// Serial.printf("valueCurrent: %13.3f\n", valueCurrent); +// Serial.printf("valueMin: %13.3f\n", valueMin); +// Serial.printf("valueMax: %13.3f\n", valueMax); +// Serial.printf("valueRange: %13.3f\n", valueRange); +// Serial.printf("timeTotal: %13.3f\n", (double) timeTotal); +// Serial.printf("timeDelta: %13.3f\n", (double) timeDelta); +// Serial.printf("timeRatio: %13.3f\n", timeRatio); +// Serial.printf("valueStep: %13.3f\n", valueStep); +// Serial.printf("valueNew: %13.3f\n", valueNew); +// Serial.println(); + return valueNew; +} + +bool randomBool(int uncertainty) { + return random(uncertainty) == 0; +} diff --git a/src/BASICS.h b/src/BASICS.h index de1af91..e18f917 100644 --- a/src/BASICS.h +++ b/src/BASICS.h @@ -8,26 +8,8 @@ typedef int64_t microseconds_t; -double step(double valueCurrent, double valueMin, double valueMax, microseconds_t timeTotal, microseconds_t timeDelta) { - double valueRange = valueMax - valueMin; - double timeRatio = (double) timeDelta / (double) timeTotal; - double valueStep = valueRange * timeRatio; - double valueNew = max(valueMin, min(valueMax, valueCurrent + valueStep)); -// Serial.printf("valueCurrent: %13.3f\n", valueCurrent); -// Serial.printf("valueMin: %13.3f\n", valueMin); -// Serial.printf("valueMax: %13.3f\n", valueMax); -// Serial.printf("valueRange: %13.3f\n", valueRange); -// Serial.printf("timeTotal: %13.3f\n", (double) timeTotal); -// Serial.printf("timeDelta: %13.3f\n", (double) timeDelta); -// Serial.printf("timeRatio: %13.3f\n", timeRatio); -// Serial.printf("valueStep: %13.3f\n", valueStep); -// Serial.printf("valueNew: %13.3f\n", valueNew); -// Serial.println(); - return valueNew; -} +double step(double valueCurrent, double valueMin, double valueMax, microseconds_t timeTotal, microseconds_t timeDelta); -bool randomBool(int uncertainty) { - return random(uncertainty) == 0; -} +bool randomBool(int uncertainty); #endif diff --git a/src/display/Display.cpp b/src/display/Display.cpp new file mode 100644 index 0000000..a46c5be --- /dev/null +++ b/src/display/Display.cpp @@ -0,0 +1,95 @@ +#include "Display.h" + +bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT] = { + { + X, X, X, + X, _, X, + X, _, X, + X, _, X, + X, X, X, + }, + { + _, _, X, + _, X, X, + _, _, X, + _, _, X, + _, _, X, + }, + { + X, X, X, + _, _, X, + X, X, X, + X, _, _, + X, X, X, + }, + { + X, X, X, + _, _, X, + _, X, X, + _, _, X, + X, X, X, + }, + { + X, _, X, + X, _, X, + X, X, X, + _, _, X, + _, _, X, + }, + { + X, X, X, + X, _, _, + X, X, X, + _, _, X, + X, X, X, + }, + { + X, X, X, + X, _, _, + X, X, X, + X, _, X, + X, X, X, + }, + { + X, X, X, + _, _, X, + _, X, _, + X, _, _, + X, _, _, + }, + { + X, X, X, + X, _, X, + X, X, X, + X, _, X, + X, X, X, + }, + { + X, X, X, + X, _, X, + X, X, X, + _, _, X, + X, X, X, + }, + { + _, _, _, + _, X, _, + _, _, _, + _, X, _, + _, _, _, + }, + { + _, _, X, + _, _, X, + _, _, X, + X, _, X, + _, X, _, + }, + { + X, _, X, + X, X, X, + _, X, _, + X, X, X, + X, _, X, + }, +}; diff --git a/src/display/Display.h b/src/display/Display.h index cddbca4..5045dbe 100644 --- a/src/display/Display.h +++ b/src/display/Display.h @@ -4,6 +4,12 @@ #include "Pixel.h" #include "Adafruit_NeoPixel.h" +#define SYMBOL_COUNT 13 +#define DISPLAY_CHAR_WIDTH 3 +#define DISPLAY_CHAR_HEIGHT 5 + +extern bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT]; + class Display { public: @@ -33,6 +39,36 @@ public: clear(); } + void print(uint8_t xPos, uint8_t yPos, uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + print(&xPos, yPos, index, r, g, b); + } + + void print(uint8_t xPos, uint8_t yPos, uint8_t index) { + print(&xPos, yPos, index, 255, 255, 255); + } + + void print(uint8_t *xPos, uint8_t yPos, uint8_t index) { + print(xPos, yPos, index, 255, 255, 255); + } + + void print(uint8_t *xPos, uint8_t yPos, uint8_t index, uint8_t r, uint8_t g, uint8_t b) { + if (index >= SYMBOL_COUNT) { + Serial.printf("Cannot print symbol #%u.\n", index); + return; + } + bool *symbolBit = SYMBOLS[index]; + for (uint8_t y = 0; y < DISPLAY_CHAR_HEIGHT; ++y) { + for (uint8_t x = 0; x < DISPLAY_CHAR_WIDTH; ++x) { + if (*(symbolBit++)) { + set(*xPos + x, yPos + y, r, g, b); + } else { + set(*xPos + x, yPos + y, 0, 0, 0); + } + } + } + *xPos += 3; + } + void set(uint8_t x, uint8_t y, uint32_t color) { setPixelColor(x, y, color); } @@ -67,6 +103,10 @@ public: return leds.getBrightness(); } + void setIndex(uint16_t index, uint8_t r, uint8_t g, uint8_t b) { + leds.setPixelColor(index, r, g, b); + } + }; #endif diff --git a/src/display/Vector.h b/src/display/Vector.h index 26378a0..abc2ba6 100644 --- a/src/display/Vector.h +++ b/src/display/Vector.h @@ -22,6 +22,30 @@ public: y = sin(radians) * length; } + Vector *add(Vector that) { + this->x += that.x; + this->y += that.y; + return this; + } + + Vector *bounceInBox(uint8_t width, uint8_t height) { + while (x < 0 || x >= width) { + if (x < 0) { + x = -x; + } else if (x >= width) { + x = 2 * width - x; + } + } + while (y < 0 || y >= height) { + if (y < 0) { + y = -y; + } else if (y >= height) { + y = 2 * height - y; + } + } + return this; + } + }; #endif diff --git a/src/main.cpp b/src/main.cpp index 3a3b66a..ecb2cd5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ enum ModeId { Display display(32, 8); -ModeId newModeId = SPACE_INVADERS; +ModeId newModeId = PONG; ModeId currentModeId = NONE; @@ -30,7 +30,7 @@ microseconds_t lastMicros = 0; Mode *mode = nullptr; -double speed = 4.0; +double speed = 1.0; bool connected = false; @@ -49,6 +49,26 @@ void setup() { Serial.begin(115200); Serial.println("\n\n\nStartup!"); WiFi.begin("HappyNet", "1Grausame!Sackratte7"); + ArduinoOTA.onStart([]() { + display.clear(); + display.loop(); + }); + ArduinoOTA.onProgress([](unsigned int total, unsigned int progress) { + double ratio = (double) progress / (double) total; + auto index = (uint16_t) round(ratio * (double) display.pixelCount); + auto color = (uint8_t) round(ratio * 255.0); + display.setIndex(index, 255 - color, color, 0); + display.loop(); + }); + ArduinoOTA.onEnd([]() { + display.clear(); + display.loop(); + }); + ArduinoOTA.onError([](int error) { + display.clear(); + display.loop(); + }); + ArduinoOTA.begin(); display.setup(); } diff --git a/src/mode/Clock/Clock.h b/src/mode/Clock/Clock.h index 18d66b9..f97aace 100644 --- a/src/mode/Clock/Clock.h +++ b/src/mode/Clock/Clock.h @@ -5,88 +5,6 @@ class Clock : public Mode { -private: - - bool SYMBOLS[11][35] = { - { - X, X, X, - X, _, X, - X, _, X, - X, _, X, - X, X, X, - }, - { - _, _, X, - _, X, X, - _, _, X, - _, _, X, - _, _, X, - }, - { - X, X, X, - _, _, X, - X, X, X, - X, _, _, - X, X, X, - }, - { - X, X, X, - _, _, X, - _, X, X, - _, _, X, - X, X, X, - }, - { - X, _, X, - X, _, X, - X, X, X, - _, _, X, - _, _, X, - }, - { - X, X, X, - X, _, _, - X, X, X, - _, _, X, - X, X, X, - }, - { - X, X, X, - X, _, _, - X, X, X, - X, _, X, - X, X, X, - }, - { - X, X, X, - _, _, X, - _, X, _, - X, _, _, - X, _, _, - }, - { - X, X, X, - X, _, X, - X, X, X, - X, _, X, - X, X, X, - }, - { - X, X, X, - X, _, X, - X, X, X, - _, _, X, - X, X, X, - }, - { - _, _, _, - _, X, _, - _, _, _, - _, X, _, - _, _, _, - }, - }; - public: explicit Clock(Display *display) : @@ -100,37 +18,22 @@ public: return "Clock"; } - void step(microseconds_t dt) override { + void doStep(microseconds_t dt) override { tm time{}; getLocalTime(&time); display->clear(); uint8_t x = 0; - print(&x, time.tm_hour / 10); + display->print(&x, 1, time.tm_hour / 10); x++; - print(&x, time.tm_hour % 10); - print(&x, 10); - print(&x, time.tm_min / 10); + display->print(&x, 1, time.tm_hour % 10); + display->print(&x, 1, 10); + display->print(&x, 1, time.tm_min / 10); x++; - print(&x, time.tm_min % 10); - print(&x, 10); - print(&x, time.tm_sec / 10); + display->print(&x, 1, time.tm_min % 10); + display->print(&x, 1, 10); + display->print(&x, 1, time.tm_sec / 10); x++; - print(&x, time.tm_sec % 10); - } - - void print(uint8_t *pos, uint8_t index) { - if (index > 10) { - Serial.printf("Cannot print symbol #%u.", index); - return; - } - bool *symbolBit = SYMBOLS[index]; - for (uint8_t y = 0; y < 5; ++y) { - for (uint8_t x = 0; x < 3; ++x) { - uint8_t value = *(symbolBit++) ? 255 : 0; - display->set(*pos + x, y, value, value, value); - } - } - *pos += 3; + display->print(&x, 1, time.tm_sec % 10); } }; diff --git a/src/mode/GameOfLife/Cell.h b/src/mode/GameOfLife/Cell.h index 3b33d5a..763c476 100644 --- a/src/mode/GameOfLife/Cell.h +++ b/src/mode/GameOfLife/Cell.h @@ -14,7 +14,7 @@ public: uint32_t color = 0; void animate(microseconds_t dt) { - // TODO "step" does not work as expected + // TODO "doStep" does not work as expected if (alive) { fade = step(fade, 0.0, 255.0, 200000, +dt); } else { diff --git a/src/mode/GameOfLife/GameOfLife.h b/src/mode/GameOfLife/GameOfLife.h index 1e4eb53..8db088c 100644 --- a/src/mode/GameOfLife/GameOfLife.h +++ b/src/mode/GameOfLife/GameOfLife.h @@ -69,7 +69,7 @@ public: return "???"; } - void step(microseconds_t dt) override { + void doStep(microseconds_t dt) override { runtime += dt; if (runtime >= 500000) { runtime = 0; diff --git a/src/mode/Mode.h b/src/mode/Mode.h index 7830062..b870bf8 100644 --- a/src/mode/Mode.h +++ b/src/mode/Mode.h @@ -1,29 +1,93 @@ #ifndef MODE_H #define MODE_H +#define TIMER_COUNT 10 + #include "BASICS.h" #include "display/Display.h" +#include "Timer.h" class Mode { +private: + + Timer *timers[TIMER_COUNT]{}; + + uint8_t timerCount = 0; + protected: Display *display; - microseconds_t clock; - public: explicit Mode(Display *display) : display(display) { - // nothing + for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { + *timer = nullptr; + } } - virtual ~Mode() = default; + virtual ~Mode() { + destroyAllTimers(); + }; virtual const char *getName() = 0; - virtual void step(microseconds_t dt) = 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(microseconds_t interval, Timer::Callback callback) { + for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) { + if (*timer == nullptr) { + *timer = new Timer(interval, callback); + timerCount++; + Serial.printf("Timer created: %p (having %u now)\n", *timer, 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); + Serial.printf("Timer destroyed: %p (having %u now)\n", *timer, timerCount); + *timer = nullptr; + } + }; #endif diff --git a/src/mode/Pong/Player.h b/src/mode/Pong/Player.h index 778e0bd..a0e01f9 100644 --- a/src/mode/Pong/Player.h +++ b/src/mode/Pong/Player.h @@ -7,10 +7,28 @@ class Player { public: - int8_t position = 0; + uint8_t position = 0; + + uint8_t size = 2; uint8_t score = 0; + bool moveUp = false; + + void randomMove(uint8_t height) { + if (moveUp) { + position--; + if (position <= 0 || randomBool(20)) { + moveUp = !moveUp; + } + } else { + position++; + if (position + size >= height || randomBool(20)) { + moveUp = !moveUp; + } + } + } + }; #endif diff --git a/src/mode/Pong/Pong.cpp b/src/mode/Pong/Pong.cpp new file mode 100644 index 0000000..7f39ccf --- /dev/null +++ b/src/mode/Pong/Pong.cpp @@ -0,0 +1,12 @@ +#include "Pong.h" + +Pong *Pong::instance = nullptr; + +void Pong::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; +} diff --git a/src/mode/Pong/Pong.h b/src/mode/Pong/Pong.h index 183db84..ccf93a5 100644 --- a/src/mode/Pong/Pong.h +++ b/src/mode/Pong/Pong.h @@ -9,6 +9,12 @@ class Pong : public Mode { private: + enum Status { + SCORE, PLAY, OVER + }; + + static Pong *instance; + Player player0; Player player1; @@ -17,17 +23,119 @@ private: Vector velocity; + Status status = PLAY; + + microseconds_t timeout = 0; + + static void tick(Timer *timer, uint32_t totalCount, uint32_t currentCount) { + instance->_tick(timer, totalCount, currentCount); + } + + void resetPlayer(); + + void _tick(Timer *timer, uint32_t totalCount, uint32_t currentCount) { + switch (status) { + case SCORE: + timeout -= currentCount * timer->interval; + if (timeout <= 0) { + status = PLAY; + } + break; + case PLAY: + position.add(velocity); + player0.randomMove(display->height); + player1.randomMove(display->height); + paddleBounce(); + checkScoring(); + topBottomBounce(); + draw(); + break; + case OVER: + timeout -= currentCount * timer->interval; + if (timeout <= 0) { + resetPlayer(); + status = SCORE; + timeout = 2000000; + } + break; + } + } + + void topBottomBounce() { + position.bounceInBox(display->width, display->height); + } + + void checkScoring() { + if (position.x < 0) { + player1.score++; + spawnBall(+1); + } else if (position.x >= display->width) { + player0.score++; + spawnBall(-1); + } + if (player0.score >= 10 || player1.score >= 10) { + status = OVER; + timeout = 2000000; + } + } + + void paddleBounce() { + 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) { + velocity.x = -velocity.x; + position.x = display->width - 4; + } + } + + void spawnBall(int direction) { + position.x = (double) display->width / 2.0; + position.y = (double) display->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, 0, 255, 0); + display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, 255, 0, 0); + break; + case PLAY: + for (int i = 0; i < player0.size; ++i) { + display->set(1, (uint8_t) round(player0.position) + i, 0, 255, 0); + } + for (int i = 0; i < player1.size; ++i) { + display->set(display->width - 2, (uint8_t) round(player1.position) + i, 255, 0, 0); + } + display->set((uint8_t) round(position.x), (uint8_t) round(position.y), 255, 255, 255); + break; + case OVER: + if (player0.score > player1.score) { + display->print(1, 1, 11, 0, 255, 0); + display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, 255, 0, 0); + } else if (player0.score < player1.score) { + display->print(1, 1, 12, 255, 0, 0); + display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, 0, 255, 0); + } + break; + } + } + public: explicit Pong(Display *display) : Mode(display), position(display->width / 2.0, display->height / 2.0), velocity(random(360), exp10(1)) { - // nothing - } - - static int8_t randomSignum() { - return random(2) == 0 ? -1 : +1; + instance = this; + createTimer(100000, &tick); + spawnBall(random(2) == 0 ? -1 : +1); + resetPlayer(); } ~Pong() override = default; @@ -36,10 +144,6 @@ public: return "Pong"; } - void step(microseconds_t dt) override { - - } - }; #endif diff --git a/src/mode/SpaceInvaders/SpaceInvaders.h b/src/mode/SpaceInvaders/SpaceInvaders.h index acadfcf..243b8d4 100644 --- a/src/mode/SpaceInvaders/SpaceInvaders.h +++ b/src/mode/SpaceInvaders/SpaceInvaders.h @@ -68,7 +68,7 @@ public: return "Space Invaders"; } - void step(microseconds_t dt) override { + void doStep(microseconds_t dt) override { stepRockets(dt); stepInvaders(dt); stepHero(dt); @@ -80,7 +80,7 @@ public: 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) + if (swarmY + (invadersCountY - 1) * 2 >= display->height - 1) { // TODO this is only correct if there still are any invaders in the accu row (otherwise we "Game Over" too early) Serial.println("GAME OVER"); reset(); } diff --git a/src/mode/Test/Border.h b/src/mode/Test/Border.h index 84a5f73..75e3b74 100644 --- a/src/mode/Test/Border.h +++ b/src/mode/Test/Border.h @@ -18,7 +18,7 @@ public: return "Border"; } - void step(microseconds_t dt) override { + 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) { diff --git a/src/mode/Timer.h b/src/mode/Timer.h new file mode 100644 index 0000000..4f293bd --- /dev/null +++ b/src/mode/Timer.h @@ -0,0 +1,41 @@ +#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; + accu = accu % interval; + callback(this, totalCount++, currentCount); + } + } + +}; + +#endif