Mode refactor

This commit is contained in:
Patrick Haßel 2023-01-06 15:16:12 +01:00
parent eaf3dcea91
commit c562c7d932
19 changed files with 591 additions and 507 deletions

16
TODO.txt Normal file
View File

@ -0,0 +1,16 @@
Modes:
Tetris
Matrix
Ludo
Pacman
Snake
Strobe
Improvements:
NewYear => change due date (birthday etc)
Hardware:
Gamepads
Heatsink
Extend Matrix (double?)
Microphone

View File

@ -20,3 +20,4 @@ upload_protocol = espota
;upload_speed = 921600
monitor_port = /dev/ttyUSB0
monitor_speed = 115200
monitor_filters = esp32_exception_decoder

View File

@ -9,8 +9,12 @@
#define ____ 0
#define FULL 255
#define countof(x) (sizeof(x) / sizeof(x[0]))
typedef int64_t microseconds_t;
typedef unsigned long millis_t;
double doStep(double valueCurrent, double valueMin, double valueMax, microseconds_t millisecondsTotal, microseconds_t microsecondsDelta);
bool randomBool(int uncertainty);

View File

@ -16,7 +16,7 @@ bool dirty = false;
bool notify = false;
unsigned long lastDirtyMillis = 0;
millis_t lastDirtyMillis = 0;
void config_setup() {
if (!config_load()) {

View File

@ -29,7 +29,7 @@ private:
Adafruit_NeoPixel leds;
unsigned long fpsLastMillis = 0;
millis_t fpsLastMillis = 0;
int fps = 0;

View File

@ -8,12 +8,13 @@
#include "mode/NewYear/NewYear.h"
#include "mode/Starfield/Starfield.h"
#include "display.h"
#include "mode/Matrix/Matrix.h"
ModeId currentModeId = NONE;
microseconds_t lastMicros = 0;
ModeBase *mode = nullptr;
Mode *mode = nullptr;
void unloadOldMode();
@ -22,6 +23,8 @@ void loadNewMode();
void mode_step();
void mode_loop() {
Serial.print("\n");
if (currentModeId != config.mode) {
unloadOldMode();
loadNewMode();
@ -49,6 +52,7 @@ void setSpeed(double speed) {
void unloadOldMode() {
if (mode != nullptr) {
Serial.print("[MODE] unload\n");
delete mode;
mode = nullptr;
}
@ -58,36 +62,40 @@ void unloadOldMode() {
void loadNewMode() {
currentModeId = config.mode;
lastMicros = 0;
Serial.printf("[MODE] loading %d\n", currentModeId);
switch (currentModeId) {
case BORDER:
mode = new Border(&display);
mode = new Border(display);
break;
case CLOCK:
mode = new Clock(&display);
mode = new Clock(display);
break;
case GAME_OF_LIFE_BLACK_WHITE:
mode = new GameOfLife(&display, BLACK_WHITE);
mode = new GameOfLife(display, BLACK_WHITE);
break;
case GAME_OF_LIFE_GRAYSCALE:
mode = new GameOfLife(&display, GRAYSCALE);
mode = new GameOfLife(display, GRAYSCALE);
break;
case GAME_OF_LIFE_COLOR_FADE:
mode = new GameOfLife(&display, COLOR_FADE);
mode = new GameOfLife(display, COLOR_FADE);
break;
case GAME_OF_LIFE_RANDOM_COLOR:
mode = new GameOfLife(&display, RANDOM_COLOR);
mode = new GameOfLife(display, RANDOM_COLOR);
break;
case PONG:
mode = new Pong(&display);
mode = new Pong(display);
break;
case SPACE_INVADERS:
mode = new SpaceInvaders(&display);
mode = new SpaceInvaders(display);
break;
case NEW_YEAR:
mode = new NewYear(&display);
mode = new NewYear(display);
break;
case STARFIELD:
mode = new Starfield(&display);
mode = new Starfield(display);
break;
case MATRIX:
mode = new Matrix(display);
break;
default:
Serial.print("No mode loaded.\n");
@ -104,5 +112,5 @@ void mode_step() {
auto currentMicros = (int64_t) micros();
microseconds_t dt = (microseconds_t) min(1000000.0, max(1.0, (double) (currentMicros - lastMicros) * config.speed));
lastMicros = currentMicros;
mode->step(dt);
mode->loop(dt);
}

View File

@ -1,30 +1,32 @@
#ifndef TEST_H
#define TEST_H
#ifndef MODE_BORDER_H
#define MODE_BORDER_H
#include "mode/Mode.h"
class Border : public Mode<Border> {
class Border : public Mode {
public:
explicit Border(Display *display) :
explicit Border(Display &display) :
Mode(display) {
// nothing
}
~Border() override = default;
const char *getName() override {
return "Border";
}
void doStep(microseconds_t dt) override {
for (int y = 0; y < display->height; y++) {
for (int x = 0; x < display->width; x++) {
if (x == 0 || x == display->width - 1 || y == 0 || y == display->height - 1) {
display->set(x, y, WHITE);
protected:
void draw(Display &display) override {
Serial.print("[BORDER] draw\n");
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Serial.printf("[BORDER] draw %d / %d\n", x, y);
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
display.set(x, y, WHITE);
} else {
display->set(x, y, BLACK);
display.set(x, y, BLACK);
}
}
}

View File

@ -1,43 +1,44 @@
#ifndef CLOCK_H
#define CLOCK_H
#ifndef MODE_CLOCK_H
#define MODE_CLOCK_H
#include "mode/Mode.h"
class Clock : public Mode<Clock> {
class Clock : public Mode {
public:
explicit Clock(Display *display) :
explicit Clock(Display &display) :
Mode(display) {
// nothing
}
~Clock() override = default;
const char *getName() override {
return "Clock";
}
void doStep(microseconds_t dt) override {
tm info{};
time_t now;
time(&now);
localtime_r(&now, &info);
bool ok = info.tm_year >= (2023 - 1900);
protected:
void step(microseconds_t dt) override {
if (realtimeChanged) {
markDirty();
}
}
void draw(Display &display) override {
display.clear();
display->clear();
uint8_t x = 2;
x += display->print(x, 1, ok ? info.tm_hour / 10 : 13, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_hour / 10 : 13, WHITE);
x++;
x += display->print(x, 1, ok ? info.tm_hour % 10 : 13, WHITE);
x += display->print(x, 1, 10, WHITE);
x += display->print(x, 1, ok ? info.tm_min / 10 : 13, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_hour % 10 : 13, WHITE);
x += display.print(x, 1, 10, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_min / 10 : 13, WHITE);
x++;
x += display->print(x, 1, ok ? info.tm_min % 10 : 13, WHITE);
x += display->print(x, 1, 10, WHITE);
x += display->print(x, 1, ok ? info.tm_sec / 10 : 13, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_min % 10 : 13, WHITE);
x += display.print(x, 1, 10, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_sec / 10 : 13, WHITE);
x++;
x += display->print(x, 1, ok ? info.tm_sec % 10 : 13, WHITE);
x += display.print(x, 1, realtimeOK ? now.tm_sec % 10 : 13, WHITE);
}
};

View File

@ -14,7 +14,7 @@ public:
Color color = BLACK;
void animate(microseconds_t dt) {
// TODO "doStep" does not work as expected
// TODO fading does not work as expected
if (alive) {
fade = doStep(fade, 0.0, 255.0, 200, +dt);
} else {

View File

@ -1,20 +1,27 @@
#ifndef GAMEOFLIFE_H
#define GAMEOFLIFE_H
#ifndef MODE_GAME_OF_LIFE_H
#define MODE_GAME_OF_LIFE_H
#include "mode/Mode.h"
#include "display/Display.h"
#include "Cell.h"
enum ColorMode {
BLACK_WHITE, GRAYSCALE, COLOR_FADE, RANDOM_COLOR
};
class GameOfLife : public Mode<GameOfLife> {
class GameOfLife : public Mode {
private:
ColorMode colorMode;
size_t cellsSize;
Cell *cells;
Cell *cellsEnd;
Cell *next;
microseconds_t runtime = 0;
uint8_t steps = 0;
@ -25,21 +32,19 @@ private:
uint8_t isSteadyCount = 0;
Cell *cells;
Cell *next;
public:
explicit GameOfLife(Display *display, ColorMode colorMode) :
explicit GameOfLife(Display &display, ColorMode colorMode) :
Mode(display),
colorMode(colorMode) {
cells = (Cell *) malloc(display->pixelCount * sizeof(Cell));
for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) {
colorMode(colorMode),
cellsSize(display.pixelCount * sizeof(Cell)) {
cells = (Cell *) malloc(cellsSize);
cellsEnd = cells + cellsSize;
for (Cell *cell = cells; cell < cells + display.pixelCount; cell++) {
kill(cell);
}
next = (Cell *) malloc(display->pixelCount * sizeof(Cell));
for (Cell *cell = next; cell < next + display->pixelCount; cell++) {
next = (Cell *) malloc(cellsSize);
for (Cell *cell = next; cell < next + display.pixelCount; cell++) {
kill(cell);
}
}
@ -69,7 +74,9 @@ public:
return "???";
}
void doStep(microseconds_t dt) override {
protected:
void step(microseconds_t dt) override {
runtime += dt;
if (runtime >= 500000) {
runtime = 0;
@ -85,14 +92,45 @@ public:
nextGeneration();
}
}
animate(dt);
for (Cell *cell = cells; cell < cellsEnd; cell++) {
cell->animate(dt);
}
// TODO don't always markDirty. Be more efficient
markDirty();
}
void draw(Display &display) override {
Cell *cell = cells;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t brightness;
switch (colorMode) {
case BLACK_WHITE:
brightness = cell->alive ? 255 : 0;
display.set(x, y, gray(brightness));
break;
case GRAYSCALE:
brightness = (uint8_t) cell->fade;
display.set(x, y, gray(brightness));
break;
case COLOR_FADE:
display.set(x, y, {cell->getR(), cell->getG(), cell->getB()});
break;
case RANDOM_COLOR:
display.set(x, y, cell->alive ? cell->color : BLACK);
break;
}
cell++;
}
}
}
private:
void randomFill() {
isSteadyCount = 0;
for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) {
for (Cell *cell = cells; cell < cellsEnd; cell++) {
if (random(4) == 0) {
if (!cell->alive) {
spawn(cell);
@ -117,11 +155,11 @@ private:
}
void nextGeneration() {
memcpy(next, cells, display->pixelCount * sizeof(Cell));
memcpy(next, cells, cellsSize);
Cell *src = cells;
Cell *dst = next;
for (int y = 0; y < display->height; y++) {
for (int x = 0; x < display->width; x++) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t around = countAround(x, y);
if (src->alive) {
if (around <= 2 || 6 <= around) {
@ -134,14 +172,14 @@ private:
dst++;
}
}
memcpy(cells, next, display->pixelCount * sizeof(Cell));
memcpy(cells, next, cellsSize);
}
void print() {
Cell *cell = cells;
for (int y = 0; y < display->height; y++) {
for (int y = 0; y < height; y++) {
Serial.print("|");
for (int x = 0; x < display->width; x++) {
for (int x = 0; x < width; x++) {
Serial.print(cell->alive ? "x|" : " |");
cell++;
}
@ -150,33 +188,6 @@ private:
}
}
void animate(microseconds_t dt) {
Cell *cell = cells;
for (int y = 0; y < display->height; y++) {
for (int x = 0; x < display->width; x++) {
cell->animate(dt);
uint8_t brightness;
switch (colorMode) {
case BLACK_WHITE:
brightness = cell->alive ? 255 : 0;
display->set(x, y, gray(brightness));
break;
case GRAYSCALE:
brightness = (uint8_t) cell->fade;
display->set(x, y, gray(brightness));
break;
case COLOR_FADE:
display->set(x, y, {cell->getR(), cell->getG(), cell->getB()});
break;
case RANDOM_COLOR:
display->set(x, y, cell->alive ? cell->color : BLACK);
break;
}
cell++;
}
}
}
uint8_t countAround(int x, int y) {
return countIfAlive(x - 1, y - 1) + countIfAlive(x + 0, y - 1) + countIfAlive(x + 1, y - 1) +
countIfAlive(x - 1, y + 0) + /* */ countIfAlive(x + 1, y + 0) +
@ -184,15 +195,16 @@ private:
}
uint8_t countIfAlive(int x, int y) {
if (x < 0 || y < 0 || x >= display->width || y >= display->height) {
if (x < 0 || y < 0 || x >= width || y >= height) {
return 0;
}
return get(x, y)->alive ? 1 : 0;
}
Cell *get(int x, int y) {
return cells + display->width * y + x;
return cells + width * y + x;
}
};
#endif

View File

@ -1,12 +1,8 @@
#ifndef MODE_H
#define MODE_H
#define TIMER_COUNT 10
#include "BASICS.h"
#include "display/Display.h"
#include "Timer.h"
#include "ModeBase.h"
enum ModeId {
NONE,
@ -20,25 +16,121 @@ enum ModeId {
SPACE_INVADERS,
NEW_YEAR,
STARFIELD,
MATRIX,
};
template<typename T>
class Mode : public ModeBase {
class Mode {
private:
struct Timer {
millis_t interval;
millis_t last;
};
Display &_display;
bool dirty = true;
Timer timers[2] = {
{0, 0},
{0, 0},
};
protected:
static T *instance;
const uint8_t width;
const uint8_t height;
bool realtimeOK = false;
bool realtimeChanged = false;
tm now = {0, 0, 0, 0, 0, 0, 0, 0, 0};
time_t epoch = 0;
virtual void tick(uint8_t index, millis_t dt) {};
virtual void step(microseconds_t dt) {};
virtual void draw(Display &display) {};
void timer(uint8_t index, millis_t interval) {
if (index >= countof(timers)) {
return;
}
timers[index].interval = interval;
timers[index].last = millis();
}
void markDirty() {
dirty = true;
}
public:
explicit Mode(Display *display) :
ModeBase(display) {
instance = (T *) this;
explicit Mode(Display &display) :
_display(display),
width(display.width),
height(display.height) {
// nothing
}
virtual ~Mode() = default;
virtual const char *getName() = 0;
void loop(microseconds_t dt) {
Serial.print("[MODE] realtime\n");
realtime();
Serial.print("[MODE] handleTimers\n");
handleTimers();
Serial.print("[MODE] step\n");
step(dt);
if (dirty) {
dirty = false;
Serial.print("[MODE] draw\n");
draw(_display);
}
}
private:
void realtime() {
time_t tmp;
time(&tmp);
realtimeOK = tmp > 1600000000;
if (realtimeOK) {
realtimeChanged = epoch != tmp;
if (realtimeChanged) {
epoch = tmp;
localtime_r(&tmp, &now);
now.tm_year += 1900;
now.tm_mon += 1;
}
} else {
realtimeChanged = false;
}
}
void handleTimers() {
millis_t ms = millis();
for (Timer *timer = timers; timer < timers + sizeof(timers); timer++) {
if (timer->interval > 0) {
millis_t dt = ms - timer->last;
if (dt >= timer->interval) {
timer->last = ms;
tick(timer - timers, dt);
}
}
}
}
};
template<typename T>
T *Mode<T>::instance = nullptr;
#endif

View File

@ -1,91 +0,0 @@
#ifndef MODE_BASE_H
#define MODE_BASE_H
#define TIMER_COUNT 10
#include "BASICS.h"
#include "display/Display.h"
#include "Timer.h"
class ModeBase {
private:
Timer *timers[TIMER_COUNT]{};
uint8_t timerCount = 0;
protected:
Display *display;
public:
explicit ModeBase(Display *display) :
display(display) {
for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) {
*timer = nullptr;
}
}
virtual ~ModeBase() {
destroyAllTimers();
};
virtual const char *getName() = 0;
void step(microseconds_t dt) {
for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) {
if (*timer != nullptr) {
(*timer)->step(dt);
}
}
doStep(dt);
}
protected:
virtual void doStep(microseconds_t dt) {};
Timer *createTimer(long long millisecondsInterval, Timer::Callback callback) {
for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) {
if (*timer == nullptr) {
*timer = new Timer(millisecondsInterval * 1000, callback);
timerCount++;
return *timer;
}
}
Serial.printf("ERROR: Cannot create more than %d timers!\n", TIMER_COUNT);
return nullptr;
}
bool destroyTimer(Timer *t) {
for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) {
if (*timer == t) {
_destroyTimer(timer);
return true;
}
}
Serial.printf("ERROR: No such timer.\n");
return false;
}
void destroyAllTimers() {
for (Timer **timer = timers; timer < timers + TIMER_COUNT; timer++) {
if (*timer != nullptr) {
_destroyTimer(timer);
}
}
}
private:
void _destroyTimer(Timer **timer) {
timerCount--;
free(*timer);
*timer = nullptr;
}
};
#endif

View File

@ -11,7 +11,9 @@ class Firework {
INITIAL, RISE, EXPLODE, SPARKLE
};
Display *display{};
uint8_t width = 0;
uint8_t height = 0;
Color color = MAGENTA;
@ -31,17 +33,18 @@ class Firework {
public:
void init(Display *_display) {
this->display = _display;
void init(Display &display) {
width = display.width;
height = display.height;
reset();
}
void reset() {
position = Vector((double) random(display->width), display->height);
position = Vector((double) random(width), height);
color = randomColor();
state = INITIAL;
destinationHeight = display->height / 2.0 + (double) random(5) - 2;
destinationHeight = height / 2.0 + (double) random(5) - 2;
explosionRadius = (double) random(3) + 1;
sparkleMax = 100;
@ -49,12 +52,12 @@ public:
sparkle = 0.0;
}
void launch() {
bool launch() {
if (state != INITIAL) {
Serial.println("ERROR: Cannot start Firework. Already started.");
return;
return false;
}
state = RISE;
return true;
}
void step(microseconds_t dt) {
@ -81,7 +84,7 @@ public:
case INITIAL:
break;
case RISE:
position.y = doStep(position.y, 0.0, display->height, 1000, -dt);
position.y = doStep(position.y, 0.0, height, 1000, -dt);
break;
case EXPLODE:
explosion = doStep(explosion, 0.0, explosionRadius, 500, +dt);
@ -92,42 +95,40 @@ public:
}
}
void draw() {
void draw(Display display) {
switch (state) {
case INITIAL:
break;
case RISE:
display->set(position, YELLOW);
display.set(position, YELLOW);
break;
case EXPLODE:
drawParticle(+0.0, +1.0);
drawParticle(+0.7, +0.7);
drawParticle(+1.0, +0.0);
drawParticle(+0.7, -0.7);
drawParticle(+0.0, -1.0);
drawParticle(-0.7, -0.7);
drawParticle(-1.0, +0.0);
drawParticle(-0.7, +0.7);
drawParticle(display, +0.0, +1.0);
drawParticle(display, +0.7, +0.7);
drawParticle(display, +1.0, +0.0);
drawParticle(display, +0.7, -0.7);
drawParticle(display, +0.0, -1.0);
drawParticle(display, -0.7, -0.7);
drawParticle(display, -1.0, +0.0);
drawParticle(display, -0.7, +0.7);
break;
case SPARKLE:
if (randomBool(2)) drawParticle(+0.0, +1.0);
if (randomBool(2)) drawParticle(+0.7, +0.7);
if (randomBool(2)) drawParticle(+1.0, +0.0);
if (randomBool(2)) drawParticle(+0.7, -0.7);
if (randomBool(2)) drawParticle(+0.0, -1.0);
if (randomBool(2)) drawParticle(-0.7, -0.7);
if (randomBool(2)) drawParticle(-1.0, +0.0);
if (randomBool(2)) drawParticle(-0.7, +0.7);
if (randomBool(2)) drawParticle(display, +0.0, +1.0);
if (randomBool(2)) drawParticle(display, +0.7, +0.7);
if (randomBool(2)) drawParticle(display, +1.0, +0.0);
if (randomBool(2)) drawParticle(display, +0.7, -0.7);
if (randomBool(2)) drawParticle(display, +0.0, -1.0);
if (randomBool(2)) drawParticle(display, -0.7, -0.7);
if (randomBool(2)) drawParticle(display, -1.0, +0.0);
if (randomBool(2)) drawParticle(display, -0.7, +0.7);
break;
}
}
void drawParticle(double x, double y) {
display->set(position.plus(x * explosion, y * explosion), color);
}
private:
bool isAlive() {
return state != INITIAL;
void drawParticle(Display display, double x, double y) {
display.set(position.plus(x * explosion, y * explosion), color);
}
};

View File

@ -1,145 +1,225 @@
#ifndef NEWYEAR_H
#define NEWYEAR_H
#ifndef MODE_NEW_YEAR_H
#define MODE_NEW_YEAR_H
#define MAX_FIREWORKS 10
#include "mode/Mode.h"
#include "Firework.h"
class NewYear : public Mode<NewYear> {
class NewYear : public Mode {
Firework fireworksBegin[MAX_FIREWORKS];
Firework *fireworksEnd = fireworksBegin + MAX_FIREWORKS;
private:
void launch(Timer *timer, uint32_t counter, uint32_t currentCount) {
for (Firework *firework = fireworksBegin; firework < fireworksEnd; firework++) {
if (!firework->isAlive()) {
firework->launch();
Firework fireworks[MAX_FIREWORKS];
int8_t lastSecond = -1;
millis_t lastSecondMillis = 0;
uint8_t days = 0;
uint8_t level = 0;
public:
explicit NewYear(Display &display) :
Mode(display) {
timer(0, 500);
for (auto &firework: fireworks) {
firework.init(display);
}
}
const char *getName() override {
return "NewYear";
}
protected:
void step(microseconds_t dt) override {
if (!realtimeOK) {
setMode(NO_TIME);
} else if (now.tm_mon != 1 || now.tm_mday != 1 || now.tm_hour != 0) {
days = getDayCountForYear(now.tm_year) - now.tm_yday - 1;
if (days == 0) {
loopLastDay();
} else {
loopMultipleDays();
}
} else {
setMode(FIREWORK);
for (auto &firework: fireworks) {
firework.step(dt);
}
}
}
void loopLastDay() {
setMode(LAST_DAY);
int levelTmp = getLevel();
if (level != levelTmp) {
level = levelTmp;
markDirty();
}
}
void loopMultipleDays() {
setMode(MULTIPLE_DAYS);
if (realtimeChanged) {
markDirty();
}
}
void tick(uint8_t index, millis_t dt) override {
launch();
}
void draw(Display &display) override {
display.clear();
if (realtimeOK) {
drawNoTime(display);
} else if (now.tm_mon == 1 && now.tm_mday == 1 && now.tm_hour == 0) {
drawYear(display, now.tm_year + 1900);
drawFirework(display);
} else {
drawCountdown(display, now);
}
}
private:
enum State {
NO_TIME,
MULTIPLE_DAYS,
LAST_DAY,
FIREWORK,
};
State _state = NO_TIME;
void setMode(State state) {
if (_state != state) {
_state = state;
markDirty();
timer(0, 0);
switch (state) {
case LAST_DAY:
lastSecond = -1;
break;
case FIREWORK:
timer(0, 500);
break;
default:
break;
}
}
}
int getLevel() {
if (lastSecond < 0 || lastSecond != now.tm_sec) {
lastSecond = (int8_t) now.tm_sec;
lastSecondMillis = millis();
}
millis_t mils = millis() - lastSecondMillis;
int levelTmp = (int) round(32 * mils / 1000.0);
return levelTmp;
}
void launch() {
for (auto &firework: fireworks) {
if (firework.launch()) {
return;
}
}
}
int lastSecond = -1;
unsigned long lastSecondMillis = 0;
void step(Timer *timer, uint32_t counter, uint32_t currentCount) {
tm info{};
time_t now;
time(&now);
localtime_r(&now, &info);
info.tm_year += 1900;
info.tm_mon += 1;
bool ok = info.tm_year >= 2023;
display->clear();
if (!ok) {
drawNoTime();
} else if (info.tm_mon == 1 && info.tm_mday == 1 && info.tm_hour == 0) {
drawYear(counter, info.tm_year + 1900);
drawFirework(timer->interval);
} else {
drawCountdown(info);
}
}
void drawNoTime() {
uint8_t x = 2;
x += display->print(x, 1, 13, WHITE);
x++;
x += display->print(x, 1, 13, WHITE);
x += display->print(x, 1, 10, WHITE);
x += display->print(x, 1, 13, WHITE);
x++;
x += display->print(x, 1, 13, WHITE);
x += display->print(x, 1, 10, WHITE);
x += display->print(x, 1, 13, WHITE);
x++;
x += display->print(x, 1, 13, WHITE);
}
void drawCountdown(const tm &time) {
int days = getDayCountForYear(time.tm_year) - time.tm_yday - 1;
int hours = (24 - time.tm_hour - (time.tm_min > 0 || time.tm_sec > 0 ? 1 : 0));
int minutes = (60 - time.tm_min - (time.tm_sec > 0 ? 1 : 0)) % 60;
int seconds = (60 - time.tm_sec) % 60;
void drawCountdown(Display &display, const tm &now) {
uint8_t hours = (24 - now.tm_hour - (now.tm_min > 0 || now.tm_sec > 0 ? 1 : 0));
uint8_t minutes = (60 - now.tm_min - (now.tm_sec > 0 ? 1 : 0)) % 60;
uint8_t seconds = (60 - now.tm_sec) % 60;
uint8_t x = 0;
if (days > 0) {
drawDay(days, &x);
x += display->print(x, 1, 10, WHITE);
drawDay(display, days, &x);
x += display.print(x, 1, 10, WHITE);
} else {
x += 2;
}
drawHour(days, hours, &x);
x += display->print(x, 1, 10, WHITE);
draw2Digit(minutes, &x);
drawHour(display, days, hours, &x);
x += display.print(x, 1, 10, WHITE);
draw2Digit(display, minutes, &x);
if (days <= 0) {
x += display->print(x, 1, 10, WHITE);
draw2Digit(seconds, &x);
drawSubSecondsBar(time.tm_sec);
x += display.print(x, 1, 10, WHITE);
draw2Digit(display, seconds, &x);
drawSubSecondsBar(display);
} else {
drawSecondsBar(seconds);
drawSecondsBar(display, seconds);
}
}
void drawDay(int days, uint8_t *x) {
static void drawNoTime(Display &display) {
uint8_t x = 2;
x += display.print(x, 1, 13, WHITE);
x++;
x += display.print(x, 1, 13, WHITE);
x += display.print(x, 1, 10, WHITE);
x += display.print(x, 1, 13, WHITE);
x++;
x += display.print(x, 1, 13, WHITE);
x += display.print(x, 1, 10, WHITE);
x += display.print(x, 1, 13, WHITE);
x++;
x += display.print(x, 1, 13, WHITE);
}
static void drawDay(Display &display, int days, uint8_t *x) {
if (days > 100) {
*x += display->print(*x, 1, days / 100, WHITE);
*x += display.print(*x, 1, days / 100, WHITE);
} else {
*x += 3;
}
(*x)++;
if (days > 10) {
*x += display->print(*x, 1, days / 10 % 10, WHITE);
*x += display.print(*x, 1, days / 10 % 10, WHITE);
} else {
*x += 3;
}
(*x)++;
*x += display->print(*x, 1, days % 10, WHITE);
*x += display.print(*x, 1, days % 10, WHITE);
}
void drawHour(int days, int hours, uint8_t *x) {
static void drawHour(Display &display, int days, int hours, uint8_t *x) {
if (days > 0 || hours >= 10) {
*x += display->print(*x, 1, hours / 10, WHITE);
*x += display.print(*x, 1, hours / 10, WHITE);
} else {
*x += 3;
}
(*x)++;
*x += display->print(*x, 1, hours % 10, WHITE);
*x += display.print(*x, 1, hours % 10, WHITE);
}
void draw2Digit(int value, uint8_t *x) {
*x += display->print(*x, 1, value / 10, WHITE);
static void draw2Digit(Display &display, int value, uint8_t *x) {
*x += display.print(*x, 1, value / 10, WHITE);
(*x)++;
*x += display->print(*x, 1, value % 10, WHITE);
*x += display.print(*x, 1, value % 10, WHITE);
}
void drawSecondsBar(int seconds) {
static void drawSecondsBar(Display &display, int seconds) {
for (int pos = 0; pos < 30; pos++) {
if (pos <= seconds - 30) {
display->set(pos + 1, 7, GREEN);
display.set(pos + 1, 7, GREEN);
} else if (pos <= seconds) {
display->set(pos + 1, 7, RED);
display.set(pos + 1, 7, RED);
}
}
}
void drawSubSecondsBar(const int second) {
if (lastSecond < 0 || lastSecond != second) {
lastSecond = second;
lastSecondMillis = millis();
}
unsigned long mils = millis() - lastSecondMillis;
int level = (int) round(32 * mils / 1000.0);
void drawSubSecondsBar(Display &display) const {
for (int pos = 0; pos < 32; pos++) {
if (pos < 32 - level) {
display->set(pos, 7, GREEN);
display.set(pos, 7, GREEN);
}
}
}
@ -152,42 +232,22 @@ class NewYear : public Mode<NewYear> {
return 365;
}
void drawYear(uint32_t counter, int year) {
static void drawYear(Display &display, int year) {
uint8_t x = 8;
x += display->print(x, 1, year / 1000 % 10, counter % 64 != 0 ? WHITE : BLACK);
x += display.print(x, 1, year / 1000 % 10, WHITE);
x++;
x += display->print(x, 1, year / 100 % 10, counter % 64 != 1 ? WHITE : BLACK);
x += display.print(x, 1, year / 100 % 10, WHITE);
x++;
x += display->print(x, 1, year / 10 % 10, counter % 64 != 2 ? WHITE : BLACK);
x += display.print(x, 1, year / 10 % 10, WHITE);
x++;
x += display->print(x, 1, year / 1 % 10, counter % 64 != 3 ? WHITE : BLACK);
x += display.print(x, 1, year / 1 % 10, WHITE);
}
void drawFirework(microseconds_t dt) const {
for (auto *firework = (Firework *) fireworksBegin; firework < fireworksEnd; firework++) {
if (firework->isAlive()) {
firework->step(dt);
firework->draw();
void drawFirework(Display &display) {
for (auto &firework: fireworks) {
firework.draw(display);
}
}
}
public:
explicit NewYear(Display *display) :
Mode(display) {
createTimer(500, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->launch(timer, counter, currentCount); });
createTimer(50, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->step(timer, counter, currentCount); });
for (Firework *firework = fireworksBegin; firework < fireworksEnd; firework++) {
firework->init(display);
}
}
~NewYear() override = default;
const char *getName() override {
return "NewYear";
}
};

View File

@ -1,11 +1,11 @@
#ifndef PONG_H
#define PONG_H
#ifndef MODE_PONG_H
#define MODE_PONG_H
#include "mode/Mode.h"
#include "Player.h"
#include "display/Vector.h"
class Pong : public Mode<Pong> {
class Pong : public Mode {
private:
@ -25,34 +25,45 @@ private:
microseconds_t timeout = 0;
void resetPlayer() {
player0.size = 3;
player0.score = 0;
player0.position = (display->height - player0.size) / 2;
player1.size = 3;
player1.score = 0;
player1.position = (display->height - player1.size) / 2;
public:
explicit Pong(Display &display) :
Mode(display),
position(width / 2.0, height / 2.0),
velocity(Vector::polar(random(360), exp10(1))) {
timer(0, 100);
spawnBall(random(2) == 0 ? -1 : +1);
resetPlayer();
}
void tick(Timer *timer, uint32_t totalCount, uint32_t currentCount) {
const char *getName() override {
return "Pong";
}
protected:
void tick(uint8_t index, millis_t dt) override {
switch (status) {
case SCORE:
timeout -= currentCount * timer->interval;
timeout -= dt;
if (timeout <= 0) {
status = PLAY;
}
break;
case PLAY:
position = position.plus(velocity);
player0.randomMove(display->height);
player1.randomMove(display->height);
player0.randomMove(height);
player1.randomMove(height);
paddleBounce();
checkScoring();
topBottomBounce();
draw();
// TODO don't always markDirty. Be more efficient
markDirty();
break;
case OVER:
timeout -= currentCount * timer->interval;
timeout -= dt;
if (timeout <= 0) {
resetPlayer();
status = SCORE;
@ -62,15 +73,54 @@ private:
}
}
void draw(Display &display) override {
display.clear();
switch (status) {
case SCORE:
display.print(1, 1, player0.score, GREEN);
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED);
break;
case PLAY:
for (int i = 0; i < player0.size; ++i) {
display.set(1, (uint8_t) round(player0.position) + i, GREEN);
}
for (int i = 0; i < player1.size; ++i) {
display.set(width - 2, (uint8_t) round(player1.position) + i, RED);
}
display.set((uint8_t) round(position.x), (uint8_t) round(position.y), WHITE);
break;
case OVER:
if (player0.score > player1.score) {
display.print(1, 1, 11, GREEN);
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED);
} else if (player0.score < player1.score) {
display.print(1, 1, 12, RED);
display.print(width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN);
}
break;
}
}
private:
void resetPlayer() {
player0.size = 3;
player0.score = 0;
player0.position = (height - player0.size) / 2;
player1.size = 3;
player1.score = 0;
player1.position = (height - player1.size) / 2;
}
void topBottomBounce() {
position = position.bounceInBox(display->width, display->height);
position = position.bounceInBox(width, height);
}
void checkScoring() {
if (position.x < 0) {
player1.score++;
spawnBall(+1);
} else if (position.x >= display->width) {
} else if (position.x >= width) {
player0.score++;
spawnBall(-1);
}
@ -84,67 +134,21 @@ private:
if (position.x == 1 && player0.position <= position.y && position.y < player0.position + player0.size) {
velocity.x = -velocity.x;
position.x = 3;
} else if (position.x == display->width - 2 && player1.position <= position.y && position.y < player1.position + player1.size) {
} else if (position.x == width - 2 && player1.position <= position.y && position.y < player1.position + player1.size) {
velocity.x = -velocity.x;
position.x = display->width - 4;
position.x = width - 4;
}
}
void spawnBall(int direction) {
position.x = (double) display->width / 2.0;
position.y = (double) display->height / 2.0;
position.x = (double) width / 2.0;
position.y = (double) height / 2.0;
velocity.x = direction;
velocity.y = 0;
status = SCORE;
timeout = 2000000;
}
void draw() {
display->clear();
switch (status) {
case SCORE:
display->print(1, 1, player0.score, GREEN);
display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, player1.score, RED);
break;
case PLAY:
for (int i = 0; i < player0.size; ++i) {
display->set(1, (uint8_t) round(player0.position) + i, GREEN);
}
for (int i = 0; i < player1.size; ++i) {
display->set(display->width - 2, (uint8_t) round(player1.position) + i, RED);
}
display->set((uint8_t) round(position.x), (uint8_t) round(position.y), WHITE);
break;
case OVER:
if (player0.score > player1.score) {
display->print(1, 1, 11, GREEN);
display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 12, RED);
} else if (player0.score < player1.score) {
display->print(1, 1, 12, RED);
display->print(display->width - 1 - DISPLAY_CHAR_WIDTH, 1, 11, GREEN);
}
break;
}
}
public:
explicit Pong(Display *display) :
Mode(display),
position(display->width / 2.0, display->height / 2.0),
velocity(Vector::polar(random(360), exp10(1))) {
instance = this;
createTimer(100, [](Timer *timer, uint32_t counter, uint32_t currentCount) { instance->tick(timer, counter, currentCount); });
spawnBall(random(2) == 0 ? -1 : +1);
resetPlayer();
}
~Pong() override = default;
const char *getName() override {
return "Pong";
}
};
#endif

View File

@ -1,5 +1,5 @@
#ifndef SPACEINVADERS_H
#define SPACEINVADERS_H
#ifndef MODE_SPACE_INVADERS_H
#define MODE_SPACE_INVADERS_H
#define ROCKET_MAX 20
@ -18,7 +18,7 @@ struct Invader {
uint8_t y;
};
class SpaceInvaders : public Mode<SpaceInvaders> {
class SpaceInvaders : public Mode {
private:
@ -45,10 +45,10 @@ private:
public:
explicit SpaceInvaders(Display *display) :
explicit SpaceInvaders(Display &display) :
Mode(display),
invadersCountX(display->width / 3),
invadersCountY(display->height / 4) {
invadersCountX(width / 3),
invadersCountY(height / 4) {
swarmBegin = (Invader *) malloc(sizeof(Invader) * invadersCountX * invadersCountY);
swarmEnd = swarmBegin + invadersCountX * invadersCountY;
@ -68,24 +68,37 @@ public:
return "Space Invaders";
}
void doStep(microseconds_t dt) override {
protected:
void step(microseconds_t dt) override {
stepRockets(dt);
stepInvaders(dt);
stepHero(dt);
draw();
collide();
if (invadersAlive == 0) {
Serial.println("WINNER!");
reset();
}
if (swarmY + (invadersCountY - 1) * 2 >= display->height - 1) { // TODO this is only correct if there still are any invaders in the last row (otherwise we "Game Over" too early)
// TODO this is only correct if there still are any invaders in the last row (otherwise we "Game Over" too early)
if (swarmY + (invadersCountY - 1) * 2 >= height - 1) {
Serial.println("GAME OVER");
reset();
}
// TODO don't always markDirty. Be more efficient
markDirty();
}
void draw(Display &display) override {
display.clear();
drawInvaders(display);
drawRockets(display);
drawHero(display);
}
private:
void stepRockets(microseconds_t dt) {
rocketRuntime += dt;
if (rocketRuntime > 200000) {
@ -146,7 +159,7 @@ public:
}
} else {
heroX++;
if (heroX >= display->width - 2 || randomBool(20)) {
if (heroX >= width - 2 || randomBool(20)) {
heroLeft = true;
}
}
@ -164,7 +177,7 @@ public:
if (!rocket->alive && rocket->flash == 0) {
rocket->alive = true;
rocket->x = heroX;
rocket->y = display->height - 2;
rocket->y = height - 2;
}
}
}
@ -195,47 +208,40 @@ public:
&& swarmX + invader->x * 3 + 1 >= rocket->x;
}
void draw() {
display->clear();
drawInvaders();
drawRockets();
drawHero();
}
void drawInvaders() {
void drawInvaders(Display &display) {
for (Invader *invader = swarmBegin; invader < swarmEnd; invader++) {
if (invader->alive) {
display->set(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, RED);
display->set(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, RED);
display.set(swarmX + invader->x * 3 + 0, swarmY + invader->y * 2, RED);
display.set(swarmX + invader->x * 3 + 1, swarmY + invader->y * 2, RED);
}
}
}
void drawRockets() {
void drawRockets(Display &display) {
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {
if (rocket->alive) {
display->set(rocket->x, rocket->y, YELLOW);
display.set(rocket->x, rocket->y, YELLOW);
} else if (rocket->flash > 0) {
display->set(rocket->x - 1, rocket->y - 1, gray(rocket->flash));
display->set(rocket->x - 1, rocket->y + 1, gray(rocket->flash));
display->set(rocket->x + 0, rocket->y + 0, gray(rocket->flash));
display->set(rocket->x + 1, rocket->y + 1, gray(rocket->flash));
display->set(rocket->x + 1, rocket->y - 1, gray(rocket->flash));
display.set(rocket->x - 1, rocket->y - 1, gray(rocket->flash));
display.set(rocket->x - 1, rocket->y + 1, gray(rocket->flash));
display.set(rocket->x + 0, rocket->y + 0, gray(rocket->flash));
display.set(rocket->x + 1, rocket->y + 1, gray(rocket->flash));
display.set(rocket->x + 1, rocket->y - 1, gray(rocket->flash));
}
}
}
void drawHero() {
display->set(heroX - 1, display->height - 1, BLUE);
display->set(heroX + 0, display->height - 1, BLUE);
display->set(heroX + 1, display->height - 1, BLUE);
void drawHero(Display &display) {
display.set(heroX - 1, height - 1, BLUE);
display.set(heroX + 0, height - 1, BLUE);
display.set(heroX + 1, height - 1, BLUE);
}
void reset() {
heroRuntime = 0;
heroLeft = false;
heroShoot = 0;
heroX = display->width / 2;
heroX = width / 2;
rocketRuntime = 0;
for (Rocket *rocket = rocketsBegin; rocket < rocketsEnd; rocket++) {

View File

@ -1,11 +1,11 @@
#ifndef MEDIATABLE_STARFIELD_H
#define MEDIATABLE_STARFIELD_H
#ifndef MODE_STARFIELD_H
#define MODE_STARFIELD_H
#include "mode/Mode.h"
#define STAR_COUNT 20
class Starfield : public Mode<Starfield> {
class Starfield : public Mode {
private:
@ -15,12 +15,12 @@ private:
public:
explicit Starfield(Display *display) :
explicit Starfield(Display &display) :
Mode(display),
center(display->width / 2.0, display->height / 2.0) {
center(width / 2.0, height / 2.0) {
for (auto &star: stars) {
star.x = random(display->width);
star.y = random(display->height);
star.x = random(width);
star.y = random(height);
}
}
@ -28,16 +28,26 @@ public:
return "Starfield";
}
void doStep(microseconds_t dt) override {
display->clear();
protected:
void step(microseconds_t dt) override {
for (auto &star: stars) {
const Vector velocity = star.minus(center).multiply((double) dt / 200000.0);
star = star.plus(velocity);
if (star.x < 0 || star.x >= display->width || star.y < 0 || star.y >= display->height) {
if (star.x < 0 || star.x >= width || star.y < 0 || star.y >= height) {
star = center.plus(Vector::polar(random(360), 1));
}
uint8_t brightness = (uint8_t) round(255.0 * star.minus(center).length / (display->width / 2.0));
display->set(star, gray(brightness));
}
// TODO don't always markDirty. Be more efficient
markDirty();
}
void draw(Display &display) override {
display.clear();
for (auto &star: stars) {
uint8_t brightness = (uint8_t) round(255.0 * star.minus(center).length / (width / 2.0));
display.set(star, gray(brightness));
}
}

View File

@ -1,42 +0,0 @@
#ifndef TIMER_H
#define TIMER_H
#include "BASICS.h"
class Timer {
public:
typedef void (*Callback)(Timer *timer, uint32_t counter, uint32_t currentCount);
private:
Callback callback;
microseconds_t accu = 0;
uint32_t totalCount = 0;
public:
microseconds_t interval;
Timer(microseconds_t interval, Callback callback) :
callback(callback),
interval(interval) {
// nothing
}
void step(microseconds_t dt) {
accu += dt;
if (accu >= interval) {
uint32_t currentCount = accu / interval;
totalCount += currentCount;
accu = accu % interval;
callback(this, totalCount, currentCount);
}
}
};
#endif

View File

@ -113,5 +113,5 @@ void web_fps_off() {
void redirect() {
server.sendHeader("location", "/");
server.send(302, "text/plain", "ok");
server.send(302, "text/plain", "realtimeOK");
}