Files
battleship/docs/API_CONTRACT.md
T

105 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API contract v1
All JSON is UTF-8 and uses `Content-Type: application/json`. API responses set
`Cache-Control: no-store`. A request exceeding its route limit is rejected
before parsing with `PAYLOAD_TOO_LARGE`; malformed JSON is `MALFORMED_JSON`.
All numeric fields are decimal JSON integers, never strings.
## Common values
| Type | Values / bound |
| ------------------- | --------------------------------------------------------------------------- |
| `role` | `player1`, `player2`, `spectator` |
| `mode` | `human`, `bot` |
| `phase` | `lobby`, `preparing`, `in_progress`, `finished`, `rematch_wait` |
| `token` | exactly 32 lowercase hexadecimal characters |
| `gameId`, `version` | unsigned 32-bit integer |
| `x`, `y` | integer 09 |
| `name` | 120 scalar values, at most 80 UTF-8 bytes |
| `board` | exactly 100 ASCII cells: `0` unknown/water, `1` revealed ship, `2` miss, `3` hit, `4` sunk hit |
Every state-changing request has `token` and `gameId`; they must precede any
mutation validation. A stale `gameId` is rejected as `STALE_GAME`.
## Response envelope
Success:
```json
{"ok":true,"version":17,"gameId":4}
```
Failure (maximum 160 encoded bytes):
```json
{"ok":false,"code":"NOT_YOUR_TURN","message":"Сейчас ход соперника","version":17}
```
`code` is one of `MALFORMED_JSON`, `PAYLOAD_TOO_LARGE`, `INVALID_NAME`,
`INVALID_ROLE`, `INVALID_MODE`, `INVALID_COORDINATE`, `UNAUTHORIZED`,
`NO_PLAYER_SLOT`, `NO_SPECTATOR_SLOT`, `FORBIDDEN_ROLE`, `WRONG_PHASE`,
`NOT_YOUR_TURN`, `CELL_ALREADY_SHOT`, `STALE_GAME`, or `SERVER_BUSY`.
`message` is Russian and at most 80 UTF-8 bytes.
## HTTP routes
| Route | Maximum request | Response / maximum |
| -------------------------- | --------------: | --------------------------------------------- |
| `GET /api/info` | 128 B target | public device/slot state, 192 B |
| `GET /api/health` | 128 B target | diagnostics without secrets, 320 B |
| `POST /api/session/join` | 192 B body | `{name,requestedRole}`; token and role, 192 B |
| `POST /api/session/resume` | 96 B body | `{token}`; role and state metadata, 192 B |
| `POST /api/game/config` | 96 B body | `{token,gameId,mode}`; common envelope |
| `POST /api/game/start` | 80 B body | `{token,gameId}`; common envelope |
| `POST /api/game/shot` | 96 B body | `{token,gameId,x,y}`; common envelope |
| `POST /api/game/rematch` | 80 B body | `{token,gameId}`; common envelope |
| `POST /api/game/abort` | 80 B body | `{token,gameId}`; common envelope |
| `GET /api/state?version=N` | 128 B target | one role-safe state, 512 B |
| `GET /api/statistics` | 128 B target | role and bounded match/cumulative counters |
The session token is in each POST body. For `GET /api/state`, it is supplied
in `X-Session-Token`; absence creates a spectator-safe view. It is never a URL
parameter.
## Role-safe state event
The HTTP state response and WebSocket `state` event use this single 512-byte
maximum schema:
```json
{"type":"state","version":17,"gameId":4,"phase":"in_progress","mode":"human","viewer":"player1","turn":"player2","boards":["000...100 cells...","000...100 cells..."],"wins":[0,0],"winner":null,"statistics":[[3,2,1,1],[4,1,3,0]]}
```
`boards[0]` belongs to player 1 and `boards[1]` to player 2. The presenter
replaces every unauthorized unhit ship with `0`. In `finished`, both boards may
contain `1`. `winner` is `null` until `finished`, then player index `0` or `1`.
Each compact statistics tuple is `[shots,hits,misses,shipsSunk]`. No other event
contains a board.
## Statistics response
`GET /api/statistics` accepts the same optional `X-Session-Token` as state and
returns no board data. `match` uses `[shots,hits,misses,shipsSunk]` per side;
`cumulative` uses `[games,wins,losses,shipsSunk,shots,hits,misses]` per side.
```json
{"ok":true,"viewer":"player1","gameId":4,"match":[[3,2,1,1],[4,1,3,0]],"cumulative":[[2,1,1,10,30,15,15],[2,1,1,8,28,14,14]]}
```
## WebSocket
Endpoint: `GET /ws`; all incoming frames are text JSON, at most 192 B. The
first frame must arrive within 5 seconds:
```json
{"type":"hello","token":"32-lowercase-hex-characters","version":17}
```
It receives a `state` snapshot. Supported inbound frames are `hello` (96 B),
`ping` (16 B), `config` (96 B), `start` (80 B), `shot` (96 B), `rematch`
(80 B), and `abort` (80 B). Their fields exactly match the corresponding HTTP
commands. Outbound `state` is at most 512 B; `error` uses the common 160-byte
failure envelope; `pong` is 16 B. Commands are idempotent when the same
`gameId`, `version`, and command payload are retried: the server returns the
current state rather than applying the action twice.