mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
broken game over
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user