Skip to content

Repository files navigation

Root

A web implementation of the asymmetric woodland board game Root, played solo against AI opponents or with friends through a hosted website anyone can create or join games on.

Quick start

Solo (local)

npm install
npm run dev

Open http://localhost:5173, pick a faction, and play.

Hosted (Docker)

docker compose pull && docker compose up -d

Pulls the pre-built image from docker.kepi.site and starts the server on port 8787. Room data persists to a Docker volume and survives container rebuilds.

Hosted (development, no Docker)

npm run host

Starts Vite on :5173 with hot reload and the multi-room server on :8787.

Multiplayer

  1. Visit the homepage → Create game. A 6-character room code is allocated and the URL updates to /r/<code>.
  2. Share the link. Others click it or paste the code into Join a game.
  3. Each player claims a faction seat. Unclaimed seats become AI bots when the game starts.
  4. The server validates every action against the player's seat and broadcasts state to all clients.

If a player closes the tab their seat is freed and the AI takes over. They can rejoin from the same URL and reclaim any free seat.

Card art

The app fetches official card art from the Leder Games CDN at runtime. To cache all images locally (removes the CDN dependency):

npm run download-assets

This downloads all 65 card .webp files into src/assets/raw/cards/. The asset loader checks that directory first, so the CDN is no longer needed once the files are present. Re-running the command skips files that already exist. The raw/ folder is gitignored — files stay local and are never committed.

Custom art

To use your own scans, drop files into src/assets/raw/. The loader prefers them over the built-ins on a per-file basis:

src/assets/raw/
├── board/autumn.png
├── cards/<card-slug>.png        e.g. ambush-fox.png, travel-gear-mouse.png
├── factions/<faction>/icon.png · warrior.png · <building>.png
├── items/<item>.png             sword, hammer, crossbow, boots, bag, tea, coin, torch
└── dominance/<suit>.png

Card filenames are lowercase-kebab slugs of the card name (punctuation stripped, spaces → -). Run node scripts/list-asset-names.mjs to print every expected filename.

Admin

Set ADMIN_PASSWORD in .env to enable /admin. Without it, all admin routes return 503.

echo 'ADMIN_PASSWORD=your-secret' >> .env

The admin page lists every room with timestamps and player state, and lets you delete rooms or run a stale-room prune.

Scripts

Command What it does
npm run dev Vite dev server, single-player local
npm run host Vite + multi-room WS server (two ports)
npm run server Multi-room server only (serves ./dist)
npm run build Production bundle into dist/
npm test Vitest test suite
npm run typecheck tsc -b --noEmit
npm run download-assets Download all card art from CDN into src/assets/raw/cards/
npm run release Build multi-arch Docker image and push :VERSION + :latest
npm run prune-stale One-shot stale-room cleanup (--days N, --dry-run)

Release

Bump "version" in package.json, then:

npm run release

Builds a multi-arch (linux/amd64 + linux/arm64) image, tags it with the version number and latest, and pushes both to docker.kepi.site. Deploy on the server with:

docker compose pull && docker compose up -d

Environment variables

Copy .env.example to .env and edit. docker compose and the dev scripts both read it automatically.

Server

Variable Default Purpose
PORT 8787 HTTP + WebSocket port
DIST_DIR ./dist Where to serve the React bundle from
DATA_DIR ./data/rooms Where to write per-room JSON files
MAX_ROOM_AGE_DAYS 90 Rooms idle longer than this are auto-pruned
ADMIN_PASSWORD unset Enables /admin. Unset → admin disabled (503)

Datadog (optional)

Telemetry is off by default. Set DD_TRACE_ENABLED=true and run a Datadog Agent to enable APM traces and custom metrics.

In the Docker Compose setup, DD_AGENT_HOST is hardcoded to host.docker.internal (with extra_hosts: host-gateway for Linux) so the container always reaches the agent on the Docker host. The other variables default via shell substitution in docker-compose.yml and can be overridden in .env.

Variable Compose default Purpose
DD_TRACE_ENABLED true Set to false to disable. Any non-true → off
DD_AGENT_HOST host.docker.internal Hostname of the Datadog Agent
DD_TRACE_AGENT_PORT 8126 APM trace port on the agent
DD_SITE datadoghq.com Datadog intake site
DD_SERVICE root-game Service name shown in Datadog
DD_ENV home Environment tag (production, staging, …)
DD_VERSION unset Version tag, e.g. 1.0.0

Custom metrics emitted:

Metric Type Tags
root.room.created count
root.room.deleted count
root.room.pruned count count:N
root.game.started count factions:<order>
root.game.over count winner:<faction>, via:<reason>
root.action.applied count kind:<faction_action>
root.ws.connected count
root.ws.disconnected count
root.bot.turn_ms histogram
root.rooms.active gauge — (reported every 30 s)
root.players.online gauge — (reported every 30 s)

HTTP requests are traced automatically via dd-trace auto-instrumentation (no manual spans required).

License

The code in this repository is mine. The game itself, rules, and any artwork are © Leder Games. This project is for personal use; deploy publicly with original art only.

About

Web implementation of the Root board game with AI opponents and hosted multiplayer.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages