Connect 4 — Socket events
Connect 4 uses the /connect4 Socket.IO namespace. Use useSocket() to access the raw socket for custom listeners.
Emit (client → server)
| Event | Payload | Description |
|---|---|---|
matchmaking:join | { username, playerId?, sessionId?, wantsBot?, botDifficulty?, afkTimeoutEnabled?, afkTimeoutSeconds?, afkWarnSeconds? } | Enter matchmaking or start a bot game. |
matchmaking:leave | { playerId } | Cancel matchmaking before a match is found. |
game:move | { roomId, playerId, column } | Drop a piece in the given column (0–6, left to right). |
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 within the 60-second window. |
game:spectate | { roomId, username } | Join a room as a read-only spectator. |
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 | — | The sessionId from your session token. |
wantsBot | boolean | No | false | 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 | Whether the AFK forfeit rule is active. |
afkTimeoutSeconds | number | No | 90 | Seconds of idle time before forfeit. |
afkWarnSeconds | number | No | 30 | How many seconds before forfeit the warning fires. |
Receive (server → client)
| Event | Payload | Description |
|---|---|---|
matchmaking:waiting | { playerId, position } | In queue. position is your place in line (1 = next to be matched). |
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, rows, cols, afkTimeoutEnabled, afkTimeoutSeconds, afkWarnSeconds } | Game is ready. |
game:move:made | { column, position, board, currentTurn, moveHistory, lastMove } | A piece was dropped by either player. |
connect4:afk_warning | { playerId, secondsRemaining } | Active player has been idle. Count down locally from secondsRemaining. |
connect4: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, winningCells?: number[] } | Game ended. |
player:disconnected | { socketId } | Opponent lost connection. 60-second reconnect window starts. |
player:reconnected | { playerId } | Opponent reconnected. |
game:reconnected | { roomId, playerId, players, color, board, currentTurn, moveHistory, rows, cols, 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 | Include in all subsequent emits. |
playerId | string | Your player ID for this session. |
players | Player[] | [{ id, username }] — both players. Index 0 is red, index 1 is yellow. |
color | "red" | "yellow" | Your assigned color. Red always moves first. |
board | (string|null)[] | Flat array, length rows×cols. null = empty, "red"/"yellow" = piece. Row 0 is the top row. |
currentTurn | number | Index into players[] — whose turn it is (0 or 1). |
rows | number | 6 |
cols | number | 7 |
afkTimeoutEnabled | boolean | Whether AFK forfeit is active for this game. |
afkTimeoutSeconds | number | Total idle seconds before forfeit. |
afkWarnSeconds | number | Seconds before forfeit that the warning fires. |
game:move:made — field reference
| Field | Type | Description |
|---|---|---|
column | number | Column the piece was dropped into (0–6). |
position | number | Board index where the piece landed (row * cols + col). Use this to animate the drop. |
board | (string|null)[] | Full updated board state. |
currentTurn | number | Index into players[] for whoever moves next. |
moveHistory | MoveEntry[] | [{ column, position, playerId }] — full move history. |
lastMove | number | Same as position — the board index of the piece just placed. |
Usage — with Connect4Board component
Use Connect4Board when you want the board rendered for you. You only need useSocket() for side events — AFK warnings, disconnect banners, game over.
import { useEffect, useRef } from 'react';
import { BetaGamerProvider, Connect4Board, useSocket } from '@beta-gamer/react';
function Connect4SideEvents({ 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('connect4:afk_warning', ({ playerId, secondsRemaining }) => {
// show countdown banner
});
socket.on('player:disconnected', () => {
// show "opponent disconnected" banner
});
socket.on('game:over', ({ winner, reason, winningCells }) => {
// show result screen — winningCells highlights the four in a row
});
return () => {
socket.off('game:started');
socket.off('connect4:afk_warning');
socket.off('player:disconnected');
socket.off('game:over');
};
}, [socket]);
const resign = () => socket?.emit('game:resign', {
roomId: roomIdRef.current, playerId: myPlayerId,
});
return (
<div>
<Connect4Board />
<button onClick={resign}>Resign</button>
</div>
);
}
export default function Connect4Page({ sessionToken }: { sessionToken: string }) {
return (
<BetaGamerProvider token={sessionToken}>
<Connect4SideEvents myPlayerId="user_123" />
</BetaGamerProvider>
);
}Usage — manual (no Connect4Board component)
Skip Connect4Board entirely if you want to render your own board. You emit game:move with a column number — the server handles gravity and returns the updated board.
import { useEffect, useRef, useState } from 'react';
import { useSocket } from '@beta-gamer/react';
function Connect4GameManual({ myPlayerId }: { myPlayerId: string }) {
const socket = useSocket();
const roomIdRef = useRef('');
const playerIdRef = useRef('');
const [board, setBoard] = useState<(string | null)[]>(Array(42).fill(null));
const [currentTurn, setCurrentTurn] = useState(0);
const [players, setPlayers] = useState<any[]>([]);
const [winningCells, setWinningCells] = useState<number[] | null>(null);
useEffect(() => {
if (!socket) return;
socket.emit('matchmaking:join', { username: 'Alex', playerId: myPlayerId });
socket.on('game:started', ({ roomId, playerId, board: b, players: p, currentTurn: t }) => {
roomIdRef.current = roomId;
playerIdRef.current = playerId;
setBoard(b);
setPlayers(p);
setCurrentTurn(t);
});
socket.on('game:move:made', ({ board: b, currentTurn: t, position }) => {
setBoard(b);
setCurrentTurn(t);
// position = board index where piece landed — use for drop animation
});
socket.on('game:over', ({ winner, reason, winningCells: wc }) => {
setWinningCells(wc ?? null); // highlight the four winning cells
console.log('Game over:', reason, winner ?? 'draw');
});
return () => {
socket.off('game:started');
socket.off('game:move:made');
socket.off('game:over');
};
}, [socket]);
const isMyTurn = players[currentTurn]?.id === playerIdRef.current;
// Player clicks a column header to drop a piece
const dropPiece = (column: number) => {
if (!isMyTurn) return;
socket?.emit('game:move', {
roomId: roomIdRef.current, playerId: playerIdRef.current, column,
});
};
// Render your own 7×6 grid using board[], winningCells
return null;
}💡 No chess-style clock in Connect 4 — only AFK timeout. No draw offers — draw only happens when the board fills with no winner. Both approaches use the same events.
Beta Gamer GaaS API — questions? support@beta-gamer.com