Skip to content

Repository files navigation

🕵️ Cipher — modern realtime CodeNames

A clean, modern, realtime take on the classic word‑association party game. Spin up a room, share the link, and play instantly on any device — no account, no download. Built‑in chat, a large curated wordbank, fully‑configurable rules, and 20 hand‑tuned gamemodes (plus your own, authored in YAML).

Realtime backend on PartyKit · static client on Vercel · React + TypeScript + Tailwind.

Home


✨ Highlights

  • Instant & accountless — create a room, copy the link, play. Identity persists in localStorage, so a refresh or redeploy rejoins your exact seat.
  • Server‑authoritative & cheat‑proof — the key card never leaves the server for operatives or spectators. Roles, turns, and guess counts are enforced server‑side, so a crafted client can't peek or cheat.
  • Durable, reconnect‑safe rooms — game state is persisted to PartyKit storage. Disconnect, deploy, or close the tab; the room and your seat survive and you reconnect right where you left off.
  • In‑app everything — structured clue input (word + number, validated), all‑chat + team‑chat, and an automatic clue/guess log so the table has one source of truth. No external Discord required.
  • 20 configurable gamemodes — Classic, Tiny, Marathon, Blitz, Zen, Duet Co‑op, Triple Threat, Minefield, Sudden Death, Party Night, and more — each fun‑and‑balance judged. Board size, team count, agent/assassin distribution, timers, clue rules, and win conditions are all tunable.
  • Custom YAML gamemodes — author or paste a mode in seconds; it's validated against a strict schema (the tile distribution must exactly fill the board) and shared instantly with the room.
  • A large curated wordbank1,951 independently‑audited words across 11 stackable packs (Classic, Easy/Family, Hard/Abstract, Tech, Pop Culture, Food, Nature, Travel, Sports, Myth, and an 18+ Party pack), with a live word‑pool counter.
  • Mobile‑first & accessible — fluid layout from phone to desktop, 44px+ touch targets, full keyboard board navigation, colorblind‑safe tiles (color + pattern + text label, never hue alone), light/dark/high‑contrast themes, and a real prefers-reduced-motion path.
  • Tasteful juice — spring tile flips, active‑team pulse, a dramatic assassin reveal, win confetti, and synthesized sound effects — all reduced‑motion / mute aware.
Lobby Spymaster view Mobile
Lobby Spymaster Mobile
Gamemodes Custom YAML End of game
Modes Custom YAML Endgame

