From 970206fb995335a4ba82adc733ce790fc6ddbf19 Mon Sep 17 00:00:00 2001 From: Gabriel Franco Date: Thu, 10 Sep 2026 10:37:52 -0300 Subject: [PATCH] feat(backend): scaffold matchmaking server project Bare HTTP + WebSocket bootstrap (health check route, ws upgrade, connection logging) with matchmaking/ and rooms/ reserved but unimplemented. No matchmaking logic yet. Also adds root README.md (project overview) and AGENTS.md (architecture map, code style, git workflow). --- AGENTS.md | 70 +++++++++++++++++++++++++++++ README.md | 56 +++++++++++++++++++++++ backend/.env.example | 1 + backend/package-lock.json | 36 +++++++++++++++ backend/package.json | 15 +++++++ backend/src/index.js | 24 ++++++++++ backend/src/matchmaking/README.md | 4 ++ backend/src/rooms/README.md | 5 +++ backend/src/ws/connectionHandler.js | 8 ++++ 9 files changed, 219 insertions(+) create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 backend/.env.example create mode 100644 backend/package-lock.json create mode 100644 backend/package.json create mode 100644 backend/src/index.js create mode 100644 backend/src/matchmaking/README.md create mode 100644 backend/src/rooms/README.md create mode 100644 backend/src/ws/connectionHandler.js diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..76fb992 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +# AGENTS.md + +Context and working agreements for anyone (human or agent) changing this +repo. See `README.md` for what the product is; this file is about how we +build it. + +## Architecture + +`frontend/` (Vite + TypeScript, **no UI framework** — plain DOM) is +layered bottom-up; each layer only imports from the ones below it: + +``` +types.ts shared domain types (Token, CategoryState, ThemeDefinition, ...) +data/ static content: theme palettes, code samples +engine/ pure, DOM-free utilities: tokenizer, color math, particles, sound +game/ canvas rendering, scoring, panel/result DOM views, ThemeGuessGame +preview/ the pre-round "flash the real theme" renderer +ui/ composable DOM widgets (theme grid, preview countdown flow) +main.ts composition root — wires the above to the page, nothing else +``` + +New game logic goes in `engine/` if it doesn't touch the DOM, `game/` if +it does. Don't reach for React/Vue/etc.: the canvas rendering and the +handful of DOM widgets don't need a framework, and adding one is a +bigger discussion than a drive-by PR. + +`backend/` is currently a bare HTTP+WebSocket bootstrap +(`src/index.js` + `src/ws/connectionHandler.js`) with `src/matchmaking/` +and `src/rooms/` reserved but empty (see the `README.md` in each). Plain +Node with ESM (`"type": "module"`), no framework, no TypeScript — keep it +that way until there's an actual reason to add one. + +## Code style + +- Frontend TypeScript runs under `strict`. Don't widen types to work + around an error without understanding why it's there. +- Small static string-keyed lookup tables are `Record`, not `Set`/ + `Map` — reach for `Set`/`Map` only for dynamic membership, non-string + keys, or when you need `.size`/iteration order/`.clear()`. +- Don't wrap a single expression in a named function just to name it, + unless the name is a stable public API, a callback whose identity + matters, or documents a non-obvious formula. Inline the rest. +- A property literally named `constructor` in an object literal typed as + `Record` gets its value type silently widened by TS (see + `engine/tokenizer.ts`'s `toLookup`) — build such tables from an array + via `Object.fromEntries` instead of a literal when a key might collide. + +## Git + +- Trunk is `master`; keep it green (typechecks, builds, game loads). +- One short-lived branch per change, prefixed by intent: + `feat/…`, `fix/…`, `refactor/…`, `chore/…`, `docs/…`. +- Commit messages follow Conventional Commits: + `type(scope): summary` — e.g. `refactor(frontend): split game into modules`, + `feat(backend): add matchmaking queue`. Scope is usually `frontend`, + `backend`, or a package-relative area (`engine`, `ui`, `ws`, ...). +- Squash-merge feature branches into `master`; delete the branch after. + Keep history readable — no "wip", "fix typo", "asdf" commits on `master`. +- Frontend and backend evolve independently; a PR touching only one + should only bump/discuss that package's version and deps. + +## Verification expectations + +- Frontend change: `npm run typecheck` (or `build`) in `frontend/`, plus + an actual smoke test (open the dev server, play a round) for anything + touching rendering, input, or scoring — type-checking alone doesn't + catch a canvas drawn one line too high. +- Backend change: whatever's implemented must actually boot + (`npm start`) and the touched transport (HTTP route / WS message) must + be exercised manually or with a script; there's no test suite yet. diff --git a/README.md b/README.md new file mode 100644 index 0000000..82eb468 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Theme Guess + +A browser game about syntax-highlighting themes. A snippet of code is +rendered with every token type (keywords, strings, functions, variables, +comments, ...) in a neutral color. You click a token, every token of that +same kind lights up together, and you pick a color for it — repeat until +the whole file is themed. Reveal shows how close your from-scratch palette +landed to a real theme (Gruvbox, Tokyo Night, VS Code Dark+, GitHub Dark, +Dracula, Catppuccin), token kind by token kind. + +The project is moving from a single-player-only page toward supporting +**multiplayer matchmaking** (race another player to the closest match on +the same theme/snippet). This repo is split into a frontend game client +and a backend matchmaking/realtime server so that work can land +independently. + +## Layout + +``` +frontend/ Canvas game client: Vite + TypeScript, no UI framework. +backend/ Matchmaking + realtime session server: Node, HTTP + WebSocket. +``` + +Each package has its own `package.json`, dependencies, and scripts — see +`frontend/README.md`-equivalent notes below and `backend/src/*/README.md` +for what's reserved but not implemented yet. + +## Running the frontend + +```sh +cd frontend +npm install +npm run dev # Vite dev server with HMR +npm run build # type-check + production bundle to dist/ +npm run typecheck # tsc --noEmit only +``` + +## Running the backend + +```sh +cd backend +npm install +npm start # node src/index.js +npm run dev # same, with --watch +``` + +Boots a plain HTTP server (with a `/health` check) and attaches a +WebSocket server to it. Matchmaking and room/session logic are not +implemented yet — see `backend/src/matchmaking/README.md` and +`backend/src/rooms/README.md`. + +## Status + +Single-player game: playable. Multiplayer: backend is a bare HTTP+WS +scaffold with no matchmaking logic yet; frontend has no multiplayer UI +yet. See `AGENTS.md` for architecture notes and contribution conventions. diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..25241b7 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +PORT=8080 diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..4da1b4a --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "theme-guess-backend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "theme-guess-backend", + "version": "0.1.0", + "dependencies": { + "ws": "^8.18.0" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..f532b1f --- /dev/null +++ b/backend/package.json @@ -0,0 +1,15 @@ +{ + "name": "theme-guess-backend", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Matchmaking and realtime session server for Theme Guess (HTTP + WebSocket).", + "main": "src/index.js", + "scripts": { + "start": "node src/index.js", + "dev": "node --watch src/index.js" + }, + "dependencies": { + "ws": "^8.18.0" + } +} diff --git a/backend/src/index.js b/backend/src/index.js new file mode 100644 index 0000000..108320e --- /dev/null +++ b/backend/src/index.js @@ -0,0 +1,24 @@ +import http from 'node:http'; +import { WebSocketServer } from 'ws'; +import { handleConnection } from './ws/connectionHandler.js'; + +const PORT = process.env.PORT || 8080; + +const server = http.createServer((req, res) => { + if (req.url === '/health') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'ok' })); + return; + } + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not found'); +}); + +// The WebSocket server shares the HTTP server's port via the upgrade +// handshake, so one process serves both plain HTTP and `ws://`. +const wss = new WebSocketServer({ server }); +wss.on('connection', handleConnection); + +server.listen(PORT, () => { + console.log(`Matchmaking server listening on :${PORT}`); +}); diff --git a/backend/src/matchmaking/README.md b/backend/src/matchmaking/README.md new file mode 100644 index 0000000..50d9575 --- /dev/null +++ b/backend/src/matchmaking/README.md @@ -0,0 +1,4 @@ +# matchmaking/ + +Reserved for the matchmaking queue: pairing waiting players, party/lobby +handling, and handing off matched players to a room. Not implemented yet. diff --git a/backend/src/rooms/README.md b/backend/src/rooms/README.md new file mode 100644 index 0000000..7d3e004 --- /dev/null +++ b/backend/src/rooms/README.md @@ -0,0 +1,5 @@ +# rooms/ + +Reserved for game-session/room state: tracking which sockets belong to a +match, broadcasting round state, and cleaning up on disconnect. Not +implemented yet. diff --git a/backend/src/ws/connectionHandler.js b/backend/src/ws/connectionHandler.js new file mode 100644 index 0000000..6e1e485 --- /dev/null +++ b/backend/src/ws/connectionHandler.js @@ -0,0 +1,8 @@ +// Placeholder connection handler. Matchmaking, room assignment, and +// game-state sync are not implemented yet — this only proves the +// WebSocket transport itself works end to end. + +export function handleConnection(socket) { + console.log('client connected'); + socket.on('close', () => console.log('client disconnected')); +}