- TypeScript 60.7%
- Go 33%
- JavaScript 4.3%
- HTML 2%
Direct-pushed under path (alpha) per ADR-0007 (release-bot identity). Tracker: frankenbit/release-toolkit#52 |
||
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| changelog.d | ||
| client | ||
| protocol | ||
| server | ||
| tools | ||
| .gitignore | ||
| .release-toolkit-manifest.json | ||
| CHANGELOG.md | ||
| README.md | ||
| release-toolkit.yml | ||
CELLBLOCK
Stack or be stacked.
A two-player versus Tetris brawl from the yard. Clear lines, and the rubble you sweep gets shoveled straight into your opponent's cell. Keep your own stack below the ceiling. Last inmate standing walks free.
Built in an afternoon by the alcatraz crew. It's a jam — it works, it's mean, and it has a name. Polish is for the parole hearing.
The pitch
Two wells, side by side. You and one other inmate, each dropping the same seven blocks. Every line you clear doesn't just vanish — it gets packed into garbage and sent across the yard into the bottom of their well, walls creeping up toward their ceiling. They do the same to you. Clear fast, clear big, stack your clears into combos, and bury them before they bury you.
First stack to hit the ceiling loses. There's no second round unless you both want one.
How to play
Objective: don't top out. Make the other guy top out.
Controls — every action has an arrow-cluster key and a WASD-cluster key, so play whichever side of the keyboard feels right:
| Action | Keys |
|---|---|
| Move left / right | ← → · A D |
| Soft drop (fall faster) | ↓ · S |
| Rotate clockwise | ↑ · X · W · E |
| Rotate counter-clockwise | Z · Q |
| Hard drop (slam it down) | Space |
Up rotates. Both ↑ and W spin the piece — that's the Tetris reflex, so
we honor it. Hard drop is Space, and only Space, so you never fire off a
slam-down when your fingers were reaching for a rotate.
The rules
Attack — clear lines, send time
When you clear lines, garbage rows get sent to your opponent's well, rising from the bottom:
| You clear | Garbage sent |
|---|---|
| Single (1 line) | 0 |
| Double (2 lines) | 1 |
| Triple (3 lines) | 2 |
| Tetris (4 lines) | 4 |
Combos. Clear lines on back-to-back pieces and the attack grows. A
combo counter starts at 0 and ticks up by 1 each time a piece locks
while clearing at least one line; it resets to 0 the instant a piece locks
clearing nothing. On every clear you send base + min(combo, 4) garbage,
where base is the attack-table value above. So the first clear of a streak
sends just the base; the second sends base +1, the third base +2 … and the
bonus tops out at +4 no matter how long the streak runs. The cap is on the
combo bonus only — the base attack value is never capped.
Garbage rows are solid except for a single hole, and the hole sits in the same column for the whole volley so it's always clearable — but a new volley picks a new random hole, so you can't just camp one column.
Clearing is defense, too. When you clear lines, your outgoing garbage first cancels your own pending incoming garbage — oldest volley first — and only the leftover crosses the yard. So a well-timed clear can wipe out an attack that was already heading for you and hit back with whatever's left over. Offense and defense are the same move.
Garbage waits for your next lock. Incoming garbage doesn't slam in the instant it's sent — it queues, and only rises into your well when your next piece locks. Until then it sits in the pending queue (the pendingGarbage indicator shows how much is stacked up against you), so you get one piece's grace to clear it away first.
Same blocks, both wells. Both players draw their pieces from the same seeded sequence, so a match is a fair fight over identical tetrominoes — no one gets luckier bags.
Win condition
Last stack standing. You top out — and lose — the moment either of two things happens: a new piece can't spawn into the field (it's locked out), or incoming garbage shoves a block above the ceiling. The other inmate walks free. Best of one — rematch if you've got the nerve.
If both players somehow top out on the exact same tick, Player 1 (the left well, paired first) takes it — a deterministic tiebreak so a match always has one clear winner.
The look — neon prison-break arcade
CELLBLOCK is concrete and neon: a dark cell-block lit by buzzing arcade signage. CRT energy, not chrome.
Palette:
| Role | Hex |
|---|---|
| Concrete background | #15151f |
| Steel panel / well border | #2b2b3d |
| Grid lines | #222230 |
| Neon cyan (primary UI / highlights) | #19e6ff |
| Hot magenta (danger / garbage warning) | #ff2d95 |
| Amber (alerts / countdown) | #ffb000 |
Tetromino colors (neon-saturated):
I cyan #19e6ff · O yellow #ffe600 · T purple #b14bff ·
S green #39ff14 · Z red #ff2d55 · J blue #2d6cff ·
L orange #ff8c1a · garbage steel #5a5a6e (with a darker hole)
Type: arcade-pixel display face (Press Start 2P) for the logo and big
banners; a chunky CRT monospace (VT323, fallback system mono) for HUD,
scores, and the trash-talk. Stretch flavor, Shipwright's call: scanline
overlay, a soft neon bloom, and a short screen-shake when garbage lands.
In the yard — the flow
- Title. Big neon
CELLBLOCKlogo, the tagline, a blinking PRESS ENTER. - Check-in. "What do they call you, inmate?" — type your handle.
- Matchmaking. "Scanning the yard for a cellmate…"
- Lockdown. Both names up, then "Lockdown in 3… 2… 1… BREAK!"
- The brawl. Two wells side by side. An INCOMING bar warns when garbage is on its way across the yard.
- Lights out. Loser gets a sendoff; winner breaks out. Rematch or back to the yard.
What the yard says to you
When you lose:
- "Lights out, inmate."
- "You got stacked. Back to your cell."
- "The walls won this round."
- "Solitary confinement: party of one."
- "Stacked, packed, and denied parole."
When you win:
- "You broke out. Cellblock cleared."
- "Warden's favorite — you walk free."
- "Parole granted. The yard is yours."
Along the way:
- Waiting on a match: "Your cellmate is lacing up…"
- Garbage inbound: "INCOMING — brace the wall."
Building it
CELLBLOCK is two halves: a Go server (the authoritative game engine + WebSocket matchmaking) and a TypeScript / Canvas client (built with Vite). No database, no accounts — you just run them.
Just want to see it? Play solo, no server:
cd client
npm install
npm run dev
# then open http://localhost:5173/?mock
The ?mock flag runs the whole game against a throwaway in-browser engine — one
well, fully playable — so you can feel the controls and the look without
standing up a server.
Two-player dev (live versus, hot-reloading client):
# terminal 1 — the server (authoritative engine + matchmaking)
cd server
CELLBLOCK_ADDR=:8085 go run .
# terminal 2 — the client dev server
cd client
npm install
npm run dev
Open http://localhost:5173 in two browser windows — the first two to join are
paired into a match. The Vite dev server (:5173) proxies the /ws WebSocket to
the Go server on :8085. (Why :8085 and not the default :8080? :8080 is
taken on the alcatraz host — override anytime with CELLBLOCK_ADDR.)
One binary (server with the client baked in):
cd client && npm install && npm run build # → client/dist
cp -r dist/* ../server/web/ # stage the built client for embedding
cd ../server && go build -o cellblock . # //go:embed bakes server/web into the binary
CELLBLOCK_ADDR=:8085 ./cellblock # serves the client at / and the game at /ws
The server embeds whatever sits in server/web/ at build time, so the built
client ships inside the one binary. Set CELLBLOCK_WEB_DIR=<dir> to serve a
client from disk instead, without rebuilding.
Tests: cd server && go test ./... (engine + lobby) and cd client && npm test (TypeScript typecheck) — both run in CI on every push.
The crew
Built in one afternoon jam by the alcatraz crew:
| Creative direction, design & words | Herald |
| Server game logic | Engineer |
| WebSocket protocol | Carpenter |
| Client rendering | Shipwright |
| Lobby & matchmaking | Pilot |
| Code review | Surveyor |
| Runtime & observability | Lookout |
| Steering & dispatch | Bosun |
| Repo & infra | Quartermaster |
| The Warden | Alex |
CELLBLOCK — do your time, send theirs back.