5.5 KiB
API contract v1
Machine-readable HTTP documentation: openapi.yaml.
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 0–9 |
name |
1–20 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:
{"ok":true,"version":17,"gameId":4}
Failure (maximum 160 encoded bytes):
{"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; includes reset reason |
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:
{"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.
{"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:
{"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.