added docs

This commit is contained in:
2026-09-27 11:26:10 +02:00
parent 87151c2cc9
commit 905574e8f1
10 changed files with 1736 additions and 1 deletions
+168
View File
@@ -0,0 +1,168 @@
# 5. Protocol reference
All messages are JSON objects with a `type` field, sent as websocket text
frames. There are two protocols:
- **Client protocol** — browser ↔ server on `/ws`
(public URL `wss://game.marvinkrausser.com/wizard/api/ws`).
Schema: `wizard_server/app/protocol.py` (Python) and
`wizard_website/src/api/types.ts` (TypeScript). Keep both in sync.
- **AI protocol** — AI service ↔ server on `/internal/ai`.
Implemented in `wizard_server/app/ai_bridge.py` and
`wizard_ai/wizard_ai/service/client.py`.
Game objects inside messages (cards, actions, observations, events) use the
engine's JSON format described in [the engine chapter](03-game-engine.md#json-format).
---
## Client protocol
### Browser → server
| `type` | Fields | Allowed when | Effect |
|--------|--------|--------------|--------|
| `hello` | `token: string \| null` | always; must be the first message | starts or resumes a session |
| `create_lobby` | `name` | after hello | creates a lobby and joins it as host |
| `join_lobby` | `code`, `name` | after hello | joins an existing lobby |
| `leave_lobby` | – | in a lobby | leaves (during a game an AI takes the seat) |
| `add_ai` | – | host, lobby waiting, < 6 seats | adds an AI seat |
| `remove_player` | `player_id` | host, lobby waiting | removes a seat (human or AI) |
| `set_options` | `hidden_bids: bool` | host, lobby waiting | changes game options |
| `start_game` | – | host, lobby waiting, 3–6 seats | starts the game |
| `action` | `action: {"type":"bid","value":n}` or `{"type":"play","card":id}` | your turn in a running game | performs a move |
| `return_to_lobby` | – | host, game finished | back to the waiting lobby |
Validation (`protocol.py`):
- `name`: whitespace stripped, 1–20 characters.
- `code`: whitespace stripped, upper-cased, 4–12 characters.
- Unknown `type` or wrong field types → `error` message, connection stays open.
### Server → browser
#### `welcome`
Sent after every `hello`.
```json
{"type": "welcome", "player_id": "3f9c0a1b2d4e", "token": "Zq…", "name": "Marvin"}
```
The browser stores `token` (in `sessionStorage`) and sends it in the next
`hello` to resume the same session. `player_id` identifies you in
`lobby.players` and `lobby.host_id`.
#### `state`
Sent after `hello` and whenever anything in your lobby changes. It always
contains the **complete** current state.
```json
{
"type": "state",
"lobby": {
"code": "7C7TAJ",
"status": "playing",
"host_id": "3f9c0a1b2d4e",
"options": {"hidden_bids": false},
"players": [
{"id": "3f9c0a1b2d4e", "name": "Marvin", "is_ai": false, "connected": true},
{"id": "ai-1a2b3c4d", "name": "Merlin", "is_ai": true, "connected": true},
{"id": "ai-5e6f7a8b", "name": "Morgana", "is_ai": true, "connected": true}
],
"min_players": 3,
"max_players": 6,
"ai_available": true
},
"game": {
"you": 0,
"observation": { "…": "the personal Observation of seat 0" }
},
"events": [
{"type": "trick_completed", "trick": {"…": "…"}}
]
}
```
- `lobby: null, game: null` → you are not in a lobby (start page).
- `game: null` → lobby is waiting (no game running).
- `players[i]` is seat `i` in the game (`observation` uses the same indices).
- `events` lists what the last action caused: `trick_completed`,
`round_completed`, `round_started`, `game_finished`. Usually empty.
- `ai_available` is `false` when no AI service is connected; AI seats then play
random moves.
#### `error`
```json
{"type": "error", "message": "Only the host can do that"}
```
Sent only to the client that caused it. Typical messages: `Send 'hello' first`,
`Invalid message: …` (followed by the pydantic reason),
`Lobby ABC123 does not exist`, `The game in this lobby has already started`,
`This lobby is full`, `Only the host can do that`,
`Wizard needs 3 to 6 players`, `Illegal action Bid(value=2) for player 0`,
`It is not player 1's turn`, `You were removed from the lobby`,
`This session was opened in another window`.
### Close codes
| Code | Meaning | Frontend reaction |
|------|---------|-------------------|
| (handshake rejected) | origin not in `ALLOWED_ORIGINS`; the server closes with 1008 before accepting, so the browser sees a failed connection | reconnects with backoff |
| 4000 | the session was opened in another tab | stops reconnecting, shows a banner |
| other | network problem, server restart | reconnects (1 s, 2 s, 4 s … max 10 s) |
### Example conversation
```mermaid
sequenceDiagram
participant H as Host browser
participant S as Server
participant G as Guest browser
H->>S: hello {token:null}
S-->>H: welcome, state {lobby:null}
H->>S: create_lobby {name:"Ann"}
S-->>H: state {lobby: code "K7M2QX", players:[Ann]}
G->>S: hello {token:null}
S-->>G: welcome, state {lobby:null}
G->>S: join_lobby {code:"k7m2qx", name:"Ben"}
S-->>H: state {players:[Ann, Ben]}
S-->>G: state {players:[Ann, Ben]}
H->>S: add_ai
S-->>H: state {players:[Ann, Ben, Merlin]}
S-->>G: state {…}
H->>S: start_game
S-->>H: state {game:{you:0, observation{phase:"bidding", current_player:1}}}
S-->>G: state {game:{you:1, …}}
G->>S: action {type:"bid", value:0}
S-->>H: state {…}
S-->>G: state {…}
```
---
## AI protocol
The AI service connects to `ws://wizard_server:8083/internal/ai` with the
header `Authorization: Bearer <AI_SHARED_SECRET>` and without forwarding
headers (see "Routes" in [the server chapter](04-server.md)).
| Direction | Message | Meaning |
|-----------|---------|---------|
| AI → server | `{"type":"hello","agent":"heuristic"}` | informational, logged |
| server → AI | `{"type":"decide","request_id":"<hex>","observation":{…}}` | choose an action for `observation.player` |
| AI → server | `{"type":"action","request_id":"<same>","action":{"type":"play","card":17}}` | the chosen action |
| AI → server | `{"type":"error","request_id":"<same>","message":"…"}` | the AI failed; the server plays a random move |
Rules:
- Requests are independent; the AI service may answer them in any order
(matched by `request_id`) and may handle several concurrently.
- The answer must be one of `observation.legal_actions`; otherwise the server
plays a random legal move.
- No answer within `AI_DECISION_TIMEOUT` (5 s) → random move.
- Several AI service instances may connect; the server distributes requests
round robin.