CELLBLOCK — multiplayer Tetris game-jam 2026-06-19. Stack or be stacked.
  • TypeScript 60.7%
  • Go 33%
  • JavaScript 4.3%
  • HTML 2%
Find a file
release-bot c6d7a55bd3
All checks were successful
test / client (push) Successful in 1m46s
test / server (push) Successful in 2m30s
release / decide + act (push) Successful in 3m7s
release / release (push) Successful in 0s
test / client-nav (push) Successful in 1m22s
chore(manifest): post-cut bookkeeping for v1.2.0
Direct-pushed under path (alpha) per ADR-0007 (release-bot identity).

Tracker: frankenbit/release-toolkit#52
2026-08-27 09:33:49 +00:00
.forgejo/workflows fix(ci): pass secrets to the release reusable so release PRs get CI 2026-08-26 13:54:59 +02:00
assets assets: CELLBLOCK title-screen key-art (neon prison-break splash) 2026-06-19 11:48:26 +02:00
changelog.d chore(release): v1.2.0 2026-08-27 11:33:41 +02:00
client chore(release): v1.2.0 2026-08-27 11:33:41 +02:00
protocol contract(protocol): mirror nextQueue wire to schema.ts/proto.ts/messages.md 2026-06-21 14:44:01 +02:00
server docs(game): perfect-clear bonus — record the corrected invariant, retire the 40% proxy (#176) 2026-07-14 00:27:38 +02:00
tools feat(render): NEXT preview shows next-3 piece queue (#53) 2026-06-21 15:26:14 +02:00
.gitignore feat(client): keyboard navigation harness + #79 esc-trap fix + #80 inert-on-submit fix 2026-06-22 12:55:48 +02:00
.release-toolkit-manifest.json chore(manifest): post-cut bookkeeping for v1.2.0 2026-08-27 09:33:49 +00:00
CHANGELOG.md chore(release): v1.2.0 2026-08-27 11:33:41 +02:00
README.md README: Building-it run/build instructions + controls fix (Up=rotate) 2026-06-19 15:25:16 +02:00
release-toolkit.yml chore: adopt release-toolkit @v0.10.1-rc.1 (closes #162; supersedes #167) 2026-06-26 22:24:37 +02:00

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

  1. Title. Big neon CELLBLOCK logo, the tagline, a blinking PRESS ENTER.
  2. Check-in. "What do they call you, inmate?" — type your handle.
  3. Matchmaking. "Scanning the yard for a cellmate…"
  4. Lockdown. Both names up, then "Lockdown in 3… 2… 1… BREAK!"
  5. The brawl. Two wells side by side. An INCOMING bar warns when garbage is on its way across the yard.
  6. 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.