---
title: "Blockfall — ADI Store"
description: "A falling-blocks puzzle, frontend-only, with a 7-bag randomizer, SRS rotation and wall kicks, ghost piece, hold, and DAS/ARR key repeat."
url: https://withadi.dev/store/game-blockfall/
---

[All apps](https://withadi.dev/store/)

![](data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'><rect width='48' height='48' rx='10' fill='%23101010'/><rect x='12' y='12' width='7' height='7' rx='2' fill='%23B46FE8'/><rect x='4' y='20' width='7' height='7' rx='2' fill='%23B46FE8'/><rect x='12' y='20' width='7' height='7' rx='2' fill='%23B46FE8'/><rect x='20' y='20' width='7' height='7' rx='2' fill='%23B46FE8'/><rect x='4' y='36' width='7' height='7' rx='2' fill='%234a4a4a'/><rect x='12' y='36' width='7' height='7' rx='2' fill='%234a4a4a'/><rect x='20' y='36' width='7' height='7' rx='2' fill='%234a4a4a'/><rect x='28' y='36' width='7' height='7' rx='2' fill='%234a4a4a'/></svg>)

# Blockfall

`1.0.0`

A falling-blocks puzzle, frontend-only, with a 7-bag randomizer, SRS rotation and wall kicks, ghost piece, hold, and DAS/ARR key repeat.

game blockfall tetromino puzzle

[Install](http://app.adi/marketplace/store/game-blockfall?install=1)[Download ADI](https://withadi.dev/#download)Opens in your panel, which asks where it goes Free — then this installs in one click

Already have ADI? [Install it from your panel](http://app.adi/marketplace/store/game-blockfall?install=1).

## 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-blockfall
```

It arrives archived, same as every dashboard — start it from the Dashboards page, or:

```
adi-mono marketplace start <marketplace>/game-blockfall/dashboards/game-blockfall
```

Then 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**: `shuffledBag` returns 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`/`dropDistance` compute 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.ts` starts 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**: `clearLines` drops 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.ts
```

27 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.ts
```

It 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](https://github.com/adi-family/mp-game-blockfall.git)
- **Commit** — `69308712236f2da28f7db9cf966dddc79688ab96`
- **Branch** — `main`
- **Address** — `store/game-blockfall`
- **Contains** — dashboard
- **Manifest** — [raw.githubusercontent.com/adi-family/marketplace/main/apps/marketplace.json](https://raw.githubusercontent.com/adi-family/marketplace/main/apps/marketplace.json)

---
[ADI](https://withadi.dev/)

- [Download](https://withadi.dev/#download)
- [Store](https://withadi.dev/store/)
- [Docs](https://docs.withadi.dev/)
- [Source](https://github.com/adi-family/mono)
- [Licence](https://github.com/adi-family/mono/blob/main/LICENSE)
- [Changelog](https://github.com/adi-family/mono/blob/main/CHANGELOG.md)
- [Security](https://withadi.dev/security)
- [Privacy](https://withadi.dev/privacy)
- [Red team](https://withadi.dev/red-team-engagements)
- [Terms](https://withadi.dev/terms)
- [Support](https://withadi.dev/support)
- [Startups](https://withadi.dev/startups)
- [Partners](https://withadi.dev/#partners)
- [Partner portal](https://partners.withadi.dev/)
- [info@withadi.dev](mailto:info@withadi.dev)
- [Contact](mailto:info@withadi.dev)
