diff --git a/CMakeLists.txt b/CMakeLists.txt index 284162b..b383753 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,4 +30,4 @@ add_custom_target( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) -add_executable(Z_DUMMY_TARGET ${SRC_LIST} src/display/Display.h src/display/Pixel.h src/mode/GameOfLife/Cell.h) +add_executable(Z_DUMMY_TARGET ${SRC_LIST} src/mode/Test/Border.h) diff --git a/platformio.ini b/platformio.ini index 4b30716..f046059 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,3 +12,8 @@ platform = espressif32 board = esp32dev framework = arduino +lib_deps = https://github.com/adafruit/Adafruit_NeoPixel +upload_port = /dev/ttyUSB0 +upload_speed = 921600 +monitor_port = /dev/ttyUSB0 +monitor_speed = 115200 diff --git a/src/display/Display.h b/src/display/Display.h index 9b377a2..38c7dbd 100644 --- a/src/display/Display.h +++ b/src/display/Display.h @@ -2,83 +2,59 @@ #define DISPLAY_H #include "Pixel.h" +#include "Adafruit_NeoPixel.h" class Display { +public: + + const uint8_t width; + + const uint8_t height; + + const uint16_t pixelCount; + private: - uint8_t width; - - uint8_t height; - - Pixel *pixels; - - uint16_t pixelCount; - - bool dirty = true; + Adafruit_NeoPixel leds; public: Display(uint8_t width, uint8_t height) : - width(width), height(height) { - pixelCount = width * height; - pixels = (Pixel *) malloc(pixelCount * sizeof(Pixel)); - clear(); - } - - ~Display() { - if (pixels != nullptr) { - free(pixels); - pixels = nullptr; - } - } - - uint8_t getWidth() const { - return width; - } - - uint8_t getHeight() const { - return height; - } - - uint16_t getPixelCount() const { - return pixelCount; - } - - void set(uint8_t x, uint8_t y, Pixel pixel) { - if (x > width || y > height) { - Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height); - return; - } - *(pixels + y * width + x) = pixel; - dirty = true; - } - - void clear() { - for (Pixel *p = pixels; p < end(); p++) { - *p = {0, 0, 0}; - } - dirty = true; + width(width), height(height), + pixelCount(width * height), + leds(pixelCount, GPIO_NUM_13) { + // nothing } void setup() { - // TODO init IO + leds.begin(); + leds.setBrightness(8); + clear(); + } + + void set(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { + if (x >= width || y >= height) { + Serial.printf("ERROR: Cannot set pixel (%d/%d) in matrix (%d/%d).\n", x, y, width, height); + return; + } + leds.setPixelColor(y * width + x, r, g, b); + } + + void clear() { + leds.clear(); } void loop() { - if (!dirty) { - return; - } - dirty = false; - // TODO + leds.show(); } - Pixel *begin() { - return pixels; + void setBrightness(int brightness) { + leds.setBrightness(min(255, max(0, brightness))); } - Pixel *end() { - return pixels + pixelCount; + int getBrightness() { + return leds.getBrightness(); } }; diff --git a/src/display/Pixel.h b/src/display/Pixel.h index 74949c4..b85b9be 100644 --- a/src/display/Pixel.h +++ b/src/display/Pixel.h @@ -13,6 +13,11 @@ public: uint8_t b; + Pixel(uint8_t r, uint8_t g, uint8_t b) : + r(r), g(g), b(b) { + // nothing + } + bool isOn() const { return r != 0 || g != 0 || b != 0; } @@ -23,6 +28,14 @@ public: b = brightness; } + uint32_t toInt() const { + return (((r << 8) | g) << 8) | b; + } + + static Pixel gray(uint8_t brightness) { + return {brightness, brightness, brightness}; + } + }; #endif diff --git a/src/display/Vector.h b/src/display/Vector.h new file mode 100644 index 0000000..26378a0 --- /dev/null +++ b/src/display/Vector.h @@ -0,0 +1,27 @@ +#ifndef POSITION_H +#define POSITION_H + +#include "BASICS.h" + +class Vector { + +public: + + double x; + + double y; + + Vector(double x, double y) : + x(x), y(y) { + // nothing + } + + Vector(long degrees, double length) { + double radians = (double) degrees * DEG_TO_RAD; + x = cos(radians) * length; + y = sin(radians) * length; + } + +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index f6fb655..831f177 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,14 @@ #include "mode/Mode.h" #include "mode/GameOfLife/GameOfLife.h" #include "display/Display.h" +#include "mode/Pong/Pong.h" +#include "mode/Test/Border.h" enum ModeId { - NONE, GAME_OF_LIFE + NONE, BORDER, GAME_OF_LIFE, PONG }; -Display display(8, 16); +Display display(32, 8); ModeId newModeId = GAME_OF_LIFE; @@ -24,18 +26,58 @@ void unloadOldMode(); void loadNewMode(); +void setBrightness(int value); + void setup() { + delay(500); Serial.begin(115200); Serial.println("\n\n\nStartup!\n"); display.setup(); } +void setSpeed(double value); + +double speed = 1.0; + void loop() { + if (Serial.available()) { + int input = Serial.read(); + switch (input) { + case '0': + case '1': + case '2': + case '3': + newModeId = (ModeId) (input - '0'); + break; + case '+': + setBrightness(display.getBrightness() + 10); + break; + case '-': + setBrightness(max(1, display.getBrightness() - 10)); + break; + case ',': + setSpeed(speed / 1.1); + break; + case '.': + setSpeed(speed * 1.1); + break; + } + } checkMode(); display.loop(); stepMode(); } +void setBrightness(int value) { + display.setBrightness(value); + Serial.printf("Setting brightness to %5.1f%%\n", value / 2.55); +} + +void setSpeed(double value) { + speed = min(1000.0, max(0.001, value)); + Serial.printf("Setting speed to %6.2fx\n", value); +} + void checkMode() { if (currentModeId != newModeId) { unloadOldMode(); @@ -45,7 +87,6 @@ void checkMode() { void unloadOldMode() { if (mode != nullptr) { - Serial.printf("Unloading state \"%s\".\n", mode->getName()); delete mode; mode = nullptr; } @@ -55,15 +96,21 @@ void unloadOldMode() { void loadNewMode() { currentModeId = newModeId; lastMillis = 0; - Serial.printf("Loading state #%d.\n", currentModeId); + Serial.printf("Loading mode: #%d\n", currentModeId); switch (currentModeId) { case NONE: break; + case BORDER: + mode = new Border(&display); + break; case GAME_OF_LIFE: mode = new GameOfLife(&display); break; + case PONG: + mode = new Pong(&display); + break; } - Serial.printf("Loaded state \"%s\".\n", mode == nullptr ? "None" : mode->getName()); + Serial.printf("Loaded mode: %s\n\n", mode == nullptr ? "None" : mode->getName()); } void stepMode() { @@ -73,5 +120,5 @@ void stepMode() { millis_t currentMillis = millis(); millis_t dt = currentMillis - lastMillis; lastMillis = currentMillis; - mode->step(dt); + mode->step((millis_t) max(1.0, (double) dt * speed)); } diff --git a/src/mode/GameOfLife/Cell.h b/src/mode/GameOfLife/Cell.h index 81a2baf..02cf011 100644 --- a/src/mode/GameOfLife/Cell.h +++ b/src/mode/GameOfLife/Cell.h @@ -21,10 +21,12 @@ public: void spawn() { state = GROWING; + value = 0; } void kill() { state = DYING; + value = 255; } void animate(uint8_t dt) { @@ -49,6 +51,9 @@ public: } uint8_t getR() const { + if (state == ALIVE) { + return 255; + } if (state == DYING) { return value; } @@ -56,6 +61,9 @@ public: } uint8_t getG() const { + if (state == ALIVE) { + return 255; + } if (state == GROWING) { return value; } diff --git a/src/mode/GameOfLife/GameOfLife.h b/src/mode/GameOfLife/GameOfLife.h index 2e4240c..ee50f2e 100644 --- a/src/mode/GameOfLife/GameOfLife.h +++ b/src/mode/GameOfLife/GameOfLife.h @@ -9,32 +9,37 @@ class GameOfLife : public Mode { private: - const uint8_t width; - const uint8_t height; - const uint16_t count; + millis_t runtime = 0; + + uint8_t steps = 0; uint16_t aliveCount = 0; - millis_t runtime = 0; - uint8_t steps = 0; + uint16_t lastAliveCount = 0; Cell *cells; + Cell *last; + Cell *next; public: explicit GameOfLife(Display *display) : - Mode(display, "Game of Life"), - width(display->getWidth()), - height(display->getHeight()), - count(display->getPixelCount()) { - cells = (Cell *) malloc(count * sizeof(Cell)); - next = (Cell *) malloc(count * sizeof(Cell)); + Mode(display, "Game of Life") { + cells = (Cell *) malloc(display->pixelCount * sizeof(Cell)); + last = cells; + for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) { + cell->state = DEAD; + cell->value = 0; + } + next = (Cell *) malloc(display->pixelCount * sizeof(Cell)); + for (Cell *cell = next; cell < next + display->pixelCount; cell++) { + cell->state = DEAD; + cell->value = 0; + } } - ~GameOfLife() - - override { + ~GameOfLife() override { if (cells != nullptr) { free(cells); cells = nullptr; @@ -45,13 +50,13 @@ public: } } - void step(millis_t dt) - - override { + void step(millis_t dt) override { runtime += dt; - while (runtime % 100 == 0) { - if (steps++ == 0 || aliveCount == 0) { - steps = 0; + if (runtime >= 500) { + runtime = 0; + bool isSteady = lastAliveCount == aliveCount; + lastAliveCount = aliveCount; + if (steps++ == 0 || aliveCount == 0 || isSteady) { randomFill(); } else { nextGeneration(); @@ -63,7 +68,7 @@ public: private: void randomFill() { - for (Cell *cell = cells; cell < cells + count; cell++) { + for (Cell *cell = cells; cell < cells + display->pixelCount; cell++) { if (random(4) == 0) { if (!cell->isAlive()) { cell->spawn(); @@ -79,30 +84,48 @@ private: } void nextGeneration() { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell *cell = get(x, y); + memcpy(next, cells, display->pixelCount * sizeof(Cell)); + Cell *src = cells; + Cell *dst = next; + for (int y = 0; y < display->height; y++) { + for (int x = 0; x < display->width; x++) { uint8_t around = countAround(x, y); - if (cell->isAlive()) { + if (src->isAlive()) { if (around <= 2 || 4 <= around) { - cell->kill(); + dst->kill(); aliveCount--; } } else if (around == 3) { - cell->spawn(); + dst->spawn(); aliveCount++; } + src++; + dst++; } } - memcpy(cells, next, count); + memcpy(cells, next, display->pixelCount * sizeof(Cell)); + } + + void print() { + Cell *cell = cells; + for (int y = 0; y < display->height; y++) { + Serial.print("|"); + for (int x = 0; x < display->width; x++) { + Serial.print(cell->isAlive() ? "x|" : " |"); + cell++; + } + Serial.println(); + Serial.println(); + } } void animate(millis_t dt) { Cell *cell = cells; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (int y = 0; y < display->height; y++) { + for (int x = 0; x < display->width; x++) { cell->animate(dt); - display->set(x, y, {cell->getR(), cell->getG(), cell->getB()}); + int xx = (y % 2) == 0 ? display->width - x - 1 : x; + display->set(xx, y, cell->getR(), cell->getG(), cell->getB()); cell++; } } @@ -110,21 +133,20 @@ private: 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 + 0, y + 0) + countIfAlive(x + 1, y + 0) + + 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) { + if (x < 0 || y < 0 || x >= display->width || y >= display->height) { return 0; } return get(x, y)->isAlive() ? 1 : 0; } Cell *get(int x, int y) { - return cells + width * y + x; + return cells + display->width * y + x; } - }; #endif diff --git a/src/mode/Pong/Player.h b/src/mode/Pong/Player.h new file mode 100644 index 0000000..778e0bd --- /dev/null +++ b/src/mode/Pong/Player.h @@ -0,0 +1,16 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include "BASICS.h" + +class Player { + +public: + + int8_t position = 0; + + uint8_t score = 0; + +}; + +#endif diff --git a/src/mode/Pong/Pong.h b/src/mode/Pong/Pong.h new file mode 100644 index 0000000..c5d9bea --- /dev/null +++ b/src/mode/Pong/Pong.h @@ -0,0 +1,41 @@ +#ifndef PONG_H +#define PONG_H + +#include "mode/Mode.h" +#include "Player.h" +#include "display/Vector.h" + +class Pong : public Mode { + +private: + + Player player0; + + Player player1; + + Vector position; + + Vector velocity; + +public: + + explicit Pong(Display *display) : + Mode(display, "Pong"), + position(display->width / 2.0, display->height / 2.0), + velocity(random(360), exp10(1)) { + // nothing + } + + static int8_t randomSignum() { + return random(2) == 0 ? -1 : +1; + } + + ~Pong() override = default; + + void step(millis_t dt) override { + + } + +}; + +#endif diff --git a/src/mode/Test/Border.h b/src/mode/Test/Border.h new file mode 100644 index 0000000..1a3358c --- /dev/null +++ b/src/mode/Test/Border.h @@ -0,0 +1,31 @@ +#ifndef TEST_H +#define TEST_H + +#include "mode/Mode.h" + +class Border : public Mode { + +public: + + explicit Border(Display *display) : + Mode(display, "Border") { + // nothing + } + + ~Border() override = default; + + void step(millis_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, 255, 255, 255); + } else { + display->set(x, y, 0, 0, 0); + } + } + } + } + +}; + +#endif