🎮 Beta Gamer

Chess — AFK timeout

The AFK system prevents games from stalling when a player goes idle. Each player has 90 seconds to make a move. The timer resets on every move — it only counts idle time, not total game time (that's the 10-minute clock).

Timeline

AtEventPayloadWhat to do
0s(move made)AFK timer resets to 0.
60s idlechess:afk_warning{ playerId, secondsRemaining: 30 }Show a countdown banner. If playerId === your playerId, it's urgent — you'll forfeit in 30s.
any movechess:afk_warning_cleared{}Dismiss the banner immediately.
90s idlegame:over{ winner: opponentId, reason: "afk_timeout" }Idle player is forfeited. Opponent wins.

chess:afk_warning — field reference

FieldTypeDescription
playerIdstringThe player who is idle. Compare to your own playerId to know if it's you or the opponent.
secondsRemainingnumberSeconds until forfeit. Starts at 30 and counts down. The event fires once at 30s — you must count down locally.

Disabling AFK timeout

Disable at session creation (server-side) or at join time (client-side). Client-side takes precedence.

// Option A — server-side (session creation)
body: JSON.stringify({
  game: 'chess',
  afkTimeoutEnabled: false,
  // …
})

// Option B — client-side (matchmaking:join)
socket.emit('matchmaking:join', {
  username: 'Alex',
  afkTimeoutEnabled: false,
})

Banner implementation

The server fires chess:afk_warning once with secondsRemaining: 30. You must run a local countdown interval to update the UI each second.

import { useState, useEffect, useRef } from 'react';
import { useSocket } from '@beta-gamer/react';

function AfkBanner({ myPlayerId }: { myPlayerId: string }) {
  const socket = useSocket();
  const [warning, setWarning] = useState<{ isSelf: boolean; seconds: number } | null>(null);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  useEffect(() => {
    if (!socket) return;

    socket.on('chess:afk_warning', ({ playerId, secondsRemaining }) => {
      setWarning({ isSelf: playerId === myPlayerId, seconds: secondsRemaining });

      // Count down locally each second
      intervalRef.current = setInterval(() => {
        setWarning(prev => {
          if (!prev || prev.seconds <= 1) {
            clearInterval(intervalRef.current!);
            return null;
          }
          return { ...prev, seconds: prev.seconds - 1 };
        });
      }, 1000);
    });

    // Clear banner when a move is made
    socket.on('chess:afk_warning_cleared', () => {
      clearInterval(intervalRef.current!);
      setWarning(null);
    });

    return () => {
      socket.off('chess:afk_warning');
      socket.off('chess:afk_warning_cleared');
      clearInterval(intervalRef.current!);
    };
  }, [socket, myPlayerId]);

  if (!warning) return null;

  return (
    <div
      role="alert"
      className={`fixed top-4 left-1/2 -translate-x-1/2 z-50 px-6 py-3 rounded-xl font-semibold text-white shadow-lg ${
        warning.isSelf ? 'bg-red-600' : 'bg-yellow-600'
      }`}
    >
      {warning.isSelf
        ? `⚠️ Make a move in ${warning.seconds}s or you'll forfeit!`
        : `⏳ Opponent has ${warning.seconds}s to move…`}
    </div>
  );
}
Using the SDK? The ChessBoard component from @beta-gamer/react, @beta-gamer/react-native, and @beta-gamer/angular includes a built-in AFK banner by default. Pass showAfkWarning={false} to disable it and use your own implementation below.
⚠️ Always show the AFK banner. Players should never be forfeited without a visible countdown — it's a poor experience and will generate support requests.
Beta Gamer GaaS API — questions? support@beta-gamer.com