A real-time multiplayer web implementation of a map-drawing board game, built with FastAPI, React, and WebSockets.
Disclaimer: This is an unofficial fan project and is not affiliated with, endorsed by, or connected to Thunderworks Games. Cartographers is a board game designed by Jordy Adan and published by Thunderworks Games. No copyrighted artwork is included in this project — all visuals are rendered programmatically. This project was built for educational and portfolio purposes.
- Real-time Multiplayer: Play with friends via WebSocket connections
- Canvas-based Game Board: Interactive board with keyboard and mouse controls
- Shareable Join Links: Easy game joining via URL links
- Admin Dashboard: Monitor active sessions, view game history, player statistics
- Scoring System: Full implementation of scoring algorithms
- Game Replay: Review every turn at the end of a game with a step-by-step replay viewer or any previous games in admin dashboard
- Dark Mode: Toggle between light and dark themes
- WASD: Move shape position
- Q/R: Rotate counter-clockwise/clockwise
- F: Flip shape horizontally
- T: Switch terrain type (if multiple available)
- E/Enter: Place shape
- Mouse Click: Move to position
cartographers/
├── server/ # FastAPI backend (Python)
│ └── app/
│ ├── api/ # REST & WebSocket endpoints
│ ├── core/ # Config, auth (JWT)
│ ├── models/ # Pydantic schemas
│ └── services/ # Game logic, scoring, room management
├── web/ # React + Vite frontend (TypeScript)
│ └── src/
│ ├── components/ # GameBoard, CardDisplay, PlayerList
│ ├── hooks/ # useGameWebSocket
│ ├── pages/ # Home, Game, Join, Admin
│ ├── services/ # API client
│ └── types/ # TypeScript types
└── deploy/ # Production deployment
├── Dockerfile # Multi-stage build (Node + Python + Nginx)
├── docker-compose.yml
├── nginx.conf # Reverse proxy config
└── .env.example # Production env template
uv sync
uv run uvicorn server.app.main:app --reload --port 8000cd web
npm install
npm run devFor local development no .env file is needed — sensible defaults are used automatically (debug=True, dev-only JWT signing key, default admin credentials).
- One JSONL logfile is created per session under
server/logs/sessions/<YYYY-MM-DD>/ - Filename format is
<UTC_TIMESTAMP>_<SESSION_ID>.jsonl - A
turn_endentry is appended after each turn with full board state for all players - Files are ignored by git via
.gitignore
The deploy/ folder contains everything needed to run the app in production. A single Docker container runs Nginx (serving the frontend, proxying API/WebSocket) and Uvicorn (FastAPI backend).
# SECRET_KEY — signs JWT player tokens
python -c "import secrets; print(secrets.token_urlsafe(32))"
# ADMIN_PASSWORD — for the admin dashboard
python -c "import secrets; print(secrets.token_urlsafe(16))"cd deploy
cp .env.example .envEdit deploy/.env and fill in:
DEBUG=false
SECRET_KEY=<paste generated key>
ADMIN_USERNAME=admin
ADMIN_PASSWORD=<paste generated password>
FRONTEND_BASE_URL=https://your-domain.comcd deploy
docker compose up -dThe app is available at http://localhost:8080.
When DEBUG=false, the server refuses to start if:
SECRET_KEYis not setADMIN_PASSWORDis still the default (admin123)
Swagger/ReDoc docs are disabled in production.
- Nginx (port 8080) — serves built React frontend, proxies
/api/and/ws/to backend - Uvicorn (internal port 8000) — runs FastAPI backend
- Security headers, rate limiting (30 req/s), WebSocket timeout (3h)
- Runs as non-root user
GET /api/sessions/{code}?key=INVITE_KEY- Get session detailsPOST /api/sessions/{code}/join?key=INVITE_KEY- Join a session (returns JWT)
POST /api/admin/sessions- Create new sessionGET /api/admin/stats- Dashboard statisticsGET /api/admin/sessions/active- Active sessionsDELETE /api/admin/sessions/{code}- Delete a session
WS /ws/game/{session_code}?token=JWT- Game connection (token from/join)
This project is licensed under the MIT License — see LICENSE for details.

