Tic-tac-toe — React integration guide
Exact files to create, what to put in each one, and how they connect together.
1
Install the SDK
npm install @beta-gamer/react2
Create a session on your backend
⚠️ Never call the Beta Gamer API directly from your frontend. Your API key must stay server-side only. Use any backend below — a single serverless function is enough.
📄 server/routes/game.js
app.post('/api/game/start', async (req, res) => {
const { userId, userName, game, matchType } = req.body;
const resp = await fetch('https://api.beta-gamer.com/v1/sessions', {
method: 'POST',
headers: { 'Authorization': 'Bearer bg_live_xxxx', 'Content-Type': 'application/json' },
body: JSON.stringify({
game, matchType, players: [{ id: userId, displayName: userName }],
}),
});
const { sessionToken, sessionId } = await resp.json();
res.json({ sessionToken, sessionId });
});3
Create a lobby page
📄 src/pages/tictactoe/lobby.tsx
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function TictactoeLobby() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const startGame = async (matchType: 'matchmaking' | 'bot') => {
setLoading(true);
const res = await fetch('/api/game/tictactoe/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ matchType }),
});
const { sessionToken, sessionId } = await res.json();
router.push(`/tictactoe/game/${sessionId}?token=${sessionToken}`);
};
return (
<div>
<h1>Play Tic-tac-toe</h1>
<button onClick={() => startGame('matchmaking')} disabled={loading}>Find opponent</button>
<button onClick={() => startGame('bot')} disabled={loading}>vs Bot</button>
</div>
);
}4
Create the game page
📄 src/pages/tictactoe/game/[sessionId].tsx
import { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'next/navigation';
import { BetaGamerProvider } from '@beta-gamer/react';
import { TictactoeRoom } from '@/components/tictactoe/TictactoeRoom';
export default function TictactoeGamePage() {
const { sessionId } = useParams<{ sessionId: string }>();
const token = useSearchParams().get('token');
const [status, setStatus] = useState<'checking' | 'ok' | 'ended'>('checking');
useEffect(() => {
if (!token) { setStatus('ended'); return; }
fetch(`/api/v1/sessions/validate?token=${token}`)
.then(r => r.json())
.then(d => setStatus(d.status === 'ended' ? 'ended' : 'ok'))
.catch(() => setStatus('ok'));
}, [token]);
if (status === 'checking') return <p>Loading…</p>;
if (status === 'ended') return <div><p>Session ended.</p><a href="/tictactoe/lobby">Play again</a></div>;
return (
<BetaGamerProvider token={token!} serverUrl="https://api.beta-gamer.com">
<TictactoeRoom sessionId={sessionId} onLeave={() => window.location.href = '/tictactoe/lobby'} />
</BetaGamerProvider>
);
}5
Create the TictactoeRoom component
📄 src/components/tictactoe/TictactoeRoom.tsx
'use client';
import { useEffect, useRef, useState } from 'react';
import { useSocket, useSession } from '@beta-gamer/react';
interface Props { sessionId: string; onLeave: () => void; }
export function TictactoeRoom({ sessionId, onLeave }: Props) {
const socket = useSocket();
const session = useSession();
const myPlayerId = session.players[0]?.id ?? '';
const roomIdRef = useRef('');
const [board, setBoard] = useState<('X'|'O'|null)[]>(Array(9).fill(null));
const [myMark, setMyMark] = useState<'X'|'O'>('X');
const [currentTurn, setCurrentTurn] = useState(0);
const [players, setPlayers] = useState<any[]>([]);
const [winningLine, setWinningLine] = useState<number[]|null>(null);
const [gameResult, setGameResult] = useState<{ winner: string|null; reason: string }|null>(null);
useEffect(() => {
if (!socket) return;
const join = () => socket.emit('matchmaking:join', {
username: session.players[0]?.displayName, playerId: myPlayerId, sessionId,
});
if (socket.connected) join(); else socket.once('connect', join);
socket.on('game:started', (d: any) => {
roomIdRef.current = d.roomId;
setMyMark(d.mark); setBoard(d.board);
setPlayers(d.players); setCurrentTurn(d.currentTurn);
});
socket.on('game:move:made', (d: any) => {
setBoard(d.board); setCurrentTurn(d.currentTurn);
});
socket.on('game:over', (d: any) => {
if (d.winningLine) setWinningLine(d.winningLine);
setGameResult({ winner: d.winner ?? null, reason: d.reason });
});
return () => {
socket.off('connect', join);
['game:started','game:move:made','game:over'].forEach(e => socket.off(e));
};
}, [socket]);
const isMyTurn = players[currentTurn]?.id === myPlayerId;
// Player clicks a cell (position 0-8)
const placeMarк = (position: number) => {
if (!isMyTurn || board[position] || gameResult) return;
socket?.emit('game:move', { roomId: roomIdRef.current, playerId: myPlayerId, position });
};
return (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 80px)', gap: 8 }}>
{board.map((cell, i) => (
<button
key={i}
onClick={() => placeMarк(i)}
style={{
width: 80, height: 80, fontSize: 32,
background: winningLine?.includes(i) ? '#fef08a' : '#1e293b',
border: '2px solid #334155', borderRadius: 8, cursor: isMyTurn && !cell ? 'pointer' : 'default',
}}
>
{cell}
</button>
))}
<div style={{ gridColumn: '1/-1' }}>
<p>{isMyTurn ? `Your turn (${myMark})` : "Opponent's turn"}</p>
{gameResult && <p>Game over: {gameResult.reason}</p>}
<button onClick={() => socket?.emit('game:resign', { roomId: roomIdRef.current, playerId: myPlayerId })}>Resign</button>
<button onClick={onLeave}>Leave</button>
</div>
</div>
);
}Full event payload reference → Tic-tac-toe socket events
Beta Gamer GaaS API — questions? support@beta-gamer.com