Imposter

Party game for one shared screen and everyone's phones, running free on Cloudflare Workers and Durable Objects

Published Cloudflare Workers · Durable Objects · WebSockets

A party game for one shared screen and everyone’s phones. Everyone gets the same secret word — except one of you.
Built to replace passing a single phone around the table, and to run for free on Cloudflare Workers with Durable Objects.

Play it at imposter.tommyl22.com — no account, no install, three players minimum.


🎮 How to Play

  • Three or more players. Everyone gets the same secret word, except the imposter.
  • The imposter gets nothing, a hint word, or just the category — whoever is running the game decides.
  • Take turns saying one related word out loud. Say too little and you look guilty; say too much and you hand it to the imposter.
  • After a couple of rounds, everyone votes.
  • Imposter survives the vote → imposter scores.
  • Imposter is caught → one guess at the word. Right, they score anyway. Wrong, every innocent scores.

🚀 Features

  • Shared screen optional: Play with a TV, a laptop, or nothing but phones in a car
  • Room codes: Four letters, so any group can start their own game at any time
  • Private roles: Each phone shows only its own secret, hidden behind a tap
  • QR joining: Scan from the shared screen, type the code, or share a link
  • 58 categories: 3,451 words with 13,804 hints, all toggleable mid-game
  • Reconnects cleanly: Lock your phone or lose Wi-Fi and come back as yourself
  • Runs anywhere: The same rules run on Cloudflare or on a Raspberry Pi over a home network

🧑‍💻 How It Works

The game rules live in a single file that never touches the network, the clock or the disk. It cannot call setTimeout and has no idea what a WebSocket is. When it needs waking later — a turn deadline, or the grace period before an absent admin loses control — it hands its host a timestamp and asks to be woken then.

That one constraint lets the same rules run in two very different places:

  • On Cloudflare: one Durable Object per room. getByName("BKTM") reaches the same single instance from anywhere in the world, so a game room is the object rather than rows in a database. No locks, no races, nothing to sync between servers. Deadlines become Durable Object alarms.
  • On a Raspberry Pi: an ordinary Node process holding many rooms in memory, with setTimeout doing the waking and scores saved to a file. Useful because the game still works at home when the internet does not.

Rooms hibernate. Cloudflare evicts a room’s code from memory while everyone’s phone stays connected, and the keepalive ping is answered at the edge without waking anything up. A room full of people staring at each other in silence costs nothing, which is how the whole thing fits inside the free tier with room to spare.

Hibernation is also where the interesting bugs were. A Durable Object re-runs its constructor on every wake, not just on a restart, and two things quietly assumed otherwise:

  • The turn deadline was being recalculated on load. With hibernation that meant an unattended turn could never expire — it was pushed forward every time it was checked. It had to become a stored timestamp rather than something derived.
  • Once an absent admin’s grace period lapsed, the game kept asking to be woken at a moment already in the past. That is a hot loop: wake, write to storage, ask again, forever.

Neither showed up in local testing, because a development server keeps the object in memory and never hibernates it.

The front end is plain HTML, CSS and JavaScript with no framework and no build step, and there are three runtime dependencies, all pure JavaScript so a Raspberry Pi stays happy. The whole deployed bundle is 128 KB gzipped, word bank included.


🎯 Design Notes

  • Room codes that survive being shouted across a room. Four letters from a 24-letter alphabet with I and O removed, and no digits at all. That kills every 0/O and 1/I mix-up by construction, keeps phones on a single keyboard, and makes leetspeak profanity unrepresentable. 331,776 combinations, with a blocklist for the four-letter words nobody wants to read out.
  • Hints that help without handing over the game. Every hint has to pass one test: hold the hint, hear one clue from an innocent, and you should still have five or more plausible answers. Lady Gaga for Poker Face fails the moment somebody says “cards”. spoilers for Tom Holland passes — oblique, and still ambiguous even after someone says “Marvel”. Each word carries four hints and one is drawn at random per round, so a word you have seen before is not recognisable from its hint.
  • A hidden card that is actually hidden. The role card is green for the crew and red for the imposter, covered by a “tap to reveal” overlay. Its translucent border let that colour glow through the cover, readable from across a table. A hidden card now carries no role styling at all.
  • The shared screen is a viewer, not a player. Obvious once said, with a sharp edge: three friends on three laptops, one starts the game and lands on the board, and the room is a player short and refuses to begin. The board now says so and offers to become a player instead.

🧪 Testing

Three suites, 118 assertions. The most valuable are the ones that rebuild a room from storage over and over, since that is what a Durable Object does on every wake and where both hibernation bugs were hiding. The rest play complete games over real WebSockets against both hosts — every outcome, tied votes and re-votes, reconnects, kicks, timers, room isolation and junk input — and one that kills the server mid-game to confirm the room comes back intact.


🌐 Live Demo

imposter.tommyl22.com