🏗️ Architecture

                       share link
   ┌───────────┐   ws ┌────────────────────┐
   │  Browser  │◀────▶│   PartyKit room    │  ← one durable object per room
   │ (React/   │      │  (party/server.ts) │  ← authoritative state + storage
   │  Tailwind)│      └────────────────────┘
   └───────────┘
        ▲  role-filtered snapshots (operatives never receive the key card)
  • party/server.ts — the authoritative game server. One instance per room. Holds all state, validates every action, drives turn timers via alarms, persists to room storage, and broadcasts role‑filtered snapshots (a spymaster sees the key; everyone else gets owner: "hidden" for unrevealed tiles).
  • shared/ — code shared by client + server: types.ts (the wire contract), gamemode.ts (strict schema validation), engine.ts (pure, testable game rules), builtins.ts (the validated mode/pack registry), and the generated gamedata.ts.
  • src/ — the React client. lib/party.ts wraps partysocket (auto‑reconnect), components render the role‑filtered view, optimistic taps reconcile against the server broadcast.
  • gamemodes/*.yaml + data/wordbank.json are the human‑editable source of truth. npm run gen compiles them into shared/gamedata.ts.

See docs/research.md for the competitive research that shaped this design, and docs/ARCHITECTURE.md for a deeper dive.


🚀 Quick start

npm install
npm run dev          # runs Vite (client) + PartyKit (server) together
# → client on http://localhost:5173, party on http://localhost:1999

Open the printed URL, click Create a room, and share the link (or just open it in a second tab/device) to play.

Dev tip: if port 1999 is taken, run the server on another port and point the client at it via .env.local:

VITE_PARTYKIT_HOST=127.0.0.1:1998

then npm run dev:party -- --port 1998 and npm run dev:web.

Useful scripts

Script What it does
npm run dev client + server together (hot reload)
npm run gen compile gamemodes/*.yaml + data/wordbank.jsonshared/gamedata.ts
npm run build production build of the client → dist/
npm run typecheck strict TypeScript check
npm run deploy:party deploy the PartyKit server
node scripts/sim-game.mjs headless 4‑bot full game (asserts the engine end‑to‑end)
node scripts/sim-coop.mjs headless co‑op game smoke test

☁️ Deploy

  • Client (Vercel): ✅ live → https://codenames-rho-smoky.vercel.app
  • Realtime server (PartyKit): ⛔ currently blocked by a PartyKit platform limit (their shared partykit.dev zone is at Cloudflare's 10,000-custom-domain cap). The server builds fine and runs perfectly locally; it just can't be published to partykit.dev right now.
npm run gen
npx partykit deploy        # → https://codenames.<your-user>.partykit.dev (when capacity frees)
vercel --prod              # static client → Vercel

Set VITE_PARTYKIT_HOST=codenames.<your-user>.partykit.dev in the Vercel project env so the client talks to your party (the client already falls back to that host).

👉 See docs/DEPLOYMENT.md for the current deploy status and two paths to get realtime live (retry PartyKit, or self-host on your own Cloudflare via wrangler).


🎲 Gamemodes

Every mode is a YAML document in gamemodes/. Board size, teams (2–4 or co‑op), agent/neutral/assassin counts, clue rules (numbered, numberless, zero, ∞), guess rules (clue_plus_one / exact / unlimited / single), timers, sudden death, and win conditions are all configurable. The only hard rule: the tile distribution must exactly fill the board.

The 20 built‑ins were each rated by three independent persona judges (a casual host, a competitive player, a game designer) for fun / balance / clarity — see docs/gamemodes-judging.md. To make your own, open Change mode → Custom YAML, edit, and Load — it's validated and shared with the room live.

📚 Wordbank

11 stackable packs, 1,951 words, each pack passed through two independent audits (a safety lens and a quality lens) for offensiveness, duplicates, ambiguity, and difficulty balance. The audit log lives in docs/wordbank-audit.md. The 18+ Party pack is clearly badged and off by default.


♿ Accessibility

  • Board tiles encode ownership with color + a corner glyph + a text label (e.g. RED, BYSTANDER, ASSASSIN) — never color alone.
  • Full keyboard play: arrow‑key roving focus across the grid, Enter to select/confirm, visible focus rings.
  • ARIA roles/labels on tiles, dialogs, switches, and the board.
  • Light / dark / high‑contrast themes and a real reduced‑motion mode (respects the OS setting).

🔒 Security & quality

  • Hidden information is a server guarantee. The key card never crosses the wire to operatives or spectators — the server sends each connection a role‑filtered view, and team/role seats lock the moment a game starts, so a crafted client can't switch to spymaster mid‑game to peek. There's a regression assertion for this in scripts/sim-game.mjs.
  • Pure, tested rules. All game logic lives in shared/engine.ts (no I/O) and is exercised end‑to‑end over real WebSockets by sim-game.mjs (full 2‑team game) and sim-coop.mjs (cooperative) — win detection, turn rotation, guess limits, and the key‑leak check.
  • Reviewed. A 5‑agent adversarial UI/gameplay review plus a 4‑cycle criticality-loop (independent fresh‑context auditors across structural / deslop / correctness lenses) hardened the build — ~26 fixes, including a Critical mid‑game key‑leak and an expired‑timer recovery bug.
  • Validated config. Custom YAML gamemodes flow through one strict validateGamemode() (bounded board/teams/timers; distribution must exactly fill the board), so a pasted mode can't crash or abuse a room.

🧱 Project structure

gamemodes/            20 gamemode YAML files (source of truth)
data/wordbank.json    curated, audited wordbank
scripts/
  build-data.mjs      yaml + json → shared/gamedata.ts
  sim-game.mjs        headless multiplayer engine test
  sim-coop.mjs        headless co-op test
shared/               types, schema validation, pure engine, registry
party/server.ts       authoritative PartyKit server
src/                  React client (components, lib)
docs/                 research, audits, architecture, deployment, criticality log, screenshots

Docs index

Doc What's in it
docs/ARCHITECTURE.md server/client split, hidden‑info model, data flow
docs/DEPLOYMENT.md live status + the PartyKit blocker + two ways to get realtime live
docs/self-hosting.md research: host our own realtime backend free on Cloudflare (partyserver / cloud-prem)
docs/research.md competitive survey → "how we win" product direction
docs/wordbank-audit.md per‑pack independent safety + quality audit log
docs/gamemodes-judging.md fun / balance / clarity scores for all 20 modes
docs/criticality-loop.log.md the 4‑cycle audit‑and‑fix record

Built with PartyKit · React · Vite · Tailwind · TypeScript.

About

Realtime accountless Codenames-style word-association party game with shareable rooms, chat, configurable modes, and curated word packs.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages