From e389a776be26ff52bb6600920478c42c732e18d6 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Mon, 6 Oct 2025 09:47:50 +0200 Subject: [PATCH] broken game over --- Firmware/main/src/hello_world_main.cpp | 128 +++++++++++++++++-------- 1 file changed, 88 insertions(+), 40 deletions(-) diff --git a/Firmware/main/src/hello_world_main.cpp b/Firmware/main/src/hello_world_main.cpp index 1416232..0933f6d 100644 --- a/Firmware/main/src/hello_world_main.cpp +++ b/Firmware/main/src/hello_world_main.cpp @@ -301,15 +301,14 @@ struct Font5x7 { case 'G': return 29; case 'P': - return 30; // added + return 30; // added earlier for PAUSED / GAME case 'D': - return 31; // added + return 31; // added earlier // space handled specially in drawText default: return 255; } } - // columns × 7 rows (bit0 = top, bit6 = bottom) static const uint8_t data[32][5]; }; const uint8_t Font5x7::data[32][5] = { @@ -359,7 +358,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 paused) { + bool ghost, bool paused, bool gameOver) { fb.clear(false); // white background drawBatteryOverlay(); @@ -413,7 +412,9 @@ public: if (cell_of(TET[nextIdx], 0, xx, yy)) drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1); - if (paused) + if (gameOver) + drawGameOverOverlay(); + else if (paused) drawPausedOverlay(); DispTools::get().draw_to_display(); } @@ -473,6 +474,32 @@ public: drawText(cx, cy, txt); } + void drawGameOverOverlay() { + const char* l1 = "GAME"; // 4 letters + const char* l2 = "OVER"; // 4 letters + int w = 4 * 6 - 1; // width for 4 chars + int h = 8; // font height + int totalH = h * 2 + 2; // two lines + small gap + int cx = (fb.width() - w) / 2; + int cy = (fb.height() - totalH) / 2; // centered vertically + int padX = 6, padY = 5; + // Clear background region + for (int y = -padY; y < totalH + padY; ++y) + for (int x = -padX; x < w + padX; ++x) + fb.drawPixel(cx + x, cy + y, false); + // Border + for (int x = -padX; x < w + padX; ++x) { + fb.drawPixel(cx + x, cy - padY, true); + fb.drawPixel(cx + x, cy + totalH + padY - 1, true); + } + for (int y = -padY; y < totalH + padY; ++y) { + fb.drawPixel(cx - padX, cy + y, true); + fb.drawPixel(cx + w + padX - 1, cy + y, true); + } + drawText(cx, cy, l1); + drawText(cx, cy + h + 2, l2); + } + private: IFramebuffer& fb; int ox = 0, oy = 0, bw = 0, bh = 0; @@ -777,15 +804,21 @@ public: void step() { const uint32_t now = clock.millis(); overlayTick(now); + InputState st = input.readState(); if (!running) { + // Allow restart on any button press (except no input) + if (st.left || st.right || st.down || st.rotate || st.back) { + restart(); + return; // restart sets dirty and running + } if (dirty) paintHUD(); return; } - InputState st = input.readState(); + // Pause toggle if (st.back && !backPrev) { paused = !paused; - PowerHelper::get().set_slow(paused); // engage/disengage slow mode + PowerHelper::get().set_slow(paused); dirty = true; } backPrev = st.back; @@ -794,18 +827,15 @@ public: paintHUD(); return; } - // Rotation if (st.rotate && !rotPrev) { if (tryRotate(+1)) dirty = true; } rotPrev = st.rotate; - - // Horizontal movement + autorepeat + // Horizontal handleHorizontal(st, now); - - // Gravity / soft drop + // Gravity const int g = st.down ? cfg::DropFastMs : score.dropMs; if (elapsed_ms(lastFall, now) >= (uint32_t) g) { lastFall = now; @@ -816,11 +846,9 @@ public: } else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) { lockAndAdvance(); } - } else { + } else touchingGround = false; - } } - if (dirty) paintHUD(); } @@ -831,12 +859,11 @@ public: if (paused || !running) { uint32_t untilOverlay = (lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0; - uint32_t cap = paused ? 2000u : 500u; // allow longer idle while paused + uint32_t cap = (paused || !running) ? 2000u : 500u; if (untilOverlay > cap) untilOverlay = cap; return untilOverlay; } - // Estimate next gravity event uint32_t g = score.dropMs; uint32_t sinceFall = elapsed_ms(lastFall, now); @@ -846,20 +873,14 @@ public: 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) @@ -882,17 +903,43 @@ private: uint32_t lastOverlayUpd = 0; void overlayTick(uint32_t now) { - uint32_t interval = paused ? overlayIntervalMs * 8 : overlayIntervalMs; // 4s when paused + uint32_t interval = (paused || !running) ? overlayIntervalMs * 8 : overlayIntervalMs; // 4s paused or game over 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, paused); + renderer.render(board, px, py, rot, current, score.score, score.level, score.lines, nextPiece, true, paused, + !running); dirty = false; } + void restart() { + board.clear(); + score = ScoreState{}; + bag = Bag{}; // new sequence + nextPiece = bag.next(); + running = true; + paused = false; + touchingGround = false; + rotPrev = lHeld = rHeld = backPrev = false; + PowerHelper::get().set_slow(false); + spawn(); + dirty = true; + } + void spawn() { + current = nextPiece; + nextPiece = bag.next(); + px = 3; + py = -2; + rot = 0; // spawn above board + touchingGround = false; + dirty = true; + // Game over if immediate collision at spawn position OR unable to move one row down + if (board.collides(px, py, rot, current) || board.collides(px, py + 1, rot, current)) { + running = false; + } + } bool tryMoveInternal(int dx, int dy) { if (!board.collides(px + dx, py + dy, rot, current)) { @@ -903,12 +950,10 @@ private: } 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}}; - for (auto& k: kicks) + for (auto& k: kicks) { if (!board.collides(px + k[0], py + k[1], nr, current)) { px += k[0]; py += k[1]; @@ -916,6 +961,7 @@ private: dirty = true; return true; } + } return false; } void handleHorizontal(const InputState& st, uint32_t now) { @@ -939,7 +985,6 @@ private: } } else lHeld = false; - if (st.right) { if (!rHeld) { rHeld = true; @@ -958,8 +1003,21 @@ private: rHeld = false; } void lockAndAdvance() { + // Check if any part of current piece is above visible area before locking (for classic top-out) + bool above = false; + for (int yy = 0; yy < 4 && !above; ++yy) + for (int xx = 0; xx < 4 && !above; ++xx) + if (cell_of(TET[current], rot, xx, yy)) { + int gy = py + yy; + if (gy < 0) + above = true; + } board.lock(px, py, rot, current); dirty = true; + if (above) { // immediate game over (do not spawn new piece) + running = false; + return; + } int c = board.clearLines(); if (c) { static const int pts[5] = {0, 100, 300, 500, 800}; @@ -973,16 +1031,6 @@ private: } spawn(); } - void spawn() { - current = nextPiece; - nextPiece = bag.next(); - px = 3; - py = -2; - rot = 0; - dirty = true; - if (board.collides(px, py, rot, current)) - running = false; - } }; // ─────────────────────────────────────────────────────────────────────────────