This commit is contained in:
2025-10-06 09:34:33 +02:00
parent 126d377836
commit 8b8d9d3a55

View File

@@ -16,7 +16,7 @@
#include <shutdowner.hpp>
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_pm.h"
#include "esp_pm.h" // power management (guard usage by CONFIG_PM_ENABLE)
#include "esp_system.h"
#include <algorithm>
@@ -35,6 +35,7 @@
#include "i2c_global.hpp"
// Battery monitor header (conditionally included)
#include <bat_mon.hpp>
#include "power_helper.hpp"
namespace cfg {
constexpr int BoardW = 10;
@@ -128,8 +129,10 @@ struct PlatformClock final : IClock {
return (uint32_t) ((uint64_t) t * 1000ULL / configTICK_RATE_HZ);
}
void sleep_ms(uint32_t ms) override {
if (ms)
vTaskDelay(pdMS_TO_TICKS(ms));
// Pass a longer delay when slow mode is active, cap normal delay for responsiveness
int slow_ms = (int) ms; // allow full requested sleep when slow
int normal_ms = (int) std::min<uint32_t>(ms, 30); // cap active-mode sleep to 30ms
PowerHelper::get().delay(slow_ms, normal_ms);
}
};
@@ -296,16 +299,20 @@ struct Font5x7 {
case '-':
return 28;
case 'G':
return 29; // newly added glyph
return 29;
case 'P':
return 30; // added
case 'D':
return 31; // added
// space handled specially in drawText
default:
return 255;
}
}
// columns × 7 rows (bit0 = top, bit6 = bottom)
static const uint8_t data[30][5];
static const uint8_t data[32][5];
};
const uint8_t Font5x7::data[30][5] = {
const uint8_t Font5x7::data[32][5] = {
/*0*/ {0b0111110, 0b1000001, 0b1000001, 0b1000001, 0b0111110},
/*1*/ {0b0000000, 0b1000010, 0b1111111, 0b1000000, 0b0000000},
/*2*/ {0b11100010, 0b10010001, 0b10001001, 0b10001001, 0b10000110},
@@ -335,7 +342,9 @@ const uint8_t Font5x7::data[30][5] = {
/*.*/ {0b00000000, 0b00000000, 0b01100000, 0b01100000, 0b00000000}, // wider centered dot (2 cols)
/*:*/ {0b00000000, 0b00000000, 0b00110110, 0b00110110, 0b00000000}, // beefier colon (two stacked 2x2 dots)
/*-*/ {0b00010000, 0b00010000, 0b00010000, 0b00010000, 0b00010000},
/*G*/ {0b01111110, 0b10000001, 0b10001001, 0b10001001, 0b01111010}, // refined G with inner bar & notch
/*G*/ {0b01111110, 0b10000001, 0b10001001, 0b10001001, 0b01111010},
/*P*/ {0b11111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110},
/*D*/ {0b11111111, 0b10000001, 0b10000001, 0b01000010, 0b00111100},
};
// ─────────────────────────────────────────────────────────────────────────────
@@ -350,7 +359,7 @@ public:
}
void render(const Board& b, int px, int py, int prot, int pidx, int score, int level, int lines, int nextIdx,
bool ghost) {
bool ghost, bool paused) {
fb.clear(false); // white background
drawBatteryOverlay();
@@ -404,6 +413,8 @@ public:
if (cell_of(TET[nextIdx], 0, xx, yy))
drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1);
if (paused)
drawPausedOverlay();
DispTools::get().draw_to_display();
}
@@ -438,6 +449,30 @@ public:
putPixel(ix0 + xx, iy0 + yy, true);
}
void drawPausedOverlay() {
const char* txt = "PAUSED";
int len = 6;
int w = len * 6 - 1; // width in pixels used by text
int h = 8;
int cx = (fb.width() - w) / 2;
int cy = (fb.height() - h) / 2 - 10;
// Background wipe (white) then border
int padX = 5, padY = 4;
for (int y = -padY; y < h + padY; ++y)
for (int x = -padX; x < w + padX; ++x)
fb.drawPixel(cx + x, cy + y, false);
// Solid border
for (int x = -padX; x < w + padX; ++x) {
fb.drawPixel(cx + x, cy - padY, true);
fb.drawPixel(cx + x, cy + h + padY - 1, true);
}
for (int y = -padY; y < h + padY; ++y) {
fb.drawPixel(cx - padX, cy + y, true);
fb.drawPixel(cx + w + padX - 1, cy + y, true);
}
drawText(cx, cy, txt);
}
private:
IFramebuffer& fb;
int ox = 0, oy = 0, bw = 0, bh = 0;
@@ -733,51 +768,103 @@ 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;
lastFall = clock.millis();
touchTime = lastFall;
lastOverlayUpd = lastFall;
dirty = true;
}
void step() {
const uint32_t now = clock.millis();
overlayTick(now);
if (!running) {
renderEndOnce();
if (dirty)
paintHUD();
return;
}
const uint32_t now = clock.millis();
InputState st = input.readState();
if (st.back && !backPrev)
InputState st = input.readState();
if (st.back && !backPrev) {
paused = !paused;
PowerHelper::get().set_slow(paused); // engage/disengage slow mode
dirty = true;
}
backPrev = st.back;
if (paused) {
paintHUD();
if (dirty)
paintHUD();
return;
}
if (st.rotate && !rotPrev)
tryRotate(+1);
// Rotation
if (st.rotate && !rotPrev) {
if (tryRotate(+1))
dirty = true;
}
rotPrev = st.rotate;
// Horizontal movement + autorepeat
handleHorizontal(st, now);
// Gravity / soft drop
const int g = st.down ? cfg::DropFastMs : score.dropMs;
if (elapsed_ms(lastFall, now) >= (uint32_t) g) {
lastFall = now;
if (!tryMove(0, 1)) {
if (!tryMoveInternal(0, 1)) {
if (!touchingGround) {
touchingGround = true;
touchTime = now;
} else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) {
lockAndAdvance();
touchingGround = false;
}
} else
} else {
touchingGround = false;
}
}
paintHUD();
if (dirty)
paintHUD();
}
uint32_t recommendedSleepMs(uint32_t now) const {
if (dirty)
return 0;
if (paused || !running) {
uint32_t untilOverlay =
(lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0;
uint32_t cap = paused ? 2000u : 500u; // allow longer idle while paused
if (untilOverlay > cap)
untilOverlay = cap;
return untilOverlay;
}
// Estimate next gravity event
uint32_t g = score.dropMs;
uint32_t sinceFall = elapsed_ms(lastFall, now);
uint32_t untilDrop = (sinceFall >= g) ? 0 : (g - sinceFall);
if (touchingGround) {
uint32_t sinceTouch = elapsed_ms(touchTime, now);
uint32_t untilLock = (sinceTouch >= (uint32_t) cfg::LockDelayMs) ? 0 : (cfg::LockDelayMs - sinceTouch);
untilDrop = std::min(untilDrop, untilLock);
}
// Overlay may force earlier wake
uint32_t untilOverlay =
(lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0;
uint32_t sleep = std::min(untilDrop, untilOverlay);
// Provide a max sleep to keep input latency reasonable
if (sleep > maxIdleSleepMs)
sleep = maxIdleSleepMs;
return sleep;
}
bool isPaused() const { return paused; }
private:
// Power-aware overlay throttling
static constexpr uint32_t overlayIntervalMs = 500; // base interval (paused uses multiplier)
static constexpr uint32_t maxIdleSleepMs = 120; // cap to keep input responsive
IFramebuffer& fb;
IInput& input;
IClock& clock;
@@ -790,28 +877,34 @@ private:
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;
// Dirty rendering & overlay
bool dirty = false;
uint32_t lastOverlayUpd = 0;
void overlayTick(uint32_t now) {
uint32_t interval = paused ? overlayIntervalMs * 8 : overlayIntervalMs; // 4s when paused
if (elapsed_ms(lastOverlayUpd, now) >= interval) {
lastOverlayUpd = now;
dirty = true;
}
}
void paintHUD() {
renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true);
renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true, paused);
dirty = false;
}
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) {
bool tryMoveInternal(int dx, int dy) {
if (!board.collides(px + dx, py + dy, rot, current)) {
px += dx;
py += dy;
dirty = true;
return true;
}
return false;
}
bool tryMove(int dx, int dy) { return tryMoveInternal(dx, dy); }
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}};
@@ -819,7 +912,8 @@ private:
if (!board.collides(px + k[0], py + k[1], nr, current)) {
px += k[0];
py += k[1];
rot = nr;
rot = nr;
dirty = true;
return true;
}
return false;
@@ -833,29 +927,30 @@ private:
if (!lHeld) {
lHeld = true;
rHeld = false;
tryMove(-1, 0);
(void) tryMoveInternal(-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);
(void) tryMoveInternal(-1, 0);
lLastRep = now;
}
}
} else
lHeld = false;
if (st.right) {
if (!rHeld) {
rHeld = true;
lHeld = false;
tryMove(+1, 0);
(void) tryMoveInternal(+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);
(void) tryMoveInternal(+1, 0);
rLastRep = now;
}
}
@@ -864,6 +959,7 @@ private:
}
void lockAndAdvance() {
board.lock(px, py, rot, current);
dirty = true;
int c = board.clearLines();
if (c) {
static const int pts[5] = {0, 100, 300, 500, 800};
@@ -883,6 +979,7 @@ private:
px = 3;
py = -2;
rot = 0;
dirty = true;
if (board.collides(px, py, rot, current))
running = false;
}
@@ -897,14 +994,13 @@ public:
}
~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();
game->step();
uint32_t sleepMs = game->recommendedSleepMs(now);
if (sleepMs) {
clock.sleep_ms(sleepMs);
}
}
}
@@ -918,14 +1014,14 @@ private:
// ─────────────────────────────────────────────────────────────────────────────
// Entry
extern "C" void app_main() {
// Configure dynamic frequency scaling & light sleep if enabled
#ifdef CONFIG_PM_ENABLE
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));
#endif
PowerHelper::get();
Shutdowner::get();
Buttons::get();
@@ -936,8 +1032,6 @@ extern "C" void app_main() {
I2cGlobal::get();
BatMon::get();
SpiGlobal::get();
SMD::get();
SMD::get().clear();
DispTools::get().clear();
static PlatformFramebuffer fb;