Velodrome
Real-time multiplayer typing race
Overview
A typing race game with live multiplayer over Socket.io, ghost replays of your personal best, and per-key analytics that surface the characters slowing you down.
The problem
Typing trainers are either single-player drills with no stakes, or laggy multiplayer toys. I wanted something that felt competitive and instant, gave you a real opponent to chase even when offline, and actually told you why you were slow rather than just showing a WPM number.
Approach
- A single useGame state-machine hook owns all timing, scoring, and keystroke logging so race logic lives in one predictable place.
- Ghost Racer records the keystroke timeline of your personal best and replays it as a translucent opponent; beating it overwrites the ghost.
- Live multiplayer uses 4-letter room codes; Socket.io broadcasts progress and detects the winner across players in real time.
- Per-key analytics track mistyped characters over the last 20 races to highlight your problem keys.
Architecture
- React 18 + Vite front end styled with Tailwind; a Canvas layer renders the live WPM chart sampled twice per second.
- Node.js + Express signaling server with Socket.io managing rooms, progress broadcast, and winner detection.
- Local-first storage: race history, personal bests, and ghost data persist in localStorage for fully offline play.
- Leaderboard backed by MongoDB, degrading gracefully to in-memory storage when no database is configured.
Challenges solved
Replaying a ghost without a second simulation
Re-simulating a past race in parallel would have doubled the game loop and risked drift.
Instead the ghost's position is derived by binary-searching the recorded keystroke timeline against the current race clock, so the replay is exact and nearly free.
Keeping multiplayer honest
Browser typing games are trivial to cheat with paste or autofill.
Paste and drag-and-drop are blocked during races, and losing tab focus mid-race is flagged so suspicious runs stand out.
Performance & optimizations
- Canvas-based WPM chart sampled at 2 Hz keeps the live graph smooth without re-rendering React on every frame.
- Standard WPM definition (one word = five correct characters) computed incrementally from the keystroke log.
- Local-first persistence means the whole single-player experience runs with zero network round-trips.
- Leaderboard falls back to in-memory storage so the app runs even without MongoDB provisioned.
What I learned
- A single source-of-truth state machine made an interactive, timing-sensitive game far easier to reason about.
- You can fake a convincing 'opponent' with recorded data and a binary search, no extra simulation required.
- Local-first design removes a whole class of latency and reliability problems before they start.
Future work
- Persisted accounts and global ranked leaderboards.
- Spectator mode for live rooms.
- Richer analytics: bigrams and finger-level heatmaps, not just single keys.