diff --git a/Firmware/main/include/disp_tools.hpp b/Firmware/main/include/disp_tools.hpp index be0e6c9..f51afc4 100644 --- a/Firmware/main/include/disp_tools.hpp +++ b/Firmware/main/include/disp_tools.hpp @@ -19,22 +19,30 @@ public: } } bool get_pixel(int x, int y) { - // if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) - // assert(false); + if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) + assert(false); + assert(false); // Not implemented return true; // return disp_frame[y][x]; } void reset_pixel(int x, int y) { - // if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) - // assert(false); + if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) + assert(false); SMD::get().set_pixel(x, y, false); } void set_pixel(int x, int y) { - // if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) - // assert(false); - // + if (x < 0 || x >= DISP_WIDTH || y < 0 || y >= DISP_HEIGHT) + assert(false); + SMD::get().set_pixel(x, y, true); } + void set_pixel(int x, int y, bool on) { + if (on) { + set_pixel(x, y); + } else { + reset_pixel(x, y); + } + } void draw_to_display(); }; diff --git a/Firmware/main/src/hello_world_main.cpp b/Firmware/main/src/hello_world_main.cpp index b5c6f7a..fff5101 100644 --- a/Firmware/main/src/hello_world_main.cpp +++ b/Firmware/main/src/hello_world_main.cpp @@ -1,166 +1,690 @@ -/* - * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ +// 400x240 1-bit Tetris — centered board, HUD, correct polarity, upright font. +// Logical ON = BLACK, OFF = WHITE. No hidden inversion anywhere. #include #include #include -#include -#include -#include #include -#include "esp_chip_info.h" -#include "esp_flash.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "sdkconfig.h" +#include +#include +#include +#include #include "display.hpp" - -#include "bat_mon.hpp" #include "driver/i2c_master.h" #include "driver/spi_master.h" #include "i2c_global.hpp" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include -#include +namespace cfg { +constexpr int BoardW = 10; +constexpr int BoardH = 20; +constexpr int CellPx = 12; // 10x20 -> 120x240 +constexpr int FrameW = 400; +constexpr int FrameH = 240; -#include "GridWindow.hpp" -#include "TextWindow.hpp" -#include "display.hpp" +// game feel +constexpr int DropMsStart = 650; +constexpr int DropMsMin = 90; +constexpr int DropFastMs = 20; +constexpr int LevelStepClr = 10; +constexpr int LockDelayMs = 380; -FbTty tty; +// input repeat (snappy) +constexpr int DAS_ms = 90; +constexpr int ARR_ms = 15; +// frame pacing +constexpr int FrameMs = 12; +} // namespace cfg +// ───────────────────────────────────────────────────────────────────────────── +// Interfaces +struct IFramebuffer { + virtual ~IFramebuffer() = default; + virtual void drawPixel(int x, int y, bool on) = 0; // on=true => black + virtual void clear(bool on) = 0; // fill full screen to on/off + virtual int width() const = 0; + virtual int height() const = 0; +}; + +struct InputState { + bool left = false, right = false, down = false, rotate = false, back = false; +}; +struct IInput { + virtual ~IInput() = default; + virtual InputState readState() = 0; +}; + +struct IClock { + virtual ~IClock() = default; + virtual uint32_t millis() = 0; + virtual void sleep_ms(uint32_t ms) = 0; +}; + +// ───────────────────────────────────────────────────────────────────────────── +// ESP-IDF Adapters (NO inversion here) +struct PlatformFramebuffer final : IFramebuffer { + int width() const override { return cfg::FrameW; } + int height() const override { return cfg::FrameH; } + + void drawPixel(int x, int y, bool on) override { + if (x < 0 || y < 0 || x >= width() || y >= height()) + return; + // Logical ON must be BLACK on panel: + DispTools::get().set_pixel(x, y, on); // on=true -> black; on=false -> white + } + + void clear(bool on) override { + // Explicit fill to guarantee polarity: no DispTools::clear() shortcuts. + for (int y = 0; y < height(); ++y) + for (int x = 0; x < width(); ++x) + DispTools::get().set_pixel(x, y, on); + } +}; + +struct PlatformInput final : IInput { + InputState readState() override { + InputState s{}; + const uint8_t p = Buttons::get().get_pressed(); + if (p & BTN_LEFT) + s.left = true; + if (p & BTN_RIGHT) + s.right = true; + if (p & BTN_DOWN) + s.down = true; + if (p & BTN_A) + s.rotate = true; // rotate + if (p & BTN_B) + s.back = true; // pause/back + return s; + } +}; + +struct PlatformClock final : IClock { + uint32_t millis() override { + TickType_t t = xTaskGetTickCount(); + return (uint32_t) ((uint64_t) t * 1000ULL / configTICK_RATE_HZ); + } + void sleep_ms(uint32_t ms) override { + if (ms) + vTaskDelay(pdMS_TO_TICKS(ms)); + } +}; + +static inline uint32_t elapsed_ms(uint32_t a, uint32_t b) { return b - a; } + +// ───────────────────────────────────────────────────────────────────────────── +// Pieces +using RotGrid = std::array; +using PieceR = std::array; +constexpr char _ = '.', X = '#'; + +static const std::array TET = {{ + PieceR{{RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _}, + RotGrid{_, _, X, _, _, _, X, _, _, _, X, _, _, _, X, _}, + RotGrid{_, _, _, _, X, X, X, X, _, _, _, _, _, _, _, _}, + RotGrid{_, X, _, _, _, X, _, _, _, X, _, _, _, X, _, _}}}, + PieceR{{RotGrid{X, _, _, _, X, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, X, _, _, X, _, _, _, X, _, _, _, _, _, _}, + RotGrid{_, _, _, _, X, X, X, _, _, _, X, _, _, _, _, _}, + RotGrid{_, X, _, _, _, X, _, _, X, _, _, _, _, _, _, _}}}, + PieceR{{RotGrid{_, _, X, _, X, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, _, _, _, X, _, _, _, X, X, _, _, _, _, _}, + RotGrid{_, _, _, _, X, X, X, _, X, _, _, _, _, _, _, _}, + RotGrid{X, X, _, _, _, X, _, _, _, X, _, _, _, _, _, _}}}, + PieceR{{RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, X, _, _, X, X, _, _, _, _, _, _, _, _, _}}}, + PieceR{{RotGrid{_, X, X, _, X, X, _, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, _, _, _, X, X, _, _, _, X, _, _, _, _, _}, + RotGrid{_, _, _, _, _, X, X, _, X, X, _, _, _, _, _, _}, + RotGrid{X, _, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}}, + PieceR{{RotGrid{_, X, _, _, X, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, X, _, _, _, X, X, _, _, X, _, _, _, _, _, _}, + RotGrid{_, _, _, _, X, X, X, _, _, X, _, _, _, _, _, _}, + RotGrid{_, X, _, _, X, X, _, _, _, X, _, _, _, _, _, _}}}, + PieceR{{RotGrid{X, X, _, _, _, X, X, _, _, _, _, _, _, _, _, _}, + RotGrid{_, _, X, _, _, X, X, _, _, X, _, _, _, _, _, _}, + RotGrid{_, _, _, _, X, X, _, _, _, X, X, _, _, _, _, _}, + RotGrid{_, X, _, _, X, X, _, _, X, _, _, _, _, _, _, _}}}, +}}; + +static inline bool cell_of(const PieceR& p, int rot, int x, int y) { return p[rot][y * 4 + x] == X; } + +// ───────────────────────────────────────────────────────────────────────────── +// Board +class Board { +public: + Board() { clear(); } + void clear() { a.fill(0); } + uint8_t get(int x, int y) const { return a[y * cfg::BoardW + x]; } + void set(int x, int y, uint8_t v) { a[y * cfg::BoardW + x] = v; } + + bool collides(int px, int py, int rot, int pidx) const { + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[pidx], rot, xx, yy)) { + int x = px + xx, y = py + yy; + if (x < 0 || x >= cfg::BoardW || y >= cfg::BoardH) + return true; + if (y >= 0 && get(x, y)) + return true; + } + return false; + } + void lock(int px, int py, int rot, int pidx) { + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[pidx], rot, xx, yy)) { + int x = px + xx, y = py + yy; + if (x >= 0 && x < cfg::BoardW && y >= 0 && y < cfg::BoardH) + set(x, y, 1); + } + } + int clearLines() { + int cleared = 0; + for (int y = cfg::BoardH - 1; y >= 0; --y) { + bool full = true; + for (int x = 0; x < cfg::BoardW; ++x) { + if (!get(x, y)) { + full = false; + break; + } + } + if (full) { + ++cleared; + for (int yy = y; yy > 0; --yy) + for (int x = 0; x < cfg::BoardW; ++x) + set(x, yy, get(x, yy - 1)); + for (int x = 0; x < cfg::BoardW; ++x) + set(x, 0, 0); + ++y; + } + } + return cleared; + } + +private: + std::array a{}; +}; + +// ───────────────────────────────────────────────────────────────────────────── +// 5x7 HUD font — stored with **LSB = top row** so y increases downward. +// (This orientation fixes the upside-down issue.) +struct Font5x7 { + static uint8_t glyph(char c) { + switch (c) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'S': + return 10; + case 'C': + return 11; + case 'O': + return 12; + case 'R': + return 13; + case 'E': + return 14; + case 'L': + return 15; + case 'I': + return 16; + case 'N': + return 17; + case 'X': + return 18; + case 'T': + return 19; + case 'V': + return 20; + case 'A': + return 21; + default: + return 255; + } + } + // columns × 7 rows (bit0 = top, bit6 = bottom) + static const uint8_t data[22][5]; +}; +const uint8_t Font5x7::data[22][5] = { + /*0*/ {0b0111110, 0b1000001, 0b1000001, 0b1000001, 0b0111110}, + /*1*/ {0b0000000, 0b1000010, 0b1111111, 0b1000000, 0b0000000}, + /*2*/ {0b11100010, 0b10010001, 0b10001001, 0b10001001, 0b10000110}, + /*3*/ {0b01000010, 0b10000001, 0b10001001, 0b10001001, 0b01110110}, + /*4*/ {0b00011100, 0b00010010, 0b00010001, 0b11111111, 0b00010000}, + /*5*/ {0b01001111, 0b10001001, 0b10001001, 0b10001001, 0b01110001}, + /*6*/ {0b01111110, 0b10001001, 0b10001001, 0b10001001, 0b01110010}, + /*7*/ {0b00000001, 0b11100001, 0b00010001, 0b00001001, 0b00000111}, + /*8*/ {0b01110110, 0b10001001, 0b10001001, 0b10001001, 0b01110110}, + /*9*/ {0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01111110}, + /*S*/ {0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01110010}, + /*C*/ {0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01000010}, + /*O*/ {0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01111110}, + /*R*/ {0b11111111, 0b00001001, 0b00011001, 0b01101001, 0b10000110}, + /*E*/ {0b11111111, 0b10001001, 0b10001001, 0b10001001, 0b10000001}, + /*L*/ {0b11111111, 0b10000000, 0b10000000, 0b10000000, 0b10000000}, + /*I*/ {0b10000001, 0b10000001, 0b11111111, 0b10000001, 0b10000001}, + /*N*/ {0b11111111, 0b00000110, 0b00011000, 0b01100000, 0b11111111}, + /*X*/ {0b11000011, 0b00100100, 0b00011000, 0b00100100, 0b11000011}, + /*T*/ {0b00000001, 0b00000001, 0b11111111, 0b00000001, 0b00000001}, + /*V*/ {0b00000111, 0b00111000, 0b11000000, 0b00111000, 0b00000111}, + /*A*/ {0b11111110, 0b00010001, 0b00010001, 0b00010001, 0b11111110}, +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Renderer (centered board + HUD) +class Renderer { +public: + Renderer(IFramebuffer& fb) : fb(fb) { + bw = cfg::BoardW * cfg::CellPx; // 120 + bh = cfg::BoardH * cfg::CellPx; // 240 + ox = (fb.width() - bw) / 2; // 140 + oy = (fb.height() - bh) / 2; // 0 + } + + void render(const Board& b, int px, int py, int prot, int pidx, int score, int level, int lines, int nextIdx, + bool ghost) { + fb.clear(false); // white bg (off) + + // playfield frame + rect(ox - 2, oy - 2, bw + 4, bh + 4, true); + + // settled + for (int y = 0; y < cfg::BoardH; ++y) + for (int x = 0; x < cfg::BoardW; ++x) + if (b.get(x, y)) + drawCell(x, y, true); + + // ghost + if (ghost) + drawGhost(b, px, py, prot, pidx); + + // active + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[pidx], prot, xx, yy)) { + int gx = px + xx, gy = py + yy; + if (gx >= 0 && gx < cfg::BoardW && gy >= 0 && gy < cfg::BoardH) + drawCell(gx, gy, true); + } + + // HUD (right side) + int hudX = ox + bw + 16; + int y = oy + 9; // +1 padding to avoid 1px visual cutoff + drawLabel(hudX, y, "SCORE"); + y += 12; + drawNumber(hudX, y, score); + y += 19; + drawLabel(hudX, y, "LEVEL"); + y += 12; + drawNumber(hudX, y, level); + y += 19; + drawLabel(hudX, y, "LINES"); + y += 12; + drawNumber(hudX, y, lines); + y += 19; + drawLabel(hudX, y, "NEXT"); + y += 7; + + const int p = cfg::CellPx; + const int nx = hudX, ny = y + 4; + rect(nx - 2, ny - 2, 4 * p + 4, 4 * p + 4, true); + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[nextIdx], 0, xx, yy)) + fillRect(nx + xx * p, ny + yy * p, p, p, true); + + // removed surrounding HUD border rectangle that caused panel outline + // rect(hudX - 8, oy, cfg::FrameW - (hudX - 8) - 8, cfg::FrameH - oy * 2, true); + + DispTools::get().draw_to_display(); + } + +private: + IFramebuffer& fb; + int ox = 0, oy = 0, bw = 0, bh = 0; + + void putPixel(int x, int y, bool on) { fb.drawPixel(x, y, on); } + void hline(int x, int y, int w, bool on) { + for (int i = 0; i < w; ++i) + putPixel(x + i, y, on); + } + void vline(int x, int y, int h, bool on) { + for (int i = 0; i < h; ++i) + putPixel(x, y + i, on); + } + void rect(int x, int y, int w, int h, bool on) { + hline(x, y, w, on); + hline(x, y + h - 1, w, on); + vline(x, y, h, on); + vline(x + w - 1, y, h, on); + } + void fillRect(int x, int y, int w, int h, bool on) { + for (int yy = 0; yy < h; ++yy) + for (int xx = 0; xx < w; ++xx) + putPixel(x + xx, y + yy, on); + } + + void drawCell(int cx, int cy, bool on) { + int x0 = ox + cx * cfg::CellPx; + int y0 = oy + cy * cfg::CellPx; + fillRect(x0, y0, cfg::CellPx, cfg::CellPx, on); + } + + void drawGhost(const Board& b, int px, int py, int prot, int pidx) { + int gy = py; + while (true) { + bool col = false; + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[pidx], prot, xx, yy)) { + int nx = px + xx, ny = gy + yy + 1; + if (ny >= cfg::BoardH || (ny >= 0 && nx >= 0 && nx < cfg::BoardW && b.get(nx, ny))) + col = true; + } + if (col) + break; + ++gy; + } + for (int yy = 0; yy < 4; ++yy) + for (int xx = 0; xx < 4; ++xx) + if (cell_of(TET[pidx], prot, xx, yy)) { + int gx = px + xx, gy2 = gy + yy; + if (gx < 0 || gx >= cfg::BoardW || gy2 < 0 || gy2 >= cfg::BoardH) + continue; + int x0 = ox + gx * cfg::CellPx, y0 = oy + gy2 * cfg::CellPx; + rect(x0, y0, cfg::CellPx, cfg::CellPx, true); + } + } + + // 5x7 text — LSB = top, so rows draw y..y+6. No baseline offset needed. + void drawChar5x7(int x, int y, char c, bool on) { + uint8_t gi = Font5x7::glyph(c); + if (gi == 255) + return; + for (int col = 0; col < 5; ++col) { + uint8_t bits = Font5x7::data[gi][col]; + for (int row = 0; row < 8; row++) { + if (bits & (1u << row)) { + putPixel(x + col, y + row, on); + } + } + } + } + void drawLabel(int x, int y, const char* s) { + for (int i = 0; s[i]; ++i) + drawChar5x7(x + i * 6, y, s[i], true); + } + void drawNumber(int x, int y, int n) { + char buf[16]; + int len = snprintf(buf, sizeof(buf), "%d", n); + for (int i = 0; i < len; ++i) + drawChar5x7(x + i * 6, y, buf[i], true); + } +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Bag +class Bag { +public: + Bag() : rng(std::random_device{}()) { refill(); } + int next() { + if (bag.empty()) + refill(); + int t = bag.back(); + bag.pop_back(); + return t; + } + +private: + void refill() { + bag = {0, 1, 2, 3, 4, 5, 6}; + std::shuffle(bag.begin(), bag.end(), rng); + } + std::vector bag; + std::mt19937 rng; +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Game +struct ScoreState { + int level = 0, score = 0, lines = 0, dropMs = cfg::DropMsStart; +}; + +class Game { +public: + Game(IFramebuffer& fb, IInput& in, IClock& clk) : fb(fb), input(in), clock(clk), renderer(fb) { + nextPiece = bag.next(); + spawn(); + lastFall = clock.millis(); + touchTime = lastFall; + } + + void step() { + if (!running) { + renderEndOnce(); + return; + } + const uint32_t now = clock.millis(); + InputState st = input.readState(); + + if (st.back && !backPrev) + paused = !paused; + backPrev = st.back; + if (paused) { + paintHUD(); + return; + } + + if (st.rotate && !rotPrev) + tryRotate(+1); + rotPrev = st.rotate; + + handleHorizontal(st, now); + + const int g = st.down ? cfg::DropFastMs : score.dropMs; + if (elapsed_ms(lastFall, now) >= (uint32_t) g) { + lastFall = now; + if (!tryMove(0, 1)) { + if (!touchingGround) { + touchingGround = true; + touchTime = now; + } else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) { + lockAndAdvance(); + touchingGround = false; + } + } else + touchingGround = false; + } + + paintHUD(); + } + +private: + IFramebuffer& fb; + IInput& input; + IClock& clock; + Renderer renderer; + Board board; + Bag bag; + ScoreState score; + bool running = true, paused = false, touchingGround = false; + bool rotPrev = false, lHeld = false, rHeld = false, backPrev = false; + uint32_t lHoldStart = 0, rHoldStart = 0, lLastRep = 0, rLastRep = 0, lastFall = 0, touchTime = 0; + int current = 0, nextPiece = 0, px = 3, py = -2, rot = 0; + + void paintHUD() { + renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true); + } + + void renderEndOnce() { + fb.clear(true); + DispTools::get().draw_to_display(); + clock.sleep_ms(120); + fb.clear(false); + DispTools::get().draw_to_display(); + clock.sleep_ms(120); + paintHUD(); + } + + bool tryMove(int dx, int dy) { + if (!board.collides(px + dx, py + dy, rot, current)) { + px += dx; + py += dy; + return true; + } + return false; + } + bool tryRotate(int d) { + int nr = (rot + d + 4) % 4; + static const int kicks[][2] = {{0, 0}, {-1, 0}, {1, 0}, {-2, 0}, {2, 0}, {0, -1}}; + for (auto& k: kicks) + if (!board.collides(px + k[0], py + k[1], nr, current)) { + px += k[0]; + py += k[1]; + rot = nr; + return true; + } + return false; + } + void handleHorizontal(const InputState& st, uint32_t now) { + if (st.left && st.right) { + lHeld = rHeld = false; + return; + } + if (st.left) { + if (!lHeld) { + lHeld = true; + rHeld = false; + tryMove(-1, 0); + lHoldStart = now; + lLastRep = now; + } else { + uint32_t s = elapsed_ms(lHoldStart, now), r = elapsed_ms(lLastRep, now); + if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { + (void) tryMove(-1, 0); + lLastRep = now; + } + } + } else + lHeld = false; + if (st.right) { + if (!rHeld) { + rHeld = true; + lHeld = false; + tryMove(+1, 0); + rHoldStart = now; + rLastRep = now; + } else { + uint32_t s = elapsed_ms(rHoldStart, now), r = elapsed_ms(rLastRep, now); + if (s >= (uint32_t) cfg::DAS_ms && r >= (uint32_t) cfg::ARR_ms) { + (void) tryMove(+1, 0); + rLastRep = now; + } + } + } else + rHeld = false; + } + void lockAndAdvance() { + board.lock(px, py, rot, current); + int c = board.clearLines(); + if (c) { + static const int pts[5] = {0, 100, 300, 500, 800}; + score.lines += c; + score.score += pts[c] * (score.level + 1); + int nl = score.lines / cfg::LevelStepClr; + if (nl != score.level) { + score.level = nl; + score.dropMs = std::max(cfg::DropMsMin, cfg::DropMsStart - score.level * 50); + } + } + spawn(); + } + void spawn() { + current = nextPiece; + nextPiece = bag.next(); + px = 3; + py = -2; + rot = 0; + if (board.collides(px, py, rot, current)) + running = false; + } +}; + +// ───────────────────────────────────────────────────────────────────────────── +// App +class App { +public: + App(IFramebuffer& fb, IInput& in, IClock& clk) : fb(fb), input(in), clock(clk) { + game = new Game(fb, input, clock); + } + ~App() { delete game; } + void runForever() { + uint32_t last = clock.millis(); + while (true) { + game->step(); + uint32_t now = clock.millis(); + uint32_t dt = elapsed_ms(last, now); + if (dt < (uint32_t) cfg::FrameMs) + clock.sleep_ms((uint32_t) cfg::FrameMs - dt); + last = clock.millis(); + } + } + +private: + IFramebuffer& fb; + IInput& input; + IClock& clock; + Game* game = nullptr; +}; + +// ───────────────────────────────────────────────────────────────────────────── +// Entry extern "C" void app_main() { - esp_pm_config_t pm_config = { - .max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ, .min_freq_mhz = 16, .light_sleep_enable = true}; - // ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); - printf("Hello world!\n"); - // TODO: Where to put that? - ESP_ERROR_CHECK(esp_sleep_enable_gpio_wakeup()); - // For some reason, calling it here hangs on startup, sometimes - // ESP_ERROR_CHECK(gpio_install_isr_service(0)); PowerHelper::get(); Shutdowner::get(); Buttons::get(); - ESP_ERROR_CHECK(gpio_install_isr_service(0)); + gpio_install_isr_service(0); Shutdowner::get().install_isr(); PowerHelper::get().install_isr(); Buttons::get().install_isr(); I2cGlobal::get(); - BatMon::get(); SpiGlobal::get(); - SMD::get(); - SMD::get().clear(); - DispTools::get().clear(); - // DispTools::get().draw_line(0, 0, 399, 239); - // DispTools::get().draw_circle(100, 100, 20); - DispTools::get().draw_to_display(); - tty.putstr("Hello\nworld!"); - DispTools::get().draw_to_display(); - int rx = 30, ry = 30; + static PlatformFramebuffer fb; + static PlatformInput input; + static PlatformClock clock; - int lastmove = 0; - - EventLoop loop; - SMDSurface surface(&loop); - - surface.set_window>(); - - GridWindow* window = - static_cast*>(surface.get_window()); - window->set_window, std::string>>(0, 0, &loop, "hello"); - window->set_window, std::string>>(0, 1, &loop, "hello1"); - window->set_window, 2, 2>>(1, 0); - GridWindow, 2, 2>* window2 = - static_cast, 2, 2>*>( - window->get_subsurface(1, 0).get_window()); - window->set_window, std::string>>(1, 1, &loop, "hello3"); - - window2->set_window, std::string>>( - 0, 0, &loop, "hello2"); - window2->set_window, std::string>>( - 0, 1, &loop, "hello4"); - window2->set_window, std::string>>( - 1, 0, &loop, "hello5"); - window2->set_window, std::string>>( - 1, 1, &loop, "hello6"); - - auto* tl_text = static_cast, std::string>*>( - window->get_subsurface(0, 0).get_window()); - - surface.handle(SurfaceResizeEvent{DISP_WIDTH, DISP_HEIGHT}); - std::thread loop_thread{[&] { loop.run([&] { DispTools::get().draw_to_display(); }); }}; - - uint8_t old_pressed = 0; - - while (true) { - // SMD::clear(); - // printf("Voltage: %f\n", BatMon::get_voltage()); - // DispTools::get().clear(); - // tty.reset(); - - uint8_t pressed = Buttons::get().get_pressed(); - if ((pressed & BTN_LEFT) && !(old_pressed & BTN_LEFT)) - surface.push(KeyboardEvent{Key::Left}); - if ((pressed & BTN_DOWN) && !(old_pressed & BTN_DOWN)) - surface.push(KeyboardEvent{Key::Down}); - if ((pressed & BTN_UP) && !(old_pressed & BTN_UP)) - surface.push(KeyboardEvent{Key::Up}); - if ((pressed & BTN_RIGHT) && !(old_pressed & BTN_RIGHT)) - surface.push(KeyboardEvent{Key::Right}); - if ((pressed & BTN_SELECT) && !(old_pressed & BTN_SELECT)) - surface.push(KeyboardEvent{Key::Escape}); - if ((pressed & BTN_START) && !(old_pressed & BTN_START)) - surface.push(KeyboardEvent{Key::Enter}); - - old_pressed = pressed; - - if (pressed == 0 && !PowerHelper::get().is_slow()) - lastmove++; - else if (pressed != 0) { - lastmove = 0; - PowerHelper::get().set_slow(false); - } - - if (lastmove > 20) { - lastmove = 0; - PowerHelper::get().set_slow(true); - } - - bool slow = PowerHelper::get().is_slow(); - tl_text->push(TextUpdateEvent(std::format("{:.1f}mA {:.1f}V {:.1f}mAh {}\n Buttons: {:08b}", - BatMon::get().get_current(), BatMon::get().get_voltage(), - BatMon::get().get_charge(), slow ? "S" : "", pressed))); - - // if (rx < 30) - // rx = 30; - // if (rx > 370) - // rx = 370; - // if (ry < 30) - // ry = 30; - // if (ry > 210) - // ry = 210; - // // tty.fmt("Button: {}", pressed); - // DispTools::get().draw_circle(rx, ry, 20); - // // printf("Restarting in %d seconds...\n", i); - // DispTools::get().draw_to_display(); - PowerHelper::get().delay(10000, 30); - } - // printf("Restarting now.\n"); - // fflush(stdout); - // esp_restart(); - loop_thread.join(); + static App* app = nullptr; + app = new App(fb, input, clock); + app->runForever(); }