Multiplayer Skip-Bo built on Cloudflare with a pure rules engine, one authoritative Durable Object per game, and a playable browser client.
- Learn Cloudflare Durable Objects by building a real turn-based multiplayer game
- Implement the Skip-Bo engine cleanly in TypeScript
- Use a networked CLI client first so the Worker and DO architecture stays visible
- Reuse the same backend later when porting to a web app
This project is also an experiment in building software quickly and correctly with agentic coding tools. The agent owns implementation and verification by default, while concise technical lessons preserve the important architecture, rules, and tradeoffs as the system grows.
- Cloudflare Workers for the public API
- Cloudflare Durable Objects for per-game state and live coordination
- Bun for local tooling and the CLI client
- TypeScript for shared game logic and transport types
- One game equals one Durable Object
- The game engine stays pure and Cloudflare-agnostic
- The Durable Object owns authority, persistence, and live broadcasts
- The CLI talks to the Worker over HTTP and WebSockets
- This repo is intentionally starting simple
- The completed CLI milestone remains a diagnostic tool; the next playable product checkpoint is the browser UI
- The main implementation plan lives in
docs/implementation-plan.md
bun installStart the local Worker:
bun run devOpen the URL Wrangler prints, normally http://localhost:8787. Create a table
in one browser tab, copy its game code, then join from a second tab. Each tab
keeps its own player token for the duration of that tab, including refreshes.
Choose the 5-card stock option for a quick test game.
On your turn, drag a highlighted hand, stock, or discard card. Legal build and discard destinations glow while dragging. Those destinations come directly from the exact commands accepted by the authoritative game engine.
The in-progress React client scaffold is available at /react/. The production
build copies the playable static client into dist/ and bundles the React route
with Bun's HTML bundler and Tailwind plugin:
bun run build:webThe generated dist/ directory is intentionally ignored.
Run the React unit checks and list the Playwright suite with:
bun test src/web
bunx playwright test --listReact Doctor blocks warnings and errors through the tracked Prek hook. Install the hook manager once per checkout, then run all configured hooks when needed:
uvx prek install
uvx prek run --all-filesPlay a short local game against the bot:
bun run play -- --seed 42 --stock 5 --name YeshChoose from the numbered legal commands shown each turn. Enter q to stop.
The seed makes the initial game and later shuffles reproducible.
Run a complete deterministic short game locally:
bun run simulate -- --seed 42 --stock 5 Ada GraceThe simulator uses the real legal-command and transition APIs. Change the seed
to explore another reproducible game, or omit the options for a quick default
run. Use --stock 30 for the standard two-player stock size.
Start the local Worker:
bun run devIn another terminal, create and exercise a room:
bun run cli create
bun run cli join <gameId> Alice
bun run cli join <gameId> Bob
bun run cli start <gameId> <alicePlayerToken> 5
bun run cli state <gameId> <alicePlayerToken>
bun run cli command <gameId> <alicePlayerToken> 1Each join response contains a server-issued player token. Each player uses their token to receive a private view containing their own hand, public pile state, and exact legal commands only when it is their turn.