A block-puzzle game for the whole family. Drag the coloured pieces onto the board and fill a whole row or column to make it sparkle and clear — but the pieces are big and awkward, so mind how you fill the board or you'll run out of room. Modelled on the "Block Puzzle" app, tuned to challenge a capable player (and played by both daughters).
Play it: https://rvenning.github.io/block-party/
- 🧩 Classic block puzzle: drag pieces onto an 8×8 grid, clear full rows and columns
- 🧱 A meaty piece mix — pentominoes, long bars and awkward L/T/S shapes — that fills the board fast and makes you plan ahead
- 🔥 Combo streak: clear a line on consecutive placements and each one is worth more (×1.5, ×2, ×2.5…)
- 💥 Steeper multi-line bonuses, plus a confetti Super Clear for emptying the whole board
- 🪙 Coins for every line cleared; ⭐ your best score is remembered
- 🏁 Runs really end — if a crowded board can't take any of your three pieces, the game's over; the result screen still celebrates the score
- 👨👩👧 Pick-a-name family profiles — no passwords; optional 4-digit PIN per profile with an admin override
- 🦄 Your profile avatar is your player; kid-friendly avatar set
- 🏆 Shared family leaderboard (best score)
- 🔊 All sound effects synthesized with WebAudio (no asset downloads)
- ☁️ Firebase Firestore sync (falls back to localStorage offline)
- 🎨 Hand-drawn feel: a lit party room with bunting, balloons and confetti on the floor — all of it clipped to outside the board, so nothing ever covers a block
- 📱 iPad-first: big touch targets, the dragged piece floats above the finger; works with a mouse on desktop
- 📲 Installable PWA: "Add to Home Screen" button, offline play via service worker
- +1 per cell placed
- +10 per line cleared, plus a +20 × (lines − 1) bonus for clearing several at once
- Combo streak: clearing a line on consecutive placements multiplies the clear points (×1, ×1.5, ×2, …); a placement that clears nothing resets it
- Coins = lines cleared; a full-board Super Clear adds +50
- Progress
{ best, coins, lines, games, sver }reconciles across devices by keeping the max of each field within a score season (sver). Bumping the season retires the previous scores everywhere — because the sync merges by max, that's the only way a reset can't be resurrected by an old high score on another device.
Profiles, PINs, storage/sync, the sound engine, base UI styles and the PWA kit come from
rvenning/gamekit, vendored into lib/.
To pull in a newer kit version:
# from the gamekit repo
node tools/sync-to-game.js "D:\OneDrive\Documents\Claude Code\block-party"
# then test, bump the CACHE version in sw.js, and commitEverything game-specific lives in js/:
| File | What's in it |
|---|---|
js/firebase-config.js |
the shared project's (public) config |
js/pieces.js |
the seven party colours, every shape, and the weights that set the difficulty |
js/game.js |
the rules: fitting, placing, clearing, scoring. DOM-free, so the tests can run it |
js/storage.js |
progress and the score season |
js/audio.js |
the game's own jingles on gamekit's synth |
js/art.js |
the party room and what a block looks like |
js/render.js |
layout, input, the frame loop, the feedback |
js/main.js |
profiles, screens, the results sheet, the wiring |
game.js never touches the page. It pushes presentation events onto a queue
(place, clear, super, stuck) and render.js drains them and makes the
noise — which is what lets the whole engine be replayed in Node with no browser.
npm test # node --testtests/golden.txt is five complete runs — 340 placements — captured from the
original single-file version before any of this was refactored.
tests/engine.test.js replays them through a seeded dealer and a deterministic
bot and asserts every placement, score, line count, coin and combo streak comes
out identical, plus the piece registry, the scoring rules and the end-of-run
condition.
If those tests fail, the game changed. Find the change and take it back out; don't re-bake the golden file unless a rule was deliberately altered.
Block Party is one question asked over and over: will this piece fit there, and
which of my three should I spend? Everything in js/art.js exists to make that
easier to answer, and nothing in it is allowed to make it harder.
That gives the art one hard constraint. The engine can fill all 64 cells and all
three tray slots, so the occlusion budget over the board and the tray is
zero — no decoration may ever sit in front of a block. It isn't enforced by
being careful: every layer that draws in front (bunting, the vignette, the
glitter, the balloons, the party lights) goes through Art.clipOutsidePlay,
which is an even-odd clip of the whole canvas minus the board minus the tray.
The rest of it:
- The room is baked. The empty grid, the table, the tray shelf and the
litter on the floor never change, so they are painted once into an offscreen
canvas and blitted — one
drawImageinstead of 64 rounded rects and a hundred scraps of confetti every frame. Rebuilt only on resize. - The landing preview is not a translucent block. At 45% opacity on a busy board it was being read as a block that was already there. It's an outlined socket with a coloured glow now — a promise, not a thing.
- A piece that can't go where you're holding it says so before you let go. It loses its colour and sits in a red glow. This was the game's central question and it had no feedback at all: the only "no" was a beep after the drop failed.
- A refused piece flies home instead of vanishing, which read as the game eating it.
- A dead tray piece loses its colour and goes still. Dimming alone wasn't enough — a faded orange still reads as orange next to a bright one.
- Everything that moves multiplies by
Art.motion, soprefers-reduced-motionis one number set to zero (plus the usual blanket CSS query).
manifest.json— app identity: name, icons, standalone display, theme colourssw.js— service worker; precaches the app shell (includinglib/,js/andcss/), then network-first with cache fallback. BumpCACHEwhenever a shell file changes, or devices keep serving the copy they already haveicons/— 192/512 PNG app icons plus a maskable variant, generated bytools/make-icons.js(no image tools needed — pure Node)
No build step — plain HTML/JS. Serve the folder with any static server:
npx http-server . -p 8098 -c-1 # http://localhost:8098Regenerate the icons after changing the art:
node tools/make-icons.jsDeploys are just pushes to main (GitHub Pages).
Canvas note: the
<canvas>needs an explicit CSS size (#cv { width:100%; height:100% }). A canvas is a replaced element, soposition:absolute; inset:0alone won't stretch it — without a CSS size it renders at its attribute size (W × devicePixelRatio), which is 2–3× too large on retina screens. This is invisible on a desktop preview but overflows on any iPhone/iPad.
Progress lives in localStorage (keys bp_*) and mirrors to Cloud Firestore
(collection blockparty, same Firebase project as WordVoyage and Chicken Cross).
The Firebase API key in js/firebase-config.js is public by design — it only identifies the
project; access is limited by Firestore security rules, and the key is
referrer-restricted to the game's domains and locked to the Firestore API.