FIXES after firsts tests on hardware (basically working now)

This commit is contained in:
Patrick Haßel 2025-01-23 22:29:27 +01:00
parent 80b83705ac
commit 0b891d9604
20 changed files with 1373 additions and 1171 deletions

13
src/BASICS.cpp Normal file
View File

@ -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<double>(microsecondsDelta) / (static_cast<double>(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;
}

22
src/BASICS.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef BASICS_H
#define BASICS_H
#include <patrix/display/Display.h>
#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

View File

@ -1,13 +1,102 @@
#ifndef NODE_H
#define NODE_H
#include <patrix/core/Config.h>
#include <patrix/display/DisplayMatrix.h>
#include <patrix/node/PatrixNode.h>
Config config("/test.json");
#include <mode.h>
#include <patrix/core/http.h>
DisplayMatrix<32, 8> display(27);
DisplayMatrix<32, 8> display(13);
static const auto style = R"(
<style>
body {
font-family: sans-serif;
font-size: 8vw;
margin: 0;
}
button.player{
width: 33vmin;
height: 33vmin;
font-size: 9vw;
}
table{
border-collapse: collapse;
}
td{
text-align: center;
}
</style>
)";
static const auto script = R"(
<script>
function get(path){
var r = new XMLHttpRequest();
r.open("GET", path, true);
r.send();
}
</script>
)";
inline void httpMode(AsyncWebServerRequest *request) {
if (!request->hasParam("mode")) {
request->send(400);
}
setMode(static_cast<ModeId>(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"(<p>)");
response->print(R"(<a href="/player?index=0">Player 0</a><br>)");
response->print(R"(<a href="/player?index=1">Player 1</a><br>)");
response->print(R"(</p>)");
response->print(R"(<p>)");
response->print(R"(<a onclick="get('/mode?mode=0');">NONE</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=1');">BORDER</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=2');">CLOCK</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=3');">GAME_OF_LIFE_BLACK_WHITE</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=4');">GAME_OF_LIFE_GRAYSCALE</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=5');">GAME_OF_LIFE_COLOR_FADE</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=6');">GAME_OF_LIFE_RANDOM_COLOR</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=7');">PONG</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=8');">SPACE_INVADERS</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=9');">COUNT_DOWN</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=10');">COUNT_DOWN_BARS</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=11');">COUNT_DOWN_SLEEP</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=12');">STARFIELD</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=13');">MATRIX</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=14');">POWER</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=15');">ENERGY</a><br>)");
response->print(R"(<a onclick="get('/mode?mode=16');">TIMER</a><br>)");
response->print(R"(</p>)");
response->print(R"(<p>)");
response->print(R"(Helligkeit: <a onclick="get('/brighter');">+</a> / <a onclick="get('/darker');">-</a><br>)");
response->print(R"(Geschwindigkeit: <a onclick="get('/faster');">+</a> / <a onclick="get('/slower');">-</a><br>)");
response->print(R"(FPS: <a onclick="get('/fps/on');">EIN</a> / <a onclick="get('/fps/off');">AUS</a><br>)");
response->print(R"(</p>)");
response->print(R"(<p>)");
response->print(R"(<input type="number" min="1900" max="3000" step="1" name="year" id="year">)");
response->print(R"(<input type="number" min="1" max="12" step="1" name="month" id="month">)");
response->print(R"(<input type="number" min="1" max="31" step="1" name="day" id="day">)");
response->print(R"(<button onclick="get('/config/date?year=' + document.getElementById('year').value + '&month=' + document.getElementById('month').value + '&day=' + document.getElementById('day').value);">Datum setzen</button>)");
response->print(R"(</p>)");
response->print(R"(<p>)");
response->print(R"(<button onclick="get('/config/save');">Speichern erzwingen</button>)");
response->print(R"(</p>)");
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

156
src/mode.cpp Normal file
View File

