import { useAgent } from "agents/react"; import { ArrowsClockwiseIcon, ChartBarIcon, CircleIcon, GameControllerIcon, HandshakeIcon, XIcon } from "@phosphor-icons/react"; import { Button, Surface, Switch, Text } from "@cloudflare/kumo"; import { useCallback, useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import type { TicTacToeState } from "./server"; import "./styles.css"; function App() { const [state, setState] = useState({ board: [ [null, null, null], [null, null, null], [null, null, null] ], currentPlayer: "X", winner: null }); const [gamesPlayed, setGamesPlayed] = useState(0); const [autoPlayEnabled, setAutoPlayEnabled] = useState(true); const [stats, setStats] = useState({ draws: 0, oWins: 0, xWins: 0 }); const agent = useAgent({ agent: "tic-tac-toe", onStateUpdate: (newState) => { setState(newState); }, prefix: "some/prefix" }); const handleCellClick = useCallback( async (row: number, col: number) => { if (state.board[row][col] !== null || state.winner) return; try { await agent.call("makeMove", [[row, col], state.currentPlayer]); } catch (error) { console.error("Error making move:", error); } }, [agent, state.board, state.winner, state.currentPlayer] ); const handleNewGame = useCallback(async () => { try { await agent.call("clearBoard"); setGamesPlayed((prev) => prev + 1); } catch (error) { console.error("Error clearing board:", error); } }, [agent]); // Make random move when new game starts useEffect(() => { const isBoardEmpty = state.board.every((row) => row.every((cell) => cell === null) ); if (isBoardEmpty && gamesPlayed > 0 && autoPlayEnabled) { const timer = setTimeout(() => { const row = Math.floor(Math.random() * 3); const col = Math.floor(Math.random() * 3); handleCellClick(row, col); }, 1000); return () => clearTimeout(timer); } }, [state.board, gamesPlayed, autoPlayEnabled, handleCellClick]); // Check for game over and start new game after delay useEffect(() => { const isGameOver = state.winner || state.board.every((row) => row.every((cell) => cell !== null)); if (isGameOver) { if (state.winner === "X") { setStats((prev) => ({ ...prev, xWins: prev.xWins + 1 })); } else if (state.winner === "O") { setStats((prev) => ({ ...prev, oWins: prev.oWins + 1 })); } else { setStats((prev) => ({ ...prev, draws: prev.draws + 1 })); } const timer = setTimeout(() => { handleNewGame(); }, 3000); return () => clearTimeout(timer); } }, [state.winner, state.board, handleNewGame]); const renderCell = (row: number, col: number) => { const value = state.board[row][col]; return ( // oxlint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- game board cell
handleCellClick(row, col)} key={`${row}-${col}`} > {value === "X" && ( )} {value === "O" && ( )}
); }; const playerSymbol = (player: "X" | "O") => player === "X" ? "\u2A09" : "\u25EF"; const getGameStatus = () => { if (state.winner) { const isX = state.winner === "X"; return ( Winner:{" "} {playerSymbol(state.winner)} ! ); } if (state.board.every((row) => row.every((cell) => cell !== null))) { return ( Game Draw! ); } return ( Current Player:{" "} {playerSymbol(state.currentPlayer)} ); }; return (
{/* Title */}
Tic Tac Toe
{/* Game status */} {getGameStatus()} {/* Board */}
{state.board.map((row, rowIndex) => row.map((_cell, colIndex) => renderCell(rowIndex, colIndex)) )}
{/* Stats */}
{stats.xWins}
Wins
{stats.oWins}
Wins
{stats.draws}
Draws
{/* Controls */}
setAutoPlayEnabled(checked)} label="Random First Move" controlFirst={false} />
{/* Games counter */}
Games played: {gamesPlayed}
); } const root = createRoot(document.getElementById("root")!); root.render();