Files
tic-tac-toe-ui/src/tictactoe/TicTacToe.tsx
Vishesh 'ironeagle' Bangotra 5fb3ad4205 feat(tictactoe): integrate leaderboard UI, provider API hook, and new styles
- Add *_BKP* ignore rule to .gitignore
- Insert Leaderboard component into TicTacToe screen
- Add getLeaderboardTop() API method to NakamaProvider and expose via context
- Add full leaderboard polling logic (interval-based) in new Leaderboard.tsx
- Style leaderboard in styles.css (rows, rank, name, score, empty state, card UI)
- Modernize TicTacToe UI styles (board, squares, buttons, layout)
- Replace matchmaking view with leaderboard integration
- Import ApiLeaderboardRecordList and ApiMatch types cleanly
2025-11-28 19:43:59 +05:30

164 lines
4.1 KiB
TypeScript

import { useState, useEffect } from "react";
import { useNakama } from "./providers/NakamaProvider";
import Board from "./Board";
import Leaderboard from "./Leaderboard";
// import { Match } from "@heroiclabs/nakama-js";
// import MatchList from "./MatchList";
export default function TicTacToe() {
const [username, setUsername] = useState("");
const [selectedMode, setSelectedMode] = useState("classic");
const [board, setBoard] = useState<string[][]>([
["", "", ""],
["", "", ""],
["", "", ""]
]);
const [turn, setTurn] = useState<number>(0);
const [winner, setWinner] = useState<string | null>(null);
// const [openMatches, setOpenMatches] = useState<Match[]>([]);
const [players, setPlayers] = useState<string[]>([]);
const {
loginOrRegister,
joinMatchmaker,
onMatchData,
sendMatchData,
listOpenMatches,
matchId,
session,
} = useNakama();
useEffect(() => {
onMatchData((msg) => {
console.log("[Match Data]", msg);
if (msg.opCode === 2) {
const state = msg.data;
console.log("Match state:", state);
setBoard(state.board);
setTurn(state.turn);
setWinner(state.winner || null);
// new:
setPlayers(state.players || []);
}
});
}, [onMatchData]);
// useEffect(() => {
// let active = true;
//
// async function refreshLoop() {
// while (active) {
// const matches = await listOpenMatches();
// setOpenMatches(matches);
//
// await new Promise(res => setTimeout(res, 500)); // 0.5s refresh
// }
// }
//
// refreshLoop();
//
// return () => {
// active = false;
// };
// }, [listOpenMatches]);
// ------------------------------------------
// CONNECT
// ------------------------------------------
async function connect() {
await loginOrRegister(username);
// Match data listener
onMatchData((msg) => {
console.log("[Match Data]", msg);
if (msg.opCode === 2) {
const state = msg.data;
console.log("Match state:", state);
setBoard(state.board);
setTurn(state.turn);
setWinner(state.winner || null);
// new:
setPlayers(state.players || []);
}
});
}
// ------------------------------------------
// SEND A MOVE
// ------------------------------------------
function handleCellClick(row: number, col: number) {
if (!matchId) return;
sendMatchData(matchId, 1, { row, col }); // OpMove=1
}
// ------------------------------------------
// MATCHMAKING
// ------------------------------------------
async function startQueue(selectedMode: string) {
const ticket = await joinMatchmaker(selectedMode);
console.log("Queued:", ticket);
}
return (
<div>
<h1>Tic Tac Toe Multiplayer</h1>
{!session && (
<>
<input
placeholder="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button onClick={connect}>Connect</button>
</>
)}
{session && !matchId && (
<>
<h2>Hello, {session.username}</h2>
{/* Game mode selection */}
<label style={{ display: "block", marginTop: "10px" }}>
Select Game Mode:
</label>
<select
value={selectedMode}
onChange={(e) => setSelectedMode(e.target.value)}
style={{ padding: "6px", marginBottom: "10px" }}
>
<option value="classic">Classic</option>
<option value="blitz">Blitz</option>
</select>
{/* Join matchmaking */}
<button onClick={() => startQueue(selectedMode)}>Join Matchmaking</button>
{/*/!* List open matches *!/*/}
{/*<MatchList matches={openMatches} />*/}
<Leaderboard/>
</>
)}
{matchId && (
<Board
board={board}
turn={turn}
winner={winner}
players={players}
myUserId={session?.user_id ?? null}
onCellClick={handleCellClick}
/>
)}
</div>
);
}