Ludo
Turn-based, 2–4 players, classic rules. The one game with a genuine two-step turn — roll, then move a specific token — and a server-choreographed sequence of phase events between them, built for animating a real board rather than just delivering a result.
Session creation
{
"game": "ludo",
"matchType": "matchmaking",
"players": [{ "id": "user_1" }],
"afkConfig": { "timeoutMs": 15000, "warnMs": 11250 }
}Ludo is the only game of the six with a registered afkConfig default (15000ms timeout, 11250ms warn) — the field above overrides it if you send it, but works out of the box either way.
Board position numbering
Useful if you're rendering your own board rather than relying on a component:
| Range | Meaning |
|---|---|
| 0 – 51 | The shared outer ring — one continuous loop all colors travel. |
| 52 – 56 | A color's own 5-cell home stretch, entered after completing the ring. |
| 57 | Finished — the token has reached the center. |
Each color's ring entry point differs; a token's home-stretch cells are only ever visible to tokens of that color. Safe squares exist at fixed ring positions — landing there protects a token from capture.
The roll → move sequence
A turn is choreographed as a sequence of ludo:phase events between the roll and the move, not a single atomic step — built so a client can animate dice-roll, number-reveal, and token-movement as distinct beats rather than snapping straight to a new board state.
client → ludo:roll { roomId, playerId }
server → ludo:phase { name: "roll_animation" }
server → ludo:rolled { playerId, dice, state }
server → ludo:phase { name: "show_number" }
server → ludo:phase { name: "roll_prompt" | "choose_pawn" }
client → ludo:move { roomId, playerId, tokenId }
server → ludo:phase { name: "pawn_animate" }
server → ludo:moved { playerId, tokenId, state, hasCapture, isExitBase }
server → ludo:phase { name: "roll_prompt" } // next turn, or same player again on a 6roll_animation, show_number, roll_prompt, and choose_pawn are the ones confirmed in the current source, but treat this as the shape to expect rather than an exhaustive list carved in stone.Socket events
| Event | Direction | Payload |
|---|---|---|
ludo:roll | client → server | { roomId, playerId } |
ludo:rolled | server → client | { playerId, dice, state } |
ludo:move | client → server | { roomId, playerId, tokenId } |
ludo:moved | server → client | { playerId, tokenId, state, hasCapture, isExitBase } |
ludo:phase | server → client | { name: string } |
ludo:player_forfeited | server → client | Sent when a player is AFK-forfeited |
game:over | server → client | Result object, same shape as the game.ended webhook |
Private matches
Ludo supports private rooms directly at the socket layer, distinct from the other games' pattern:
socket.emit('private:create', { playerId, username: 'Alex' });
socket.on('private:created', ({ roomCode }) => { /* share this code */ });
// second player
socket.emit('private:join', { roomCode, playerId, username: 'Jordan' });Hosted matchmaking
Supports hosted matchmaking via both direct socket events and the REST endpoints, plus the same pool-rebalance event Fight Arena exposes, for the same reason — variable player count (2–4) rather than a fixed lobby size.
socket.emit('hosted:rebalance_pool');
socket.on('hosted:pool_rebalanced', ({ groups }) => { /* ... */ });