P7.2: goal HUD + win/lose banner + restart button (sx, iOS sim)
Extend the HUD to show the per-level goal (SCORE x / target) alongside
moves. When the model's level_status (P7.1) is won/lost, draw a centered
overlay banner ("YOU WIN!" / "OUT OF MOVES") with a "PLAY AGAIN" restart
button over the dimmed board; the banner appears once any winning/losing
cascade animation settles. Status is read from the model, never recomputed
in the view.
A finished level freezes board-cell input; only the restart button is live.
Its rect is derived from the shared BoardLayout grid (new BannerLayout), so
the hit-test lands exactly on the drawn button. A tap reseeds the same
starting level through board.restart and clears the transient view layers,
returning to a clean in_progress board.
Banner is text + rects only (honours colour/alpha; no draw-time image tint,
issue 0002). New env capture hooks (M3TE_TARGET / M3TE_MOVE_LIMIT /
M3TE_RESTART) force a terminal status / restart for deterministic goldens.
Tests: tests/banner_layout.sx locks the restart button rect <-> hit-test
round-trip headlessly. Goldens p7_win / p7_lose / p7_restart captured on the
iOS simulator.
This commit is contained in:
@@ -49,4 +49,47 @@ BoardLayout :: struct {
|
||||
if col >= BOARD_COLS or row >= BOARD_ROWS { return null; }
|
||||
Cell.{ col = col, row = row }
|
||||
}
|
||||
|
||||
// Frame of the whole 8×8 grid (origin + cols/rows × cell_size). The banner
|
||||
// and its dimming overlay are sized off this so they cover exactly the board.
|
||||
grid_frame :: (self: *BoardLayout) -> Frame {
|
||||
Frame.make(
|
||||
self.origin.x, self.origin.y,
|
||||
self.cell_size * cast(f32) BOARD_COLS,
|
||||
self.cell_size * cast(f32) BOARD_ROWS
|
||||
)
|
||||
}
|
||||
|
||||
// Win/lose banner geometry (P7.2): an overlay panel centered over the board
|
||||
// grid, with the title band and the restart button inside it. Derived purely
|
||||
// from the SAME grid layout the gems use, so the restart hit-test in
|
||||
// BoardView.handle_event lands on exactly the button BoardView draws. The
|
||||
// headless banner_layout test locks the button-rect ↔ hit-test round-trip.
|
||||
banner :: (self: *BoardLayout) -> BannerLayout {
|
||||
grid := self.grid_frame();
|
||||
cx := grid.mid_x();
|
||||
cy := grid.mid_y();
|
||||
|
||||
panel_w := grid.size.width * 0.84;
|
||||
panel_h := grid.size.height * 0.44;
|
||||
panel := Frame.make(cx - panel_w * 0.5, cy - panel_h * 0.5, panel_w, panel_h);
|
||||
|
||||
title := Frame.make(panel.origin.x, panel.origin.y + panel_h * 0.18, panel_w, panel_h * 0.30);
|
||||
|
||||
btn_w := panel_w * 0.60;
|
||||
btn_h := panel_h * 0.24;
|
||||
btn_y := panel.origin.y + panel_h - btn_h - panel_h * 0.16;
|
||||
button := Frame.make(cx - btn_w * 0.5, btn_y, btn_w, btn_h);
|
||||
|
||||
BannerLayout.{ panel = panel, title = title, button = button }
|
||||
}
|
||||
}
|
||||
|
||||
// Resolved rectangles of the win/lose banner: the centered `panel`, the `title`
|
||||
// band where the win/lose headline is centered, and the restart `button` rect
|
||||
// (also the hit-test target). All in the same view-local space as BoardLayout.
|
||||
BannerLayout :: struct {
|
||||
panel: Frame;
|
||||
title: Frame;
|
||||
button: Frame;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user