Summary
A complete online version of the Secret Hitler board game, playable by 5β10 players in a browser. Real-time matches, immersive animations, configurable timers, spectator mode, and containerized deployment.
I loved this board game so much I wanted to play it anywhere, without installing anything. The result: a faithful clone, designed for the web β with everything that implies for real-time networking, resilience, and UX.
Goals
- Faithfully reproduce the full Secret Hitler ruleset (nomination, voting, legislative, veto, executive powers, win conditions, chaos)
- Deliver a smooth multiplayer experience with WebSocket and animations
- Support reconnection after page refresh/network loss without breaking the game
- Deploy to production with a solid stack (HTTPS, database, Redis cache)
Stack
- Django + DRF + Channels β REST API + async WebSocket via Daphne
- SvelteKit β reactive frontend with stores, animations, ceremonial components
- PostgreSQL β room, player, and game state persistence
- Redis β channel layer for multi-process WebSocket broadcast + session cache
- Docker + Nginx β containerized deployment with reverse proxy, Cloudflare Tunnel
- Upstash Redis β serverless fallback for environments without local Redis
Architecture
Browser (SvelteKit) ββWebSocketβββ Daphne (ASGI)
β
Channels (Redis)
β
GameConsumer
ββββββββββββββββ
β connection β
β dispatch β
β game actions β
β timers β
ββββββββββββββββ
β
PostgreSQL
ββββββββββββββββ
β Room β
β Player β
β GameState β
β PlayerRole β
β Vote β
ββββββββββββββββ
Nginx (reverse proxy)
ββββββββββββββββββββ
β / β SPA β
β /api/ β Django β
β /ws/ β Daphne β
β /admin/ β 404 β
ββββββββββββββββββββ
Cloudflare Tunnel β HTTPS
The game engine is pure Python with zero Django dependencies β fully testable in isolation. The WebSocket consumer orchestrates game phases (nomination β voting β legislative β executive) and manages timers with atomic PostgreSQL transactions to prevent duplicate timeouts. Reconnections are handled cleanly: a page refresh evicts the old connection and restores the player’s private state (role, cards in hand).
Notable Features
- Seamless reconnection β page refresh doesn’t lose your game; private state (role, cards) is restored automatically
- Ceremonies and animations β role envelope, vote reveal, presidential crown, execution, chaos, game over
- Configurable phase timers β nomination, voting, legislative, executive: each phase can have a timeout
- Spectator mode β watch a game without participating
- Anonymous voting β optional, configurable per room
- Veto power β faithful to the advanced rules
- Custom laws β customizable liberal and fascist law texts
- Built-in tutorial β for first-time players
- 401 tests β covering engine, API, WebSocket, and end-to-end flows
Lessons Learned
Cross-origin is hell. Deploying a SvelteKit frontend on Vercel and a Django backend on Render forced me to implement a header-based session system (
X-Session-Key) because third-party cookies are blocked. WebSocket required a?sk=query parameter for the same reason. 27 commits just for deployment.A pure game engine changes everything. Extracting the game rules into a module with zero Django dependencies made unit tests trivial and debugging fast. 401 tests, including full game flows.
Redis is non-negotiable for production WebSockets. Without a Redis channel layer, Daphne can’t broadcast between workers. And without Upstash as a serverless fallback, deploying on Render became impossible.
Refresh recovery is a must. In an online board game, players will inevitably switch tabs, lose WiFi, or close their browser. If every disconnect breaks the game, it’s unplayable. The socket eviction and
reconnect_private_statesystem solved this.