@ -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 <patrix/core/Config.h>
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<int64_t>(micros());
const auto deltaMicros = static_cast<microseconds_t>(min(1000000.0, max(1.0, static_cast<double>(currentMicros - modeStepLastMicros) * modeSpeed)));
modeStepLastMicros = currentMicros;
mode->loop(deltaMicros);
}

20
src/mode.h Normal file
View File

@ -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

View File

@ -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);
}
};

View File

@ -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);
}
};

View File

@ -3,15 +3,17 @@
#define MAX_FIREWORKS 6
#include <patrix/display/DisplayMatrix_FontSpecial.h>
#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<int>(floor(diffSeconds / (24 * 60 * 60)));
hours = static_cast<int>(floor(diffSeconds / (60 * 60))) % 24;
minutes = static_cast<int>(floor(diffSeconds / 60)) % 60;
seconds = static_cast<int>(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<int>(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<uint8_t>(round(static_cast<double>(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

View File

@ -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<double>(random(5)) - 2;
explosionRadius = static_cast<double>(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<int>(position.x), static_cast<int>(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<int>(p.x), static_cast<int>(p.y), color.factor(DARKER_FACTOR));
}
};

View File

@ -1,70 +1,96 @@
#ifndef MODE_ENERGY_H
#define MODE_ENERGY_H
#include <patrix/core/mqtt.h>
#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);
}
}
};

View File

@ -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<uint8_t>((fade - 128) * 2.0 + 1);
}
if (fade < 128) {
return static_cast<uint8_t>(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<uint8_t>(fade * 2.0 + 1);
}
return 255;
}
if (fade < 128) {
return 0;
}
return static_cast<uint8_t>((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<uint8_t>((fade - 128) * 2.0 + 1);
}
};

View File

@ -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<Cell *>(malloc(cellsSize));
cellsEnd = cells + display.pixelCount;
for (auto cell = cells; cell < cells + display.pixelCount; cell++) {
kill(cell);
}
next = static_cast<Cell *>(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<uint8_t>(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;
}
};

View File

@ -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<double>(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<int>(round(glyph.x)), static_cast<int>(round(glyph.y - i)), {64, 128, 64});
}
if ((static_cast<int>(round(glyph.y)) + glyph.length) % 2 == 0) {
display.setPixel(static_cast<int>(round(glyph.x)), static_cast<int>(round(glyph.y)), {0, 255, 0});
}
}
}
};

View File

@ -1,8 +1,8 @@
#ifndef MODE_H
#define MODE_H
#include "BASICS.h"
#include "display/Display.h"
#include <BASICS.h>
#include <patrix/core/log.h>
#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;
}
}
}
}
};

View File

@ -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)) {

View File

@ -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<uint8_t>(round(player0.y)) + i, Green);
}
for (auto i = 0; i < player1.size; ++i) {
display.setPixel(width - 2, static_cast<uint8_t>(round(player1.y)) + i, Red);
}
display.setPixel(static_cast<uint8_t>(round(ball.x)), static_cast<uint8_t>(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<double>(width) / 2.0;
ball.y = static_cast<double>(height) / 2.0;
velocity.x = direction;
velocity.y = 0;
status = SCORE;
timeoutMicroseconds = 2000 * 1000;
}
};

View File

@ -1,41 +1,70 @@
#ifndef MODE_POWER_H
#define MODE_POWER_H
#include <patrix/core/mqtt.h>
#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

View File

@ -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<Invader *>(malloc(sizeof(Invader) * invadersCountX * invadersCountY));
swarmEnd = swarmBegin + invadersCountX * invadersCountY;
rocketsBegin = (Rocket *) malloc(sizeof(Rocket) * ROCKET_MAX);
rocketsEnd = rocketsBegin + ROCKET_MAX;
rocketsBegin = static_cast<Rocket *>(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++;
}
}
};

View File

@ -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<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();
}
// 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<uint8_t>(round(255.0 * star.minus(center).length / (width / 2.0)));
display.setPixel(star.x, star.y, RGBA::gray(brightness));
}
}
};
#endif

View File

@ -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);
}
};