feat(server): layout-aware room.create + resolved layout echoes (Phase C)
Extends the WebSocket protocol so clients can request a specific
starting layout when creating a room, and receive the resolved
layout echoed back on room.created / room.joined.
Protocol additions (Zod):
- room.create.payload.layout: optional discriminated union of
{ kind: "premade", id } | { kind: "fen", fen, name? }
| { kind: "custom", pieces, name? }
- room.created / room.joined: new optional 'layout' field with
resolved { id, name, pieces } — present on new servers, absent
on legacy ones (backward compat).
- LAYOUT_INVALID error code for validation failures.
- PiecePlacement + ResolvedLayout schemas.
Server implementation:
- New layouts.ts: resolveLayoutRequest() handles premade lookup,
FEN parse, custom pieces. Chess960 picks a random seed server-
side (the ultimate authority on 'which position'). Always runs
validateLayout — invalid input returns LAYOUT_INVALID, never
mutates room state.
- rooms.ts: Room.layout field + RoomRegistry.createRoom/joinRoom
accept/return resolved layouts. Default is CLASSIC_LAYOUT.
- game-session.ts: GameSession + GameSessionRegistry.create
accept StartingLayout; constructs ChessEngine({ layout }).
- broadcast.ts: handleRoomCreate resolves+validates first, rejects
with LAYOUT_INVALID toast, otherwise threads layout into the
room + session + response. handleRoomJoin echoes the room's
stored layout to the joiner.
Chess package:
- packages/chess/src/index.ts exports LAYOUT_REGISTRY, all premade
layouts, buildChess960Layout, toFen/fromFen, validateLayout,
and the related types — so server can pull them without
importing internal module paths.
Tests: 20 new tests (11 in protocol.test.ts for the layout union,
12 in layouts.test.ts for server-side resolution).
1011 tests passing; bun run check clean.
PROTOCOL.md updated with examples of all three layout kinds.
This commit is contained in:
parent
f7099d754d
commit
174cad6ae7
9 changed files with 595 additions and 13 deletions
|
|
@ -54,11 +54,23 @@ Request payload:
|
|||
|
||||
```json
|
||||
{
|
||||
"rulesetIds": ["pawns-move-backward", "piece-hp"]
|
||||
"rulesetIds": ["pawns-move-backward", "piece-hp"],
|
||||
"layout": { "kind": "premade", "id": "dunsany" }
|
||||
}
|
||||
```
|
||||
|
||||
- `rulesetIds`: optional array of preset rule IDs to activate for this game
|
||||
- `layout`: optional starting-layout selector. Discriminated union:
|
||||
- `{ "kind": "premade", "id": "dunsany" }` — look up by registered id
|
||||
(`classic`, `dunsany`, `monster`, `pawns-only`, `horde`,
|
||||
`knightmate`, `chess960`, `empty`).
|
||||
- `{ "kind": "fen", "fen": "rnbqkbnr/...", "name": "optional" }` — parse
|
||||
the piece-placement field of a FEN string. Extra FEN fields
|
||||
(side-to-move, castling, en-passant, clocks) are ignored.
|
||||
- `{ "kind": "custom", "pieces": [...], "name": "optional" }` — direct
|
||||
placements; each piece is `{ type, color, square, hasMoved? }`.
|
||||
`square` is 0..63. Capped at 128 pieces.
|
||||
- When `layout` is omitted, the server uses the Classic (FIDE) layout.
|
||||
|
||||
Response (Server → Client, type `room.created`):
|
||||
|
||||
|
|
@ -69,14 +81,26 @@ Response (Server → Client, type `room.created`):
|
|||
"payload": {
|
||||
"code": "ABC123",
|
||||
"token": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"color": "white"
|
||||
"color": "white",
|
||||
"layout": {
|
||||
"id": "dunsany",
|
||||
"name": "Dunsany's Chess",
|
||||
"pieces": [{ "type": "pawn", "color": "white", "square": 0 }, ...]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `layout`: resolved starting layout echoed back. Present on all
|
||||
`room.created` and `room.joined` responses from new servers; may be
|
||||
absent on legacy (pre-layouts) servers.
|
||||
|
||||
Error cases:
|
||||
|
||||
- Server at capacity (too many rooms): `error` code `SERVER_FULL`
|
||||
- Layout fails validation (bad king count, duplicate squares,
|
||||
unknown premade id, malformed FEN): `error` code `LAYOUT_INVALID`.
|
||||
Message field carries the human-readable reason.
|
||||
|
||||
### Message: room.join
|
||||
|
||||
|
|
@ -101,11 +125,19 @@ Response (Server → Client, type `room.joined`):
|
|||
"code": "ABC123",
|
||||
"token": "661f9500-f30c-52e5-b827-557766550111",
|
||||
"color": "black",
|
||||
"activeRules": ["pawns-move-backward"]
|
||||
"activeRules": ["pawns-move-backward"],
|
||||
"layout": {
|
||||
"id": "dunsany",
|
||||
"name": "Dunsany's Chess",
|
||||
"pieces": [{ "type": "pawn", "color": "white", "square": 0 }, ...]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `layout`: resolved layout used when the room was created. Late
|
||||
joiners render the board from this. Optional for backward compat.
|
||||
|
||||
When second player joins, server broadcasts `game.state` to BOTH players (initial board state).
|
||||
|
||||
Error cases:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue