From 28411535bb41e74ae973f40a131e5360e3183aff Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Fri, 10 Oct 2025 11:18:04 +0200 Subject: [PATCH] tetris high score --- Firmware/main/src/apps/tetris_app.cpp | 65 ++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/Firmware/main/src/apps/tetris_app.cpp b/Firmware/main/src/apps/tetris_app.cpp index 7b9cb6b..19e3276 100644 --- a/Firmware/main/src/apps/tetris_app.cpp +++ b/Firmware/main/src/apps/tetris_app.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -20,10 +21,12 @@ #include #include #include +#include #include #include #include +#include #include "esp_random.h" #include "esp_timer.h" @@ -223,8 +226,8 @@ public: oy = (fb.height() - bh) / 2; // centered vertically with margin } - void render(const Board& b, int px, int py, int prot, int pidx, int score, int level, int lines, int nextIdx, - bool ghost, bool paused, bool gameOver, int fps, int avgFrameMs10) { + void render(const Board& b, int px, int py, int prot, int pidx, int score, int highScore, int level, int lines, + int nextIdx, bool ghost, bool paused, bool gameOver, int fps, int avgFrameMs10) { auto& ps = PerfStats::get(); auto tus = []() { return esp_timer_get_time(); }; uint64_t t0 = tus(); // frame start (no full clear; buffer already zeroed by async post-blit) @@ -257,6 +260,10 @@ public: drawLabel(hudX, yHUD, "SCORE"); yHUD += hudFontHeight() + kHudLabelGap; drawNumber(hudX, yHUD, score); + yHUD += hudFontHeight() + kHudLabelGap; + drawLabel(hudX, yHUD, "BEST"); + yHUD += hudFontHeight() + kHudLabelGap; + drawNumber(hudX, yHUD, highScore); yHUD += hudFontHeight() + kHudBlockGap; drawLabel(hudX, yHUD, "LEVEL"); yHUD += hudFontHeight() + kHudLabelGap; @@ -681,16 +688,18 @@ private: // ───────────────────────────────────────────────────────────────────────────── // Game struct ScoreState { - int level = 0; - int score = 0; - int lines = 0; - int dropMs = cfg::DropMsStart; + int level = 0; + int score = 0; + int highScore = 0; + int lines = 0; + int dropMs = cfg::DropMsStart; }; class Game { public: explicit Game(AppContext& ctx) : appContext(ctx), fb(ctx.framebuffer), input(ctx.input), clock(ctx.clock), renderer(ctx.framebuffer) { + loadHighScore(); nextPiece = bag.next(); spawn(); lastFall = clock.millis(); @@ -970,6 +979,32 @@ private: else if (!wantSlow && ph.is_slow()) ph.set_slow(false); } + void loadHighScore() { + nvs_handle_t h; + if (nvs_open("tetris", NVS_READONLY, &h) == ESP_OK) { + uint32_t stored = 0; + if (nvs_get_u32(h, "best", &stored) == ESP_OK) { + uint32_t capped = std::min(stored, static_cast(std::numeric_limits::max())); + score.highScore = static_cast(capped); + } + nvs_close(h); + } + } + void persistHighScore() { + nvs_handle_t h; + if (nvs_open("tetris", NVS_READWRITE, &h) == ESP_OK) { + uint32_t value = score.highScore > 0 ? static_cast(score.highScore) : 0; + nvs_set_u32(h, "best", value); + nvs_commit(h); + nvs_close(h); + } + } + void updateHighScoreIfNeeded() { + if (score.score > score.highScore) { + score.highScore = score.score; + persistHighScore(); + } + } void paintHUD() { // Complete previous frame transfer & implicit clear so dma_buf pixel area is zeroed before we draw SMD::async_draw_wait(); @@ -989,8 +1024,8 @@ private: frameAccumMs = 0; lastStatTime = rStart; } - renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true, paused, - !running, statFps, statAvgFrameMs10); + renderer.render(board, px, py, rot, current, score.score, score.highScore, score.level, score.lines, nextPiece, + true, paused, !running, statFps, statAvgFrameMs10); uint64_t rEndUs = esp_timer_get_time(); { auto& ps = PerfStats::get(); @@ -1008,8 +1043,11 @@ private: dirty = false; } void restart() { + updateHighScoreIfNeeded(); board.clear(); - score = ScoreState{}; + int best = score.highScore; + score = ScoreState{}; + score.highScore = best; bag.reset(); // new randomized sequence without reconstructing (avoids potential random_device issues) nextPiece = bag.next(); running = true; @@ -1036,6 +1074,7 @@ private: gameOverTime = clock.millis(); gameOverPrevPressed = true; // require a release after delay // slow mode applied centrally next step + updateHighScoreIfNeeded(); Buzzer::get().beepGameOver(); } } @@ -1122,6 +1161,7 @@ private: gameOverTime = clock.millis(); gameOverPrevPressed = true; // slow mode applied centrally next step + updateHighScoreIfNeeded(); Buzzer::get().beepGameOver(); return; } @@ -1130,6 +1170,7 @@ private: static const int pts[5] = {0, 100, 300, 500, 800}; score.lines += c; score.score += pts[c] * (score.level + 1); + updateHighScoreIfNeeded(); int nl = score.lines / cfg::LevelStepClr; if (nl != score.level) { score.level = nl; @@ -1177,9 +1218,9 @@ private: void runStep() { game.step(); - const auto now = context.clock.millis(); - const auto plan = game.recommendedSleepMs(now); - uint32_t delay = plan.normal_ms != 0 ? plan.normal_ms : plan.slow_ms; + const auto now = context.clock.millis(); + const auto plan = game.recommendedSleepMs(now); + uint32_t delay = plan.normal_ms != 0 ? plan.normal_ms : plan.slow_ms; scheduleNextTick(delay); }