- Deal the hands from the shuffled stack and draw the trump card from it, so the trump card is in nobody's hand and cannot be played - A wizard or jester as trump card means no trump suit (previously a wizard let the dealer choose); remove the trump selection phase, the ChooseTrump action, the trump picker UI and the AI trump actions - Completed tricks carry their round's trump card, so the table keeps showing the matching trump card while the last trick of a round is displayed - Card corners are no longer rotated, so 6 and 9 cannot be confused - Trump label explains why there is no trump (wizard, jester, last round) - Tests: trump card never dealt, special cards give no trump, tricks keep their trump card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wizard online
An online version of the card game Wizard (rules: wizard_instructions/).
| Component | Path | Tech |
|---|---|---|
| Frontend | wizard_website/ |
React + TypeScript + Vite, CSS modules, served by nginx |
| Game server | wizard_server/ |
Python, FastAPI, websockets |
| Game logic | wizard_server/game_logic/ |
Pure Python package wizard_game (no dependencies) |
| AI player | wizard_ai/ |
Python; RL training skeleton + websocket service |
browser ──wss /wizard/api/ws──────▶ Caddy ──/ws──▶ wizard_server:8083 ◀──ws /internal/ai── wizard_ai
──https /wizard/website/──▶ Caddy ──/────▶ wizard_website:8082 (network wizard_internal)
How it works
- A player opens the website, enters a name and creates a lobby (and joins it).
- The lobby shows a code (and an invite link,
?lobby=CODE) others use to join. - The host can add AI players, toggle the not equal / hidden bids rules, and start the game (3–6 players).
- The server runs the game with
wizard_gameand sends every player a personal view (Observation) after each move. Reloading the page reconnects to the same seat. A player who leaves a running game is replaced by an AI player. - When an AI seat has to act, the server sends its
Observationto the AI service over the internal websocket and applies the answered action. If the AI service is unavailable or too slow, a random legal move is played so games never get stuck.
The Observation / action JSON format is defined once in wizard_game and shared by the
frontend, the server and the AI, so AI models see exactly the same data in training and in
production.
Deployment
cp .env.example .env # set AI_SHARED_SECRET
docker network create wizard_website # if they don't exist yet
docker network create wizard_api
docker compose up -d --build
wizard_websitejoins the external networkwizard_website(port 8082).wizard_serverjoins the external networkwizard_api(port 8083) andwizard_internal.wizard_aijoins onlywizard_internal, created by compose as an internal network.
In the Caddy reverse proxy, /wizard/website/* goes to wizard_website:8082
and /wizard/api/* to wizard_server:8083, both with the prefix stripped. The website uses
relative asset paths and derives the API address from its own URL (../api/ws), so it works under
any prefix as long as the website and API are siblings. /internal/ai refuses requests that come
through the proxy (forwarding headers present), in addition to requiring AI_SHARED_SECRET.
Trained AI checkpoints go into wizard_ai/models/latest/ (mounted read-only). Until one exists
the AI service uses the built-in heuristic player.
Development
python -m venv .venv && . .venv/bin/activate
pip install -r wizard_server/requirements-dev.txt -r wizard_ai/requirements-dev.txt
(cd wizard_server && uvicorn app.main:app --reload) # :8000
(cd wizard_ai && python -m wizard_ai serve) # connects to :8000
(cd wizard_website && npm install && npm run dev) # :5173
(cd wizard_server && pytest) # game logic + server tests
(cd wizard_ai && pytest)