← All projects

Puxxle

Live

A daily word-puzzle game: Phaser 3 + TypeScript front end, Firebase backend, and an automated pipeline that curates 1,150 unique puzzles.

TypeScriptPhaser 3ViteFirebaseGitHub Actions

July 2026

Puxxle is a daily word-puzzle game: eight interlocking five-letter words on a 5×5 grid, a new puzzle every day. I built and own the whole thing end-to-end, including the game client, backend, content pipeline, CI/CD, and test infrastructure.

Architecture

The client is Phaser 3 + TypeScript, bundled by Vite with vite-plugin-singlefile so the entire app ships as one inlined index.html. The backend is Firebase: Firestore for puzzles/themes/progress, Auth (anonymous by default, linkable to Google), Remote Config for launch controls, and Analytics for observability. Pushing to main triggers GitHub Actions, which builds and deploys to Firebase Hosting.

  • Deterministic daily puzzles. Each date maps to a puzzle by index = daysSinceStart % availableCount, so the calendar is reproducible and needs no per-date authoring.
  • Multi-layer caching. An in-memory map, then localStorage (24h TTL with a schema-version key for instant global invalidation), then the network, with in-flight de-duplication so a warm-ahead fetch and the real load never race.
  • Content as a separate repo. Puzzles and themes live in their own repo that publishes to Firestore via a scoped, least-privilege CI identity, so the game app carries no content-authoring credentials.

The content pipeline

The interesting problem: I needed ~1,100 puzzles (three full years, no repeats) without hand-authoring them. The generator is exhaustive, not random. A puzzle is eight interlocking words, so quality is a function of the word set and the selection, not per-puzzle curation. The pipeline:

  1. Curate the vocabulary. Filter a scrabble word list by frequency band, strip plurals and proper nouns, and apply an auditable profanity/vetting blocklist, leaving a clean ~2,200-word set.
  2. Generate grids exhaustively. Inverted indexes plus a round-robin search emit ~2M valid interlocking grids in seconds.
  3. Select ~1,150. Quality gates (word-frequency thresholds, a higher bar for the visible across-words), diversity caps (limit word reuse across puzzles), and difficulty tiers by average word rarity.

The result: 1,150 curated puzzles, zero offensive or awkward words across all eight words in every puzzle, with a difficulty spread, fully automated and rerunnable.

Testing a game you can’t manually QA

I don’t own an Android device, and the trickiest bugs were mobile input freezes. So I built an Android emulator test harness in CI: real touch drag-and-drop, double-tap, completion, resize and orientation specs, path-gated to only run when game mechanics change, with a hard daily cap to stay on the free tier. There is also a deterministic freeze-recovery end-to-end test and boot/date-change performance budgets that gate the deploy.

What I’d highlight

This project is mostly a systems story wearing a game costume: deterministic content generation, cache-coherency across layers, a least-privilege publish pipeline, and automated testing for a platform I can’t touch by hand. The word game is just what the systems happen to run.