Mode refactor
This commit is contained in:
parent
eaf3dcea91
commit
c562c7d932
16
TODO.txt
Normal file
16
TODO.txt
Normal 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
|
||||
@ -20,3 +20,4 @@ upload_protocol = espota
|
||||
;upload_speed = 921600
|
||||
monitor_port = /dev/ttyUSB0
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -16,7 +16,7 @@ bool dirty = false;
|
||||
|
||||
bool notify = false;
|
||||
|
||||
unsigned long lastDirtyMillis = 0;
|
||||
millis_t lastDirtyMillis = 0;
|
||||
|
||||
void config_setup() {
|
||||
if (!config_load()) {
|
||||
|
||||
@ -29,7 +29,7 @@ private:
|
||||
|
||||
Adafruit_NeoPixel leds;
|
||||
|
||||
unsigned long fpsLastMillis = 0;
|
||||
millis_t fpsLastMillis = 0;
|
||||
|
||||
int fps = 0;
|
||||
|
||||
|
||||
32
src/mode.cpp
32
src/mode.cpp
@ -8,12 +8,13 @@
|
||||
#include "mode/NewYear/NewYear.h"
|
||||
#include "mode/Starfield/Starfield.h"
|
||||
#include "display.h"
|
||||
#include "mode/Matrix/Matrix.h"
|
||||
|
||||
ModeId currentModeId = NONE;
|
||||
|
||||
microseconds_t lastMicros = 0;
|
||||
|
||||
ModeBase *mode = nullptr;
|
||||
Mode *mode = nullptr;
|
||||
|
||||
void unloadOldMode();
|
||||
|
||||
@ -22,6 +23,8 @@ void loadNewMode();
|
||||
void mode_step();
|
||||
|
||||
void mode_loop() {
|
||||
Serial.print("\n");
|
||||
|
||||
if (currentModeId != config.mode) {
|
||||
unloadOldMode();
|
||||
loadNewMode();
|
||||
@ -49,6 +52,7 @@ void setSpeed(double speed) {
|
||||
|
||||
void unloadOldMode() {
|
||||
if (mode != nullptr) {
|
||||
Serial.print("[MODE] unload\n");
|
||||
delete mode;
|
||||
mode = nullptr;
|
||||
}
|
||||
@ -58,36 +62,40 @@ void unloadOldMode() {
|
||||
void loadNewMode() {
|
||||
currentModeId = config.mode;
|
||||
lastMicros = 0;
|
||||
Serial.printf("[MODE] loading %d\n", currentModeId);
|
||||
switch (currentModeId) {
|
||||
case BORDER:
|
||||
mode = new Border(&display);
|
||||
mode = new Border(display);
|
||||
break;
|
||||
case CLOCK:
|
||||
mode = new Clock(&display);
|
||||
mode = new Clock(display);
|
||||
break;
|
||||
case GAME_OF_LIFE_BLACK_WHITE:
|
||||
mode = new GameOfLife(&display, BLACK_WHITE);
|
||||
mode = new GameOfLife(display, BLACK_WHITE);
|
||||
break;
|
||||
case GAME_OF_LIFE_GRAYSCALE:
|
||||
mode = new GameOfLife(&display, GRAYSCALE);
|
||||
mode = new GameOfLife(display, GRAYSCALE);
|
||||
break;
|
||||
case GAME_OF_LIFE_COLOR_FADE:
|
||||
mode = new GameOfLife(&display, COLOR_FADE);
|
||||
mode = new GameOfLife(display, COLOR_FADE);
|
||||
break;
|
||||
case GAME_OF_LIFE_RANDOM_COLOR:
|
||||
mode = new GameOfLife(&display, RANDOM_COLOR);
|
||||
mode = new GameOfLife(display, RANDOM_COLOR);
|
||||
break;
|
||||
case PONG:
|
||||
mode = new Pong(&display);
|
||||
mode = new Pong(display);
|
||||
break;
|
||||
case SPACE_INVADERS:
|
||||
mode = new SpaceInvaders(&display);
|
||||
mode = new SpaceInvaders(display);
|
||||
break;
|
||||
case NEW_YEAR:
|
||||
mode = new NewYear(&display);
|
||||
mode = new NewYear(display);
|
||||
break;
|
||||
case STARFIELD:
|
||||
mode = new Starfield(&display);
|
||||
mode = new Starfield(display);
|
||||
break;
|
||||
case MATRIX:
|
||||
mode = new Matrix(display);
|
||||
break;
|
||||
default:
|
||||
Serial.print("No mode loaded.\n");
|
||||
@ -104,5 +112,5 @@ void mode_step() {
|
||||
auto currentMicros = (int64_t) micros();
|
||||
microseconds_t dt = (microseconds_t) min(1000000.0, max(1.0, (double) (currentMicros - lastMicros) * config.speed));
|
||||
lastMicros = currentMicros;
|
||||
mode->step(dt);
|
||||
mode->loop(dt);
|
||||
}
|
||||
|
||||
@ -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) :
|
||||
Mode(display) {
|
||||
explicit Border(Display &display) :
|
||||
Mode(display) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
~Border() override = default;
|
||||
|
||||
const char *getName() override {
|
||||
return "Border";
|
||||
}
|
||||
|
||||
void doStep(microseconds_t dt) override {
|
||||
for (int y = 0; y < display->height; y++) {
|
||||
for (int x = 0; x < display->width; x++) {
|
||||
if (x == 0 || x == display->width - 1 || y == 0 || y == display->height - 1) {
|
||||
display->set(x, y, WHITE);
|
||||
protected:
|
||||
|
||||
void draw(Display &display) override {
|
||||
Serial.print("[BORDER] draw\n");
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
Serial.printf("[BORDER] draw %d / %d\n", x, y);
|
||||
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
|
||||
display.set(x, y, WHITE);
|
||||
} else {
|
||||
display->set(x, y, BLACK);
|
||||
display.set(x, y, BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
118
src/mode/Mode.h
118
src/mode/Mode.h
@ -1,12 +1,8 @@
|
||||
#ifndef MODE_H
|
||||
#define MODE_H
|
||||
|
||||
#define TIMER_COUNT 10
|
||||
|
||||
#include "BASICS.h"
|
||||
#include "display/Display.h"
|
||||
#include "Timer.h"
|
||||
#include "ModeBase.h"
|
||||
|
||||
enum ModeId {
|
||||
NONE,
|
||||
@ -20,25 +16,121 @@ enum ModeId {
|
||||
SPACE_INVADERS,
|
||||
NEW_YEAR,
|
||||
STARFIELD,
|
||||
MATRIX,
|
||||
};
|
||||
|
||||
template<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
|
||||
|
||||
@ -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
|
||||
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -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,43 +232,23 @@ 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";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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++) {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user