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:
- 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.
- Generate grids exhaustively. Inverted indexes plus a round-robin search emit ~2M valid interlocking grids in seconds.
- 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.