broken game over

This commit is contained in:
2025-10-06 09:47:50 +02:00
parent 8b8d9d3a55
commit e389a776be

View File

@@ -301,15 +301,14 @@ struct Font5x7 {
case 'G': case 'G':
return 29; return 29;
case 'P': case 'P':
return 30; // added return 30; // added earlier for PAUSED / GAME
case 'D': case 'D':
return 31; // added return 31; // added earlier
// space handled specially in drawText // space handled specially in drawText
default: default:
return 255; return 255;
} }
} }
// columns × 7 rows (bit0 = top, bit6 = bottom)
static const uint8_t data[32][5]; static const uint8_t data[32][5];
}; };
const uint8_t Font5x7::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, 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 fb.clear(false); // white background
drawBatteryOverlay(); drawBatteryOverlay();
@@ -413,7 +412,9 @@ public:
if (cell_of(TET[nextIdx], 0, xx, yy)) if (cell_of(TET[nextIdx], 0, xx, yy))
drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1); drawCellFullPreview(nx + xx * p, ny + yy * p, nextIdx + 1);
if (paused) if (gameOver)
drawGameOverOverlay();
else if (paused)
drawPausedOverlay(); drawPausedOverlay();
DispTools::get().draw_to_display(); DispTools::get().draw_to_display();
} }
@@ -473,6 +474,32 @@ public:
drawText(cx, cy, txt); 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: private:
IFramebuffer& fb; IFramebuffer& fb;
int ox = 0, oy = 0, bw = 0, bh = 0; int ox = 0, oy = 0, bw = 0, bh = 0;
@@ -777,15 +804,21 @@ public:
void step() { void step() {
const uint32_t now = clock.millis(); const uint32_t now = clock.millis();
overlayTick(now); overlayTick(now);
InputState st = input.readState();
if (!running) { 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) if (dirty)
paintHUD(); paintHUD();
return; return;
} }
InputState st = input.readState(); // Pause toggle
if (st.back && !backPrev) { if (st.back && !backPrev) {
paused = !paused; paused = !paused;
PowerHelper::get().set_slow(paused); // engage/disengage slow mode PowerHelper::get().set_slow(paused);
dirty = true; dirty = true;
} }
backPrev = st.back; backPrev = st.back;
@@ -794,18 +827,15 @@ public:
paintHUD(); paintHUD();
return; return;
} }
// Rotation // Rotation
if (st.rotate && !rotPrev) { if (st.rotate && !rotPrev) {
if (tryRotate(+1)) if (tryRotate(+1))
dirty = true; dirty = true;
} }
rotPrev = st.rotate; rotPrev = st.rotate;
// Horizontal
// Horizontal movement + autorepeat
handleHorizontal(st, now); handleHorizontal(st, now);
// Gravity
// Gravity / soft drop
const int g = st.down ? cfg::DropFastMs : score.dropMs; const int g = st.down ? cfg::DropFastMs : score.dropMs;
if (elapsed_ms(lastFall, now) >= (uint32_t) g) { if (elapsed_ms(lastFall, now) >= (uint32_t) g) {
lastFall = now; lastFall = now;
@@ -816,11 +846,9 @@ public:
} else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) { } else if (elapsed_ms(touchTime, now) >= (uint32_t) cfg::LockDelayMs) {
lockAndAdvance(); lockAndAdvance();
} }
} else { } else
touchingGround = false; touchingGround = false;
}
} }
if (dirty) if (dirty)
paintHUD(); paintHUD();
} }
@@ -831,12 +859,11 @@ public:
if (paused || !running) { if (paused || !running) {
uint32_t untilOverlay = uint32_t untilOverlay =
(lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0; (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) if (untilOverlay > cap)
untilOverlay = cap; untilOverlay = cap;
return untilOverlay; return untilOverlay;
} }
// Estimate next gravity event // Estimate next gravity event
uint32_t g = score.dropMs; uint32_t g = score.dropMs;
uint32_t sinceFall = elapsed_ms(lastFall, now); uint32_t sinceFall = elapsed_ms(lastFall, now);
@@ -846,20 +873,14 @@ public:
uint32_t untilLock = (sinceTouch >= (uint32_t) cfg::LockDelayMs) ? 0 : (cfg::LockDelayMs - sinceTouch); uint32_t untilLock = (sinceTouch >= (uint32_t) cfg::LockDelayMs) ? 0 : (cfg::LockDelayMs - sinceTouch);
untilDrop = std::min(untilDrop, untilLock); untilDrop = std::min(untilDrop, untilLock);
} }
// Overlay may force earlier wake
uint32_t untilOverlay = uint32_t untilOverlay =
(lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0; (lastOverlayUpd + overlayIntervalMs > now) ? (lastOverlayUpd + overlayIntervalMs - now) : 0;
uint32_t sleep = std::min(untilDrop, untilOverlay); uint32_t sleep = std::min(untilDrop, untilOverlay);
// Provide a max sleep to keep input latency reasonable
if (sleep > maxIdleSleepMs) if (sleep > maxIdleSleepMs)
sleep = maxIdleSleepMs; sleep = maxIdleSleepMs;
return sleep; return sleep;
} }
bool isPaused() const { return paused; }
private: private:
// Power-aware overlay throttling // Power-aware overlay throttling
static constexpr uint32_t overlayIntervalMs = 500; // base interval (paused uses multiplier) static constexpr uint32_t overlayIntervalMs = 500; // base interval (paused uses multiplier)
@@ -882,17 +903,43 @@ private:
uint32_t lastOverlayUpd = 0; uint32_t lastOverlayUpd = 0;
void overlayTick(uint32_t now) { 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) { if (elapsed_ms(lastOverlayUpd, now) >= interval) {
lastOverlayUpd = now; lastOverlayUpd = now;
dirty = true; dirty = true;
} }
} }
void paintHUD() { 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; 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) { bool tryMoveInternal(int dx, int dy) {
if (!board.collides(px + dx, py + dy, rot, current)) { if (!board.collides(px + dx, py + dy, rot, current)) {
@@ -903,12 +950,10 @@ private:
} }
return false; return false;
} }
bool tryMove(int dx, int dy) { return tryMoveInternal(dx, dy); }
bool tryRotate(int d) { bool tryRotate(int d) {
int nr = (rot + d + 4) % 4; int nr = (rot + d + 4) % 4;
static const int kicks[][2] = {{0, 0}, {-1, 0}, {1, 0}, {-2, 0}, {2, 0}, {0, -1}}; 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)) { if (!board.collides(px + k[0], py + k[1], nr, current)) {
px += k[0]; px += k[0];
py += k[1]; py += k[1];
@@ -916,6 +961,7 @@ private:
dirty = true; dirty = true;
return true; return true;
} }
}
return false; return false;
} }
void handleHorizontal(const InputState& st, uint32_t now) { void handleHorizontal(const InputState& st, uint32_t now) {
@@ -939,7 +985,6 @@ private:
} }
} else } else
lHeld = false; lHeld = false;
if (st.right) { if (st.right) {
if (!rHeld) { if (!rHeld) {
rHeld = true; rHeld = true;
@@ -958,8 +1003,21 @@ private:
rHeld = false; rHeld = false;
} }
void lockAndAdvance() { 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); board.lock(px, py, rot, current);
dirty = true; dirty = true;
if (above) { // immediate game over (do not spawn new piece)
running = false;
return;
}
int c = board.clearLines(); int c = board.clearLines();
if (c) { if (c) {
static const int pts[5] = {0, 100, 300, 500, 800}; static const int pts[5] = {0, 100, 300, 500, 800};
@@ -973,16 +1031,6 @@ private:
} }
spawn(); 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;
}
}; };
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────