🎮 Beta Gamer

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)

EventPayloadDescription
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

FieldTypeRequiredDefaultDescription
usernamestringYesDisplay name shown in the game UI. Not used for identification — pass playerId for that.
playerIdstringNouuidYour tenant userId from the session token. Pass session.players[0].id. Returned in game:over.winner for result tracking.
sessionIdstringNoPass the sessionId from your session token to resume a specific session.
wantsBotbooleanNofalseSet true to skip matchmaking and play against the AI immediately.
botDifficulty"easy" | "medium" | "hard"No"medium"Bot strength. Only used when wantsBot is true.
afkTimeoutEnabledbooleanNotrueOverride the session-level AFK setting for this connection.

Receive (server → client)

EventPayloadDescription
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 } | nullResponse 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

FieldTypeDescription
roomIdstringUnique room identifier. Include in all subsequent emits.
playerIdstringYour player ID for this session.
playersPlayer[][{ id, username, color }] — both players.
color"red" | "black"Your assigned color. Red always moves first.
boardPiece[]64-element flat array representing the starting board. See Board layout in Rules & board.
currentTurnstringplayerId of whoever moves first (always red).
afkTimeoutEnabledbooleanWhether AFK forfeit is active for this game.
afkTimeoutSecondsnumberTotal idle seconds before forfeit (90).

game:valid_moves — field reference

FieldTypeDescription
positionnumberThe board index you requested moves for.
movesMove[]Array of valid moves for this piece.
moves[].fromnumberSource board index (same as position).
moves[].tonumberDestination board index.
moves[].capturesnumber[]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

FieldTypeDescription
move.fromnumberSource board index.
move.tonumberDestination board index.
move.capturesnumber[]Indices of captured pieces (empty for non-capture moves).
move.becameKingbooleanTrue if the piece was promoted to king on this move.
boardPiece[]Full updated 64-element board array after the move.
currentTurnstringplayerId of whoever moves next.
moveHistoryMoveEntry[]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