Spaces:
Sleeping
Sleeping
import { useState, useCallback, useRef, useEffect } from 'react' | |
import { Chess, Square, Move, Color } from 'chess.js' | |
import { GameState, GameHistoryEntry, DraggedPiece } from '../types/chess' | |
import { evaluateGameState, isPotentialPromotion } from '../utils/chessUtils' | |
import { ChessAI } from '../engines/ChessAI' | |
export function useChessGame() { | |
const [gameState, setGameState] = useState<GameState>({ | |
board: new Chess(), | |
gameActive: false, | |
playerColor: 'w', | |
selectedSquare: null, | |
legalMoves: [], | |
gameHistory: [], | |
gameOver: false, | |
gameResult: null, | |
promotionMove: null, | |
promotionDialogActive: false, | |
aiThinking: false, | |
aiModelLoaded: false, | |
aiModelLoading: false | |
}) | |
const [selectedModel, setSelectedModel] = useState<string>('mlabonne/chesspythia-70m') | |
const [draggedPiece, setDraggedPiece] = useState<DraggedPiece | null>(null) | |
const aiTimeoutRef = useRef<NodeJS.Timeout>() | |
const chessAI = useRef<ChessAI | null>(null) | |
// init chess model | |
useEffect(() => { | |
const initializeAI = async () => { | |
setGameState(prev => ({ ...prev, aiModelLoading: true })) | |
chessAI.current = new ChessAI(selectedModel) | |
try { | |
await chessAI.current.initialize() | |
setGameState(prev => ({ | |
...prev, | |
aiModelLoaded: true, | |
aiModelLoading: false | |
})) | |
console.log(`Chess AI model ${selectedModel} loaded successfully`) | |
} catch (error) { | |
console.error(`Failed to load Chess AI model ${selectedModel}:`, error) | |
setGameState(prev => ({ | |
...prev, | |
aiModelLoaded: false, | |
aiModelLoading: false | |
})) | |
} | |
} | |
initializeAI() | |
return () => { | |
if (aiTimeoutRef.current) { | |
clearTimeout(aiTimeoutRef.current) | |
} | |
} | |
}, [selectedModel]) | |
const startNewGame = useCallback(() => { | |
clearTimeout(aiTimeoutRef.current) | |
setGameState(prev => ({ | |
...prev, | |
board: new Chess(), | |
gameActive: true, | |
gameOver: false, | |
gameHistory: [], | |
selectedSquare: null, | |
legalMoves: [], | |
gameResult: null, | |
promotionDialogActive: false, | |
promotionMove: null, | |
aiThinking: false | |
})) | |
if (gameState.playerColor === 'b') { | |
setGameState(prev => ({ ...prev, aiThinking: true })) | |
aiTimeoutRef.current = setTimeout(() => { | |
makeAIMove() | |
}, 500) | |
} | |
}, [gameState.playerColor]) | |
const resignGame = useCallback(() => { | |
clearTimeout(aiTimeoutRef.current) | |
const winner = gameState.playerColor === 'w' ? 'b' : 'w' | |
const gameResult = { | |
isGameOver: true, | |
winner: winner as Color, | |
message: `Game Over! ${gameState.playerColor === 'w' ? 'White' : 'Black'} resigned - ${winner === 'w' ? 'White' : 'Black'} wins!`, | |
terminationReason: 'resignation', | |
details: `${gameState.playerColor === 'w' ? 'White' : 'Black'} player resigned the game` | |
} | |
setGameState(prev => ({ | |
...prev, | |
gameActive: false, | |
gameOver: true, | |
gameResult, | |
aiThinking: false | |
})) | |
}, [gameState.playerColor]) | |
const togglePlayerColor = useCallback(() => { | |
if (!gameState.gameActive) { | |
setGameState(prev => ({ | |
...prev, | |
playerColor: prev.playerColor === 'w' ? 'b' : 'w' | |
})) | |
} | |
}, [gameState.gameActive]) | |
const selectSquare = useCallback((square: Square) => { | |
if (!gameState.gameActive || gameState.board.turn() !== gameState.playerColor || gameState.aiThinking) { | |
return | |
} | |
const piece = gameState.board.get(square) | |
if (piece && piece.color === gameState.playerColor) { | |
const moves = gameState.board.moves({ square, verbose: true }) | |
setGameState(prev => ({ | |
...prev, | |
selectedSquare: square, | |
legalMoves: moves | |
})) | |
} | |
}, [gameState]) | |
const attemptMove = useCallback((from: Square, to: Square) => { | |
console.log('attemptMove called:', from, 'to', to) | |
if (!gameState.gameActive || gameState.board.turn() !== gameState.playerColor) { | |
console.log('Move rejected: game not active or not player turn') | |
return false | |
} | |
if (isPotentialPromotion(gameState.board, from, to)) { | |
const possibleMoves = gameState.board.moves({ square: from, verbose: true }) | |
const hasLegalPromotion = possibleMoves.some(move => | |
move.to === to && move.promotion !== undefined | |
) | |
if (hasLegalPromotion) { | |
setGameState(prev => ({ | |
...prev, | |
promotionMove: { from, to, promotion: 'q' } as Move, | |
promotionDialogActive: true, | |
selectedSquare: null, | |
legalMoves: [] | |
})) | |
return true | |
} | |
} | |
try { | |
const testBoard = new Chess(gameState.board.fen()) | |
const move = testBoard.move({ from, to }) | |
if (move) { | |
const historyEntry: GameHistoryEntry = { | |
move: move.san, | |
moveData: move, | |
player: 'Human', | |
timestamp: new Date(), | |
capturedPiece: move.captured ? { type: move.captured, color: gameState.board.turn() === 'w' ? 'b' : 'w' } : undefined | |
} | |
setGameState(prev => ({ | |
...prev, | |
board: testBoard, | |
gameHistory: [...prev.gameHistory, historyEntry], | |
selectedSquare: null, | |
legalMoves: [] | |
})) | |
const gameResult = evaluateGameState(testBoard) | |
if (gameResult.isGameOver) { | |
setGameState(prev => ({ | |
...prev, | |
gameActive: false, | |
gameOver: true, | |
gameResult | |
})) | |
} else { | |
// trigger AI move | |
setGameState(prev => ({ ...prev, aiThinking: true })) | |
aiTimeoutRef.current = setTimeout(() => { | |
makeAIMove() | |
}, 1000) | |
} | |
return true | |
} else { | |
console.log('Move returned null/false') | |
} | |
} catch (error) { | |
console.log('Invalid move attempted:', error) | |
} | |
return false | |
}, [gameState]) | |
const makeAIMove = useCallback(async () => { | |
if (!chessAI.current) { | |
setGameState(prev => ({ ...prev, aiThinking: false })) | |
return | |
} | |
try { | |
setGameState(prev => { | |
const currentBoard = new Chess(prev.board.fen()) | |
if (!prev.gameActive || currentBoard.turn() === prev.playerColor) { | |
return { ...prev, aiThinking: false } | |
} | |
const possibleMoves = currentBoard.moves({ verbose: true }) | |
if (possibleMoves.length === 0) { | |
return { ...prev, aiThinking: false } | |
} | |
chessAI.current!.getMove(currentBoard, 10000).then(aiMove => { | |
if (!aiMove) { | |
setGameState(prev => ({ ...prev, aiThinking: false })) | |
return | |
} | |
const moveResult = currentBoard.move(aiMove) | |
if (!moveResult) { | |
setGameState(prev => ({ ...prev, aiThinking: false })) | |
return | |
} | |
const historyEntry: GameHistoryEntry = { | |
move: moveResult.san, | |
moveData: moveResult, | |
player: 'AI', | |
timestamp: new Date(), | |
capturedPiece: moveResult.captured ? { type: moveResult.captured, color: currentBoard.turn() === 'w' ? 'b' : 'w' } : undefined | |
} | |
const gameResult = evaluateGameState(currentBoard) | |
setGameState(prev => ({ | |
...prev, | |
board: currentBoard, | |
gameHistory: [...prev.gameHistory, historyEntry], | |
aiThinking: false, | |
gameActive: !gameResult.isGameOver, | |
gameOver: gameResult.isGameOver, | |
gameResult: gameResult.isGameOver ? gameResult : null | |
})) | |
}).catch(error => { | |
console.error('Error in AI move generation:', error) | |
setGameState(prev => ({ ...prev, aiThinking: false })) | |
}) | |
return prev | |
}) | |
} catch (error) { | |
console.error('Error in AI move setup:', error) | |
setGameState(prev => ({ ...prev, aiThinking: false })) | |
} | |
}, []) | |
const completePromotion = useCallback((promotionPiece: 'q' | 'r' | 'b' | 'n') => { | |
if (!gameState.promotionMove) return | |
try { | |
const testBoard = new Chess(gameState.board.fen()) | |
const move = testBoard.move({ | |
from: gameState.promotionMove.from, | |
to: gameState.promotionMove.to, | |
promotion: promotionPiece | |
}) | |
if (move) { | |
const historyEntry: GameHistoryEntry = { | |
move: move.san, | |
moveData: move, | |
player: 'Human', | |
timestamp: new Date(), | |
capturedPiece: move.captured ? { type: move.captured, color: gameState.board.turn() === 'w' ? 'b' : 'w' } : undefined | |
} | |
setGameState(prev => ({ | |
...prev, | |
board: testBoard, | |
gameHistory: [...prev.gameHistory, historyEntry], | |
selectedSquare: null, | |
legalMoves: [], | |
promotionDialogActive: false, | |
promotionMove: null | |
})) | |
const gameResult = evaluateGameState(testBoard) | |
if (gameResult.isGameOver) { | |
setGameState(prev => ({ | |
...prev, | |
gameActive: false, | |
gameOver: true, | |
gameResult | |
})) | |
} else { | |
setGameState(prev => ({ ...prev, aiThinking: true })) | |
aiTimeoutRef.current = setTimeout(() => { | |
makeAIMove() | |
}, 1000) | |
} | |
} else { | |
setGameState(prev => ({ | |
...prev, | |
promotionDialogActive: false, | |
promotionMove: null | |
})) | |
} | |
} catch (error) { | |
console.error('Error during promotion:', error) | |
setGameState(prev => ({ | |
...prev, | |
promotionDialogActive: false, | |
promotionMove: null | |
})) | |
} | |
}, [gameState.promotionMove, gameState.board, makeAIMove]) | |
const startDrag = useCallback((square: Square) => { | |
if (!gameState.gameActive || gameState.board.turn() !== gameState.playerColor || gameState.aiThinking) { | |
return | |
} | |
const piece = gameState.board.get(square) | |
if (piece && piece.color === gameState.playerColor) { | |
setDraggedPiece({ piece, square }) | |
const moves = gameState.board.moves({ square, verbose: true }) | |
setGameState(prev => ({ | |
...prev, | |
selectedSquare: square, | |
legalMoves: moves | |
})) | |
} | |
}, [gameState]) | |
const endDrag = useCallback((targetSquare: Square | null) => { | |
console.log('Ending drag at:', targetSquare, 'from:', draggedPiece?.square) | |
let moveSuccessful = false | |
if (draggedPiece && targetSquare && targetSquare !== draggedPiece.square) { | |
moveSuccessful = attemptMove(draggedPiece.square, targetSquare) | |
} | |
setDraggedPiece(null) | |
if (!moveSuccessful) { | |
setGameState(prev => ({ | |
...prev, | |
selectedSquare: null, | |
legalMoves: [] | |
})) | |
} | |
}, [draggedPiece, attemptMove]) | |
const getAIStatus = useCallback(() => { | |
if (!chessAI.current) return 'Not initialized' | |
return chessAI.current.getModelInfo() | |
}, []) | |
const changeModel = useCallback((modelId: string) => { | |
if (gameState.gameActive) return | |
setSelectedModel(modelId) | |
}, [gameState.gameActive]) | |
useEffect(() => { | |
return () => { | |
if (aiTimeoutRef.current) { | |
clearTimeout(aiTimeoutRef.current) | |
} | |
} | |
}, []) | |
return { | |
gameState, | |
draggedPiece, | |
selectedModel, | |
startNewGame, | |
resignGame, | |
togglePlayerColor, | |
selectSquare, | |
attemptMove, | |
completePromotion, | |
startDrag, | |
endDrag, | |
getAIStatus, | |
changeModel | |
} | |
} |