Files
battleship/docs/API_CONTRACT.md
T

5.5 KiB
Raw Blame History

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 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:

{"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.