This commit is contained in:
2025-10-05 22:25:19 +02:00
parent 95a946e47f
commit 589c598b01
2 changed files with 673 additions and 141 deletions

View File

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

View File

@@ -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 <buttons.hpp>
#include <cstdint>
#include <disp_tools.hpp>
#include <disp_tty.hpp>
#include <esp_pm.h>
#include <inttypes.h>
#include <stdio.h>
#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 <driver/gpio.h>
#include <power_helper.hpp>
#include <shutdowner.hpp>
#include <spi_global.hpp>
#include "display.hpp"
#include "bat_mon.hpp"
#include "driver/i2c_master.h"
#include "driver/spi_master.h"
#include "i2c_global.hpp"
#include <driver/gpio.h>
#include <esp_sleep.h>
#include <memory>
#include <power_helper.hpp>
#include <shutdowner.hpp>
#include <spi_global.hpp>
#include <string>
#include <algorithm>
#include <array>
#include <random>
#include <vector>
#include <thread>
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<char, 16>;
using PieceR = std::array<RotGrid, 4>;
constexpr char _ = '.', X = '#';
static const std::array<PieceR, 7> 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<uint8_t, cfg::BoardW * cfg::BoardH> 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<int> 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<SMDSurface, 2, 2>>();
GridWindow<SMDSurface, 2, 2>* window =
static_cast<GridWindow<SMDSurface, 2, 2>*>(surface.get_window());
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(0, 0, &loop, "hello");
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(0, 1, &loop, "hello1");
window->set_window<GridWindow<SubSurface<SMDSurface>, 2, 2>>(1, 0);
GridWindow<SubSurface<SMDSurface>, 2, 2>* window2 =
static_cast<GridWindow<SubSurface<SMDSurface>, 2, 2>*>(
window->get_subsurface(1, 0).get_window());
window->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(1, 1, &loop, "hello3");
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
0, 0, &loop, "hello2");
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
0, 1, &loop, "hello4");
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
1, 0, &loop, "hello5");
window2->set_window<TextWindow<SubSurface<SMDSurface>, std::string>>(
1, 1, &loop, "hello6");
auto* tl_text = static_cast<TextWindow<SubSurface<SMDSurface>, 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::string>(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();
}