Deal City is an online card game for 2–3 players, played in the browser. It follows the rules of a well-known property-trading card game, with original names and art: collect three full property sets of different colors before anyone else, and charge rent, make deals and steal sets along the way. Deal City is not affiliated with or endorsed by any publisher.
What is in it:
- the full rules, played by a pure engine (
packages/engine) that the server runs; - a table drawn over a looping video backdrop, with drag-and-drop play, card flights and other animation, and sound (animation and sound can be switched off in settings);
- original SVG card art, with a
/gallerypage that shows every card; - text chat and voice chat in the room, for friends who are not in the same place;
- phones and tablets as well as desktops.
Players join as guests with a nickname and a 6-character room code or an invite link. There are no accounts and no database.
Requirements: Node 22 or newer, and pnpm 9 (corepack enable provides it).
pnpm install
pnpm devpnpm dev starts the game server on port 3000 and the web app on port 5173. Open http://localhost:5173, create a room, and open the invite link in another tab. Each tab is its own player, and a reload keeps your seat.
pnpm test # engine, protocol, server and web unit and integration tests
pnpm typecheck
pnpm lint
pnpm e2e # end-to-end tests in real browserspnpm e2e needs Google Chrome installed. It builds the app, starts a test-mode server on port 3100, and plays a seeded 3-player game, checks the card sheet and runs the smoke test. The card sheet screenshot is saved under apps/e2e/test-results/.
On a VPS with Docker, point your domain's DNS at the server, then run:
DOMAIN=cards.example.com docker compose up -d --buildCaddy gets and renews the HTTPS certificate and proxies the game, including its WebSocket connection. Check the server with:
curl https://cards.example.com/healthzTo smoke test any deployment from your machine:
E2E_BASE_URL=https://cards.example.com pnpm --filter @deal-city/e2e smokeTo try the stack locally on other ports, run the command below and open https://localhost:8443. Your browser will warn about Caddy's local certificate.
HTTP_PORT=8080 HTTPS_PORT=8443 docker compose up -d --buildGames live in memory. A restart or redeploy ends the games in progress.
The server reads these environment variables:
| Variable | Default | Meaning |
|---|---|---|
PORT |
3000 |
HTTP and Socket.IO port |
TURN_MS |
60000 |
Time for a turn |
RESPONSE_MS |
20000 |
Time to answer an action or pay |
GRACE_MS |
120000 |
How long a disconnected player's seat is held |
EMPTY_ROOM_MS |
600000 |
How long an empty room is kept |
RATE_LIMIT |
20 |
Messages per second per connection |
MAX_ROOMS |
1000 |
Most rooms at once |
CF_TURN_KEY_ID |
Cloudflare Realtime TURN key id: with the token, voice chat can relay through Cloudflare on strict networks | |
CF_TURN_API_TOKEN |
That key's API token. Both stay on the server; without them voice uses STUN only | |
WEB_DIST |
apps/web/dist |
Built web app to serve (set in the Docker image) |
NODE_ENV |
test lets clients choose the deck seed. Never set it to test in production. |
With docker compose, put any of these in a .env file next to docker-compose.yml. Compose passes that file to the server, and also reads DOMAIN (default localhost), HTTP_PORT (default 80) and HTTPS_PORT (default 443) from it. For example:
DOMAIN=cards.example.com
TURN_MS=90000
packages/engine: the rules engine. It is pure and deterministic, with no IO.packages/protocol: socket message schemas (zod) and shared types.apps/server: Fastify and Socket.IO. It runs rooms, timers and reconnects, and serves the web app.apps/web: React and Vite. It holds the game UI, the table, motion, sound, chat, voice and the SVG card art./gallerylists the cards;/labis a developer page for table scenarios.apps/e2e: Playwright end-to-end and smoke tests.
The rules, the decisions made where the official rules are unclear, and the architecture are in the design spec. The other specs, the plans and the review notes are mapped in docs/superpowers/README.md. Sound and ambience credits are in CREDITS.md.