mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 15:17:48 +01:00
pause
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user