Blockfall
1.0.0A falling-blocks puzzle, frontend-only, with a 7-bag randomizer, SRS rotation and wall kicks, ghost piece, hold, and DAS/ARR key repeat.
Already have ADI? Install it from your panel.
What's included
As published. What actually installs is read off the pinned commit's own tree.
- Dashboards
- game-blockfall — The game itself.
About
Blockfall
A falling-blocks puzzle, as one dashboard — a 10×20 well, the standard seven tetromino shapes drawn from a 7-bag randomizer, SRS-style rotation with wall kicks, ghost piece, hold, a next-3 preview, soft/hard drop, DAS/ARR key repeat, lock delay, and a level curve that speeds gravity up as you clear lines. Nothing on the backend: every rule lives in the browser, and the best score survives a reload in localStorage.
This bundle carries one element:
| kind | name | what it is |
|---|---|---|
| dashboard | game-blockfall | the game itself — frontend only, a one-route backend stub for the dashboard contract |
Install it:
adi-mono marketplace install <marketplace>/game-blockfallIt arrives archived, same as every dashboard — start it from the Dashboards page, or:
adi-mono marketplace start <marketplace>/game-blockfall/dashboards/game-blockfallThen open it by its hostname. No secret, no project, nothing else to configure.
Why no backend logic
Blockfall's entire rulebook — spawning, movement, rotation and wall kicks, collision, locking, line clears, scoring and the level curve — runs client-side; there is nothing for a server to compute or to keep private. The backend half of this dashboard exists only because the dashboard contract (guides/dashboards.md) needs one: it is the template's own unmodified backend/index.ts, answering /api/health so the page's status dot has something to poll, and nothing more. No route file was added.
How it's built
frontend/modules/game.ts is the panel — rendering, timing, input and persistence. The whole rulebook lives in frontend/modules/.logic.ts: pure functions over a plain Board (a ROWS×COLS grid of cell types) and a Piece (type, rotation state, and the board position of its own bounding box), with no DOM and no randomness that isn't passed in as an injectable rng. The dot in its name keeps it off the shell's auto-mounted panel list (guides/dashboards.md's module discovery lists every modules/*.ts that doesn't start with .); the panel reaches it at runtime as a same-origin ./.logic.js import, which the frontend server resolves straight back to this file.
The rules:
- 7-bag randomizer:
shuffledBagreturns one Fisher-Yates shuffle of all seven piece types;
PieceQueue always keeps a full bag ahead of what's been drawn, so a peek(3) preview is never waiting on a shuffle that hasn't happened yet, and no piece is ever more than twelve draws away from its last appearance.
- SRS rotation with wall kicks: each piece's four rotation states are the standard SRS offset
tables; tryRotate tries the "no kick" placement first, then walks the matching 5-point kick table (JLSTZ or the I piece's own, wider one) until one offset doesn't collide, or returns null if none do. O is rotationally symmetric in every state this file gives it, so it always "succeeds" with no kick and no shape change.
- ghost piece:
dropPiece/dropDistancecompute where the active piece would land without
moving it — the panel draws that as a translucent outline, and recomputes it on every move.
- lock delay: a piece that becomes grounded doesn't lock immediately;
game.tsstarts a
500ms timer the moment isGrounded turns true, cancels it if the piece moves off the ground again, and only locks when it actually elapses.
- line clears, scoring, levels:
clearLinesdrops every full row and compacts the rest down,
with fresh empty rows added at the top; scoreForClear is the classic 1/2/3/4-line table scaled by level; levelForLines advances one level per 10 lines; gravityIntervalMs shortens the fall interval each level, clamped so late levels stay playable instead of becoming an instant-drop wall.
- hold: swaps the active piece with the held one (or stashes it, if nothing was held yet),
locked to once per piece so hold can't be chained into never losing control of the active piece.
Input: arrow keys to move/soft-drop (with DAS/ARR auto-repeat — an initial delay, then a faster steady repeat, independently tuned for horizontal movement vs. soft drop), ↑/x and z to rotate clockwise/counter-clockwise, space to hard drop, c to hold, p to pause — all ignored while any text input or textarea elsewhere on the page has focus. Touch gets its own button bar (repeat-aware left/right/soft-drop, tap for rotate/hold/hard-drop) plus a swipe on the well itself (a tap rotates, a swipe moves or hard-drops), shown below 640px width or on any coarse pointer. Best score is localStorage["adi.blockfall.best"], scoped to the browser, not the backend — there is nothing server-side to lose it with.
Checking it works
bun test test/game-logic.test.ts27 checks against frontend/modules/.logic.ts directly — no DOM, no server: collision at every boundary, move/ground/drop-distance, unobstructed rotation and every wall-kick path (including a fully-blocked rotation returning null), O's trivial rotation, locking and line-clear mechanics (a tetris, non-contiguous clears, nothing clearing), top-out detection, and the 7-bag (a shuffled bag is always a permutation of all seven types, every consecutive run of seven draws is a full set, and peek doesn't consume).
A second, optional check covers what the logic tests structurally can't: whether the pause/game- over overlay is actually invisible when it should be. .bf__overlay sets display: flex (to centre its card), and an author stylesheet rule always outranks the UA's own [hidden] { display: none } at equal specificity — so a missing .bf__overlay[hidden] override renders the overlay (and its "New game" button) permanently, over the board, regardless of the hidden attribute. Neither bun:test's DOM nor happy-dom compute CSS cascade, so this needs a real browser:
bun add -d playwright && bunx playwright install chromium # one-time, not needed to play the game
bun test test/overlay-visibility.test.tsIt skips (with a warning, not a failure) if Playwright isn't installed — the dashboard itself has no dependency on it.
Licence
MIT.
Where it comes from
Installing clones this repository at this commit. A publisher who pushes something else afterwards changes nothing about what you already read.
- Repository
- github.com/adi-family/mp-game-blockfall
- Commit
69308712236f2da28f7db9cf966dddc79688ab96- Branch
main- Address
store/game-blockfall- Contains
- dashboard