Checkers — Socket events
Checkers uses the /checkers Socket.IO namespace. The BetaGamerProvider connects automatically — use useSocket() to access the raw socket. Emit matchmaking:join once the socket is ready.
Emit (client → server)
| Event | Payload | Description |
|---|---|---|
matchmaking:join | { username, sessionId?, wantsBot?, botDifficulty?, afkTimeoutEnabled? } | Enter matchmaking or start a bot game. |
matchmaking:leave | { playerId } | Cancel matchmaking before a match is found. |
game:get_moves | { roomId, position, playerId } | Request valid moves for the piece at board index position (0–63). Server responds with game:valid_moves. |
game:move | { roomId, playerId, move: { from, to, captures? } } | Submit a move. from/to are board indices (0–63). captures is an array of jumped board indices. |
game:resign | { roomId, playerId } | Forfeit the game immediately. |
afk:check | { roomId } | Ask the server if an AFK warning is still active. Useful when local countdown hits 0 with no response. |
game:reconnect | { playerId } | Rejoin after a disconnect. Must be within the 60-second window. |
game:spectate | { roomId, username } | Join a room as a read-only spectator. No session token required. |
matchmaking:join — field reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
username | string | Yes | — | Display name shown in the game UI. Not used for identification — pass playerId for that. |
playerId | string | No | uuid | Your tenant userId from the session token. Pass session.players[0].id. Returned in game:over.winner for result tracking. |
sessionId | string | No | — | Pass the sessionId from your session token to resume a specific session. |
wantsBot | boolean | No | false | Set true to skip matchmaking and play against the AI immediately. |
botDifficulty | "easy" | "medium" | "hard" | No | "medium" | Bot strength. Only used when wantsBot is true. |
afkTimeoutEnabled | boolean | No | true | Override the session-level AFK setting for this connection. |
Receive (server → client)
| Event | Payload | Description |
|---|---|---|
matchmaking:waiting | { playerId, position } | In queue. position is your place in line. |
matchmaking:left | {} | Cancel confirmed — you have been removed from the queue. |
matchmaking:already_started | { roomId } | Cancel arrived too late — a match was already made. Proceed to the game. |
game:started | { roomId, playerId, players, color, board, currentTurn, afkTimeoutEnabled, afkTimeoutSeconds } | Game is ready. Render the board from the board array. |
game:valid_moves | { position, moves: Move[] } | Valid moves for the selected piece. Each Move has from, to, and optionally captures. |
game:move:made | { move: { from, to, captures?, becameKing? }, board, currentTurn, moveHistory } | A move was played. board is the full updated 64-element array. |
checkers:afk_warning | { playerId, secondsRemaining } | Active player has been idle. secondsRemaining counts down from 30. |
checkers:afk_warning_cleared | {} | A move was made — dismiss the AFK warning banner. |
afk:status | { playerId, expiresAt } | null | Response to afk:check. null means warning was cleared; otherwise restart countdown from expiresAt - Date.now(). |
game:over | { winner: string | null, reason } | Game ended. |
player:disconnected | { socketId } | Opponent lost connection. 60-second reconnect window starts. |
player:reconnected | { playerId } | Opponent reconnected within the window. |
game:reconnected | { roomId, playerId, players, color, board, currentTurn, moveHistory, afkWarning } | You successfully rejoined. afkWarning is { playerId, expiresAt } if a warning is active, null otherwise. |
game:spectate:joined | { roomId, players, board, currentTurn, moveHistory } | Spectator joined — current board state. |
game:started — field reference
| Field | Type | Description |
|---|---|---|
roomId | string | Unique room identifier. Include in all subsequent emits. |
playerId | string | Your player ID for this session. |
players | Player[] | [{ id, username, color }] — both players. |
color | "red" | "black" | Your assigned color. Red always moves first. |
board | Piece[] | 64-element flat array representing the starting board. See Board layout in Rules & board. |
currentTurn | string | playerId of whoever moves first (always red). |
afkTimeoutEnabled | boolean | Whether AFK forfeit is active for this game. |
afkTimeoutSeconds | number | Total idle seconds before forfeit (90). |
game:valid_moves — field reference
| Field | Type | Description |
|---|---|---|
position | number | The board index you requested moves for. |
moves | Move[] | Array of valid moves for this piece. |
moves[].from | number | Source board index (same as position). |
moves[].to | number | Destination board index. |
moves[].captures | number[] | Board indices of pieces that will be captured (jumped). Empty array for non-capture moves. If any capture moves exist, only capture moves are returned — captures are mandatory. |
game:move:made — field reference
| Field | Type | Description |
|---|---|---|
move.from | number | Source board index. |
move.to | number | Destination board index. |
move.captures | number[] | Indices of captured pieces (empty for non-capture moves). |
move.becameKing | boolean | True if the piece was promoted to king on this move. |
board | Piece[] | Full updated 64-element board array after the move. |
currentTurn | string | playerId of whoever moves next. |
moveHistory | MoveEntry[] | Full move history: [{ move: { from, to, captures? }, playerId, timestamp }]. |
Usage — with CheckersBoard component
Use CheckersBoard when you want the board rendered for you. You only need useSocket() for side events the component doesn't cover — AFK warnings, disconnect banners.
import { useEffect, useRef } from 'react';
import { BetaGamerProvider, CheckersBoard, useSocket } from '@beta-gamer/react';
function CheckersSideEvents({ myPlayerId }: { myPlayerId: string }) {
const socket = useSocket();
const roomIdRef = useRef('');
useEffect(() => {
if (!socket) return;
socket.emit('matchmaking:join', { username: 'Alex', playerId: myPlayerId });
socket.on('game:started', ({ roomId }) => { roomIdRef.current = roomId; });
socket.on('checkers:afk_warning', ({ playerId, secondsRemaining }) => {
// show countdown banner
});
socket.on('player:disconnected', () => {
// show "opponent disconnected" banner
});
socket.on('game:over', ({ winner, reason }) => {
// show result screen
});
return () => {
socket.off('game:started');
socket.off('checkers:afk_warning');
socket.off('player:disconnected');
socket.off('game:over');
};
}, [socket]);
const resign = () => socket?.emit('game:resign', {
roomId: roomIdRef.current, playerId: myPlayerId,
});
return (
<div>
<CheckersBoard />
<button onClick={resign}>Resign</button>
</div>
);
}
export default function CheckersPage({ sessionToken }: { sessionToken: string }) {
return (
<BetaGamerProvider token={sessionToken}>
<CheckersSideEvents myPlayerId="user_123" />
</BetaGamerProvider>
);
}Usage — manual (no CheckersBoard component)
Skip CheckersBoard entirely if you want to render your own board. You handle piece selection, move requests, and move submission yourself.
import { useEffect, useRef, useState } from 'react';
import { useSocket } from '@beta-gamer/react';
type Piece = { player: 'red' | 'black'; type: 'man' | 'king' } | null;
function CheckersGameManual({ myPlayerId }: { myPlayerId: string }) {
const socket = useSocket();
const roomIdRef = useRef('');
const playerIdRef = useRef('');
const [board, setBoard] = useState<Piece[]>(Array(64).fill(null));
const [validMoves, setValidMoves] = useState<{ from: number; to: number; captures: number[] }[]>([]);
useEffect(() => {
if (!socket) return;
socket.emit('matchmaking:join', { username: 'Alex', playerId: myPlayerId });
socket.on('game:started', ({ roomId, playerId, board: b }) => {
roomIdRef.current = roomId;
playerIdRef.current = playerId;
setBoard(b);
});
// After emitting game:get_moves, server responds with valid moves for that piece
socket.on('game:valid_moves', ({ moves }) => setValidMoves(moves));
socket.on('game:move:made', ({ board: b, move }) => {
setBoard(b);
setValidMoves([]);
if (move.becameKing) console.log('Promoted to king!');
});
socket.on('game:over', ({ winner, reason }) => {
console.log('Game over:', reason, winner ?? 'draw');
});
return () => {
socket.off('game:started');
socket.off('game:valid_moves');
socket.off('game:move:made');
socket.off('game:over');
};
}, [socket]);
// Step 1: player clicks a piece — request its valid moves
const selectPiece = (position: number) => {
socket?.emit('game:get_moves', {
roomId: roomIdRef.current, position, playerId: playerIdRef.current,
});
};
// Step 2: player clicks a highlighted destination — submit the move
const makeMove = (from: number, to: number, captures: number[]) => {
socket?.emit('game:move', {
roomId: roomIdRef.current, playerId: playerIdRef.current,
move: { from, to, captures },
});
};
// Render your own board using board[], validMoves
return null;
}💡 Both approaches use the same socket events. Mix and match freely — use
CheckersBoard for the board but handle game:over yourself, or go fully manual. It's your layout.Beta Gamer GaaS API — questions? support@beta-gamer.com