marvinandClaude Opus 5.5 fde8e2fb2e Always forbid bids that add up to the cards in hand
The sum of all bids may never equal the number of cards each player
holds in the round; the last bidder has to adjust their bid. This was an
optional "not equal" lobby setting and is now a fixed rule of the game
engine, so it applies to humans, AI players and training alike.

- Remove GameOptions.not_equal, the set_options field, the lobby toggle
  and the --not-equal training flag
- Explain the rule in the lobby and show the blocked bid while bidding
- Drop the option from the AI observation encoding
- Tests check that no round ends with bids summing to the cards in hand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:15:25 +02:00
2026-09-27 08:58:25 +02:00
2026-09-27 09:52:00 +02:00
2026-09-27 09:52:00 +02:00
2026-09-27 08:58:25 +02:00

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

  1. A player opens the website, enters a name and creates a lobby (and joins it).
  2. The lobby shows a code (and an invite link, ?lobby=CODE) others use to join.
  3. The host can add AI players, toggle the not equal / hidden bids rules, and start the game (3–6 players).
  4. The server runs the game with wizard_game and 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.
  5. When an AI seat has to act, the server sends its Observation to 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_website joins the external network wizard_website (port 8082).
  • wizard_server joins the external network wizard_api (port 8083) and wizard_internal.
  • wizard_ai joins only wizard_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)
S
Description
No description provided
Readme
920 KiB
Languages
Python 59.5%
TypeScript 16.8%
CSS 11.9%
HTML 10.7%
Dockerfile 0.9%
Other 0.2%