Files
tic-tac-toe-ui/src/tictactoe/TicTacToe.tsx
Vishesh 'ironeagle' Bangotra 94bdec8cb4 - Added username persistence via localStorage with read-only input behavior.
- Added automatic connect() invocation on page load.
- Implemented robust login flow: register or auto-login based on local flags.
- Added logout support with clean WebSocket disconnect + state reset.
- Updated NakamaProvider with getSession(), autoLogin(), registerWithUsername().
- Connected logout button and integrated updated login behavior into UI.
2025-11-29 02:09:51 +05:30

158 lines
4.0 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(localStorage.getItem("username") ?? "");
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[]>([]);
function onMatchDataCallback(msg: { opCode: number; data: any }) {
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 || []);
}
}
const {
loginOrRegister,
logout,
joinMatchmaker,
onMatchData,
sendMatchData,
listOpenMatches,
matchId,
session,
} = useNakama();
useEffect(() => {
onMatchData(onMatchDataCallback);
}, [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(onMatchDataCallback);
}
useEffect(() => {
connect();
}, []);
// ------------------------------------------
// 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}
disabled={username.length > 0}
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>
<button onClick={() => logout()}>Logout</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>
);
}