Code documentation
Code documentation
A file-by-file walkthrough of the Collector codebase, written so somebody who has never seen the project can read the code in a sensible order and understand why each piece exists — not just what it does.
The top-level ../README.md is the product/assessment write-up:
setup, design decisions, stress results. These docs are the code tour.
Read in this order
| # | Document | Covers |
|---|---|---|
| 1 | 01-architecture.md | The big picture, the layers, the one rule that shapes everything |
| 2 | 02-flows.md | End-to-end walkthroughs: join, move, collect, game over, reconnect |
| 3 | 03-config-and-startup.md | config.js, server.js, app.js — how the process boots |
| 4 | 04-game-engine.md | src/game/ — all the rules, zero dependencies |
| 5 | 05-realtime.md | src/realtime/ — Socket.IO wiring and the 20 Hz loop |
| 6 | 06-http-api.md | src/routes/, src/controllers/, src/middleware/ |
| 7 | 07-persistence.md | Prisma schema, src/db/, src/services/ |
| 8 | 08-frontend.md | public/ — the browser client |
| 9 | 09-tests-and-scripts.md | tests/, scripts/ |
In a hurry? Read 01-architecture.md, then the "collect" section of 02-flows.md. Those two cover the parts of this project that are actually interesting.
The 30-second version
One Node process runs Express (HTTP) and Socket.IO (websockets) on the same port.
- Live game state lives in memory, in plain JavaScript objects. Movement and scoring never touch the database.
- Postgres stores only what outlives a match: finished games, per-player results, and an all-time leaderboard aggregate.
- One 20 Hz loop broadcasts a snapshot per lobby. Player input mutates memory but never triggers a broadcast of its own.
- Socket.IO rooms keep lobbies isolated from each other.
Two conventions used everywhere
No classes. Game state is a plain object; every rule is a plain exported function whose first argument is that object:
const game = createGame({ code: 'AB12CD' });
addPlayer(game, { username: 'alice' });
tryCollect(game, 'p0', 'c3');
There is no this anywhere in src/. State stays JSON-serialisable and every
rule is callable from a test with a literal object.
Results, not exceptions. Game functions return { ok: true, ... } or
{ ok: false, reason: 'SOME_CODE' }. Throwing is reserved for genuinely
exceptional situations. Callers branch on ok; the reason codes are what the
socket layer forwards to clients.