Connect 4 — AFK timeout
Each player has a configurable idle time limit before they forfeit. Defaults: 90 seconds to drop a piece, warning fires 30 seconds before forfeit. Both values are set at join time and can be customised per session.
Timeline (default settings)
| At | Event | Payload | What to do |
|---|---|---|---|
| 0s | (move made / game started) | — | AFK timer resets. |
| 60s idle | connect4:afk_warning | { playerId, secondsRemaining: 30 } | Show countdown banner. Count down locally each second. |
| any move | connect4:afk_warning_cleared | {} | Dismiss the banner immediately. |
| 90s idle | game:over | { winner: opponentId, reason: "afk_timeout" } | Idle player forfeited. No winningCells in payload. |
The warning fires at afkTimeoutSeconds - afkWarnSeconds ms. With custom values of e.g. afkTimeoutSeconds: 60, afkWarnSeconds: 15, the warning fires at 45s and forfeit at 60s.
Configuring the timeout
// Pass in matchmaking:join — applies to this game only
socket.emit('matchmaking:join', {
username: 'Alex',
playerId: session.players[0].id,
afkTimeoutEnabled: true,
afkTimeoutSeconds: 60, // forfeit after 60s idle
afkWarnSeconds: 15, // warn 15s before forfeit (fires at 45s)
});
// Disable AFK entirely
socket.emit('matchmaking:join', {
username: 'Alex',
afkTimeoutEnabled: false,
});Banner implementation
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('connect4:afk_warning', ({ playerId, secondsRemaining }) => {
setWarning({ isSelf: playerId === myPlayerId, seconds: secondsRemaining });
intervalRef.current = setInterval(() => {
setWarning(prev => {
if (!prev || prev.seconds <= 1) { clearInterval(intervalRef.current!); return null; }
return { ...prev, seconds: prev.seconds - 1 };
});
}, 1000);
});
// Clear on any move
socket.on('connect4:afk_warning_cleared', () => {
clearInterval(intervalRef.current!);
setWarning(null);
});
return () => {
socket.off('connect4:afk_warning');
socket.off('connect4:afk_warning_cleared');
clearInterval(intervalRef.current!);
};
}, [socket, myPlayerId]);
if (!warning) return null;
return (
<div className={`px-4 py-2 rounded font-semibold flex justify-between ${
warning.isSelf ? 'bg-red-600 text-white' : 'bg-yellow-600 text-white'
}`}>
<span>
{warning.isSelf ? "⚠️ Drop a piece or you'll forfeit!" : '⏳ Opponent is AFK'}
</span>
<span className="font-mono">{warning.seconds}s</span>
</div>
);
}Using the SDK? The
Connect4Board 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.
Beta Gamer GaaS API — questions? support@beta-gamer.com