Boggleflix Party ๐ŸŽ‰

July 27, 2026 ยท View on GitHub

A bright, phone-first family word game inspired by Boggle party games: swipe words on a letter grid, race the clock, and play together โ€” everyone on their own phone, no apps, no logins.

Play it: open the GitHub Pages URL for this repo, type your name, and either Host a party (share the 4-letter code) or Join with a code.

Modes

  • Party Mode โ€” up to 8 players. The host picks grid size (4ร—4 / 5ร—5 / 6ร—6), round timer (30sโ€“3m), minimum word length (3/4/5/6) and rounds (1/3/5). Minimum 6 wants a big board โ€” a 4ร—4 often holds no 6-letter word at all, so the lobby says so when you pick that pair. Everyone gets the identical board, a synced countdown, live scores during the round, standings between rounds, and a podium with awards at the end.
  • Daily Puzzle โ€” one shared board per day (seeded from the date). Compare scores in the family chat.
  • Solo Practice โ€” free play.

How multiplayer works (no server of our own!)

Every phone opens a plain outbound secure WebSocket to a couple of public MQTT brokers and publishes/subscribes on a topic named after the 4-letter room code. All the game's messages (roster, board seed, live scores, round results) flow through that shared topic. There is no game server to run or pay for, and โ€” crucially โ€” no phone-to-phone connection to negotiate, so it works on any network: home WiFi, mobile data, or a mix, including routers with client isolation. We connect to two brokers at once and de-duplicate by message id, so one broker flaking doesn't drop the party.

This replaced an earlier WebRTC/Trystero design. WebRTC needs a TURN relay whenever two phones can't reach each other directly (different networks, cellular, isolating routers), every free TURN server is now dead, and so those players got stuck on "can't find the party". The MQTT message bus sidesteps NAT traversal entirely. The bus lives in assets/mqtt-bus.js and exposes the same small API the game already used (joinRoom / makeAction / onPeerJoin), so the game logic was untouched.

Boards are seeded deterministically per round. Whoever opens the room is the host, and if they leave the others agree on a replacement by lowest peer id (never by clock โ€” phones disagree about the time).

Presence and scores are gossiped: each phone re-broadcasts the whole roster and scoreboard every few seconds and merges what it hears (scores by max, since they only grow), so a player still shows up and still scores even if a message was missed. The host also re-broadcasts the round-start on that cadence, so a phone that missed the one-shot start (backgrounded, a dropped packet, joined a beat late) is pulled into the round within a few seconds instead of being stranded in the lobby.

Words are checked against the public-domain ENABLE list (3+ letters, no upper cap โ€” real Boggle has none, and 8+ letter words are the jackpots), family-filtered with word-boundary awareness (~172k words), embedded in the page โ€” validation is instant and offline. Regenerate with python3 assets/make_dict.py (reads assets/enable1.txt).

Dice follow the real sets, with one deliberate exception: the 6ร—6 Super Big Boggle set has a cube reading QU/AN/IN/TH/ER/HE, and those two-letter tiles read as a bug to players, so that cube is a plain six-letter one here. Qu is the only tile that is ever two characters โ€” every set has it, and a bare Q is a dead tile without it.

Boards are generated exactly the way the real game shakes its tray: the dice are shuffled into the grid and each shows a uniformly random face โ€” no curation, no re-rolls. Vowel droughts and letter clumps are part of Boggle. Deterministic per seed, so every phone in a party sees the identical grid.

Joining is one step: typing the 4th letter of the code joins immediately, and the lobby says plainly whether it's still connecting or the code should be double-checked. In the last 10 seconds of a round the whole screen pulses a deep-crimson vignette so the countdown is felt, not just watched.

Scoring

Real Boggle's table: 3โ€“4 letters = 1 point, 5 = 2, 6 = 3, 7 = 5, 8+ = 11. On the 6ร—6 board, words of 9+ letters score 2 points per letter (Super Big Boggle rule). No bonus for speed, no penalty for a rejected word (rejected words just don't score).

Party Mode also plays real Boggle's duplicate rule: a word that two or more players found is crossed out and scores nothing for anyone โ€” only words nobody else found count. That needs to see everyone's word list, so in-round scores are provisional and settle once round results are in (just like comparing lists in the paper game); every phone computes it from the same reported lists and lands on the same total without a scorekeeper. Solo and Daily have no one to clash with โ€” every valid word counts.

Defaults follow the real game too: 3-minute rounds, minimum word length 3 on 4ร—4 and 4 on the bigger boards (picking a grid size resets the minimum to that board's rule; the host can still override it).

Music

A short original loop, synthesized live in the browser (same technique as the tap/word sound effects, just longer) โ€” not a recording, so nothing to license. It plays only while a round is being played: it starts with the round and stops the moment time is up, so menus, the lobby, standings and the podium are music-free. It follows the SOUND toggle (home screen and in-game HUD) like every other sound. The first tap on the page silently primes audio so a round started by the host (a network message, not a tap) may legally start the song under mobile autoplay rules.

iOS silences Web Audio when the phone's ring/silent switch is on. To play through that, a tiny silent looping clip (assets/silence.b64) holds the audio session open in "playback" mode while sound is enabled. Even so, a phone with the switch on or the volume down may still be quiet โ€” that's the OS, not the game.

Development

  • party.src.html โ€” markup + styles (placeholders for fonts/vendor/app)
  • party.app.js โ€” all game logic
  • assets/ โ€” dictionary, fonts (base64 woff2), Trystero bundle
  • python3 build.py โ†’ regenerates index.html (the deployed page)
  • p2ptest.html โ€” tiny standalone page to sanity-check P2P connectivity
  • Add ?dev to the URL for 25-second rounds; window.__end() force-ends a round

game.src.html is the original v1 (dark, single-phone) kept for reference.