71 lines
3.4 KiB
Markdown
71 lines
3.4 KiB
Markdown
# Wizard online
|
||
|
||
An online version of the card game Wizard (rules: `wizard_instructions/`).
|
||
|
||
**Detailed documentation of the whole codebase: [`docs/`](docs/README.md).**
|
||
|
||
| 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 *hidden bids* option, and start the game (3–6 players).
|
||
The sum of all bids may never equal the number of cards in hand (always on).
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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)
|
||
```
|