diff --git a/README.md b/README.md index c5ec11a..4b2a39f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ a web app with these technologies. Start the server: - cd server npm start Start the client: diff --git a/client/src/App.css b/client/src/App.css index a1c1829..7e773ec 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -85,7 +85,8 @@ .new-game-button, .suggest-button, -.go-back-button { +.go-back-button, +.resign-button { padding: 10px 15px; font-size: 16px; cursor: pointer; diff --git a/client/src/App.tsx b/client/src/App.tsx index 3d9aa55..90bd7bf 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -22,7 +22,11 @@ const App: React.FC = () => { suggestedMove, startNewGame, undoLastMove, - setSuggestedMove + setSuggestedMove, + onPieceDrop, + onSquareClick, + gameStatus, + resign } = useChessGame(); const { opponent, setOpponent, searchDepth, setSearchDepth, setStockfishDepth } = useOpponent(); const { ws } = useWebSocket(user); @@ -140,17 +144,10 @@ const App: React.FC = () => { } }; - const onPieceDrop = (sourceSquare: string, targetSquare: string) => { - // Implement move logic here - // This function should interact with your useChessGame hook to make a move - // For example: - // makeAMove(sourceSquare, targetSquare); - // Ensure that your hooks provide the necessary functions - return true; - }; - - const onSquareClick = (square: string) => { - // Implement any additional logic for square clicks here + const handleResign = () => { + if (gameStatus === 'active' && game.turn() === 'w') { + resign(); + } }; return ( @@ -192,6 +189,7 @@ const App: React.FC = () => { }}>New Game +
@@ -216,24 +214,32 @@ const App: React.FC = () => { />
-
-

Online Players

- {onlineUsers.length > 0 ? ( - - ) : ( -

No other players online

- )} +
+ {gameStatus === 'active' &&

Game in progress

} + {gameStatus === 'resigned' &&

White resigned. Black wins!

} + {gameStatus === 'checkmate' &&

Checkmate! {game.turn() === 'w' ? 'Black' : 'White'} wins!

} + {gameStatus === 'draw' &&

Game ended in a draw

}
- {incomingChallenges.length > 0 && ( + {opponent !== 'stockfish' && ( +
+

Online Players

+ {onlineUsers.length > 0 ? ( + + ) : ( +

No other players online

+ )} +
+ )} + {incomingChallenges.length > 0 && opponent !== 'stockfish' && (

Incoming Challenges:

{incomingChallenges.map((challenger, index) => ( diff --git a/client/src/hooks/useChessGame.ts b/client/src/hooks/useChessGame.ts index e83c45a..c43ba41 100644 --- a/client/src/hooks/useChessGame.ts +++ b/client/src/hooks/useChessGame.ts @@ -2,50 +2,17 @@ import { useState, useEffect } from 'react'; import { Chess, Square } from 'chess.js'; import { GetMoveRequest, GetMoveResponse, MoveRequest, MoveResponse } from '../../../shared/types'; -/** - * Custom React hook for managing a chess game. - * - * This hook encapsulates the logic for: - * - Managing the game state - * - Handling user moves - * - Interacting with an AI opponent - * - Updating the game history - * - Managing the game's evaluation - * - * @returns An object containing the game state and functions to interact with the game. - */ export function useChessGame() { - // The current state of the chess game const [game, setGame] = useState(new Chess()); - - // The current board position in Forsyth-Edwards Notation (FEN) const [fen, setFen] = useState(game.fen()); - - // The currently selected piece on the board (if any) const [selectedPiece, setSelectedPiece] = useState(null); - - // A formatted string representation of the move history const [moveHistory, setMoveHistory] = useState(''); - - // An array of all moves made in the game in Standard Algebraic Notation (SAN) const [fullHistory, setFullHistory] = useState([]); - - // The current evaluation of the board position (positive favors white, negative favors black) const [evaluation, setEvaluation] = useState(0); - - // A suggested move for the current player (if any) const [suggestedMove, setSuggestedMove] = useState(null); - - // The type of opponent (e.g., 'stockfish' for AI, 'human' for human player) const [opponent, setOpponent] = useState('stockfish'); + const [gameStatus, setGameStatus] = useState<'active' | 'resigned' | 'checkmate' | 'draw'>('active'); - /** - * Effect hook to update the game state and request moves from the AI opponent. - * - * This effect runs whenever the game state or move history changes. - * It updates the FEN representation of the board and the move history. - * If it's black's turn and the opponent is not human, it requests a move from the AI. - */ useEffect(() => { setFen(game.fen()); updateMoveHistory(); @@ -54,14 +21,8 @@ export function useChessGame() { } }, [game, fullHistory, opponent]); - /** - * Attempts to make a move on the chess board. - * - * @param from - The starting square of the move - * @param to - The ending square of the move - * @returns The move in Standard Algebraic Notation (SAN) if successful, null otherwise - */ - const makeAMove = async (from: Square, to: Square) => { + const makeAMove = (from: Square, to: Square) => { + console.log('makeAMove called with:', from, to); const gameCopy = new Chess(game.fen()); try { const result = gameCopy.move({ from, to, promotion: 'q' }); @@ -72,6 +33,7 @@ export function useChessGame() { setFullHistory(prevHistory => [...prevHistory, result.san]); setSuggestedMove(null); setSelectedPiece(null); + updateGameStatus(gameCopy); return result.san; } } catch (error) { @@ -80,16 +42,6 @@ export function useChessGame() { return null; }; - /** - * Handles the click event on a chess square. - * - * This function manages the piece selection and move execution process: - * - If no piece is selected, it selects a piece of the current player's color. - * - If a piece is already selected, it attempts to make a move to the clicked square. - * - If the move is invalid, it either selects a new piece or deselects the current piece. - * - * @param square - The clicked square on the chess board - */ const onSquareClick = (square: Square) => { if (selectedPiece === null) { const piece = game.get(square); @@ -112,14 +64,19 @@ export function useChessGame() { }; const onPieceDrop = (sourceSquare: string, targetSquare: string) => { + console.log('onPieceDrop called with:', sourceSquare, targetSquare); if (selectedPiece !== null) { + console.log('Piece already selected, returning false'); return false; } if (isValidSquare(sourceSquare) && isValidSquare(targetSquare)) { + console.log('Valid squares, attempting to make move'); const result = makeAMove(sourceSquare as Square, targetSquare as Square); + console.log('Move result:', result); return result !== null; } + console.log('Invalid squares, returning false'); return false; }; @@ -160,6 +117,7 @@ export function useChessGame() { setFen(newGame.fen()); setFullHistory(prevHistory => [...prevHistory, result.san]); setEvaluation(evaluation); + updateGameStatus(newGame); console.log('Move applied, new FEN:', newGame.fen()); @@ -190,8 +148,8 @@ export function useChessGame() { setSuggestedMove(null); setSelectedPiece(null); setOpponent(selectedOpponent); + setGameStatus('active'); - // If the opponent is not 'human', request a move for black if (selectedOpponent !== 'human' && newGame.turn() === 'b') { setTimeout(() => requestMove(), 500); } @@ -215,6 +173,7 @@ export function useChessGame() { setSuggestedMove(null); setSelectedPiece(null); updateMoveHistory(); + updateGameStatus(newGame); if (newHistory.length > 0) { const newEvaluation = await requestEvaluation(newGame.fen()); @@ -229,6 +188,23 @@ export function useChessGame() { } }; + const resign = () => { + if (game.turn() === 'w') { + setGameStatus('resigned'); + console.log('White resigned'); + } else { + console.log('Only White can resign'); + } + }; + + const updateGameStatus = (currentGame: Chess) => { + if (currentGame.isCheckmate()) { + setGameStatus('checkmate'); + } else if (currentGame.isDraw()) { + setGameStatus('draw'); + } + }; + return { game, fen, @@ -238,6 +214,7 @@ export function useChessGame() { evaluation, suggestedMove, opponent, + gameStatus, makeAMove, onSquareClick, onPieceDrop, @@ -245,7 +222,8 @@ export function useChessGame() { startNewGame, undoLastMove, setSuggestedMove, - setOpponent + setOpponent, + resign }; } diff --git a/shared/types.ts b/shared/types.ts index e7fa120..e499eed 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -117,4 +117,8 @@ export type WebSocketMessage = | { type: 'start_game'; opponent: string; + } + | { + type: 'onlineUsers'; + users: OnlineUser[]; };