Spaces:
Sleeping
Sleeping
import React, { useCallback, useState } from 'react' | |
import { Square } from 'chess.js' | |
import { ChessPiece } from './ChessPiece' | |
import { GameState, DraggedPiece } from '../types/chess' | |
import { isSquareLight } from '../utils/chessUtils' | |
import { AudioEngine } from '../engines/AudioEngine' | |
import '../styles/ChessBoard.css' | |
import '../styles/ChessSquare.css' | |
export const ChessBoard: React.FC<{ | |
gameState: GameState | |
draggedPiece: DraggedPiece | null | |
audioEngine: AudioEngine | null | |
onSquareClick: (square: Square) => void | |
onPieceDragStart: (square: Square) => void | |
onPieceDrop: (targetSquare: Square | null) => void | |
}> = ({ | |
gameState, | |
draggedPiece, | |
audioEngine, | |
onSquareClick, | |
onPieceDragStart, | |
onPieceDrop | |
}) => { | |
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }) | |
const [isDragging, setIsDragging] = useState(false) | |
const handleMouseMove = useCallback((event: React.MouseEvent) => { | |
setMousePosition({ x: event.clientX, y: event.clientY }) | |
}, []) | |
const handleSquareClick = useCallback((square: Square) => { | |
if (!isDragging) { | |
onSquareClick(square) | |
} | |
}, [onSquareClick, isDragging]) | |
const handleMouseDown = useCallback((square: Square) => { | |
const piece = gameState.board.get(square) | |
if (piece && piece.color === gameState.playerColor) { | |
setIsDragging(true) | |
onPieceDragStart(square) | |
} | |
}, [gameState, onPieceDragStart]) | |
const handleMouseUp = useCallback((square: Square) => { | |
if (isDragging) { | |
onPieceDrop(square) | |
setIsDragging(false) | |
} | |
}, [isDragging, onPieceDrop]) | |
const renderSquares = () => { | |
const renderedSquares = [] | |
for (let rank = 8; rank >= 1; rank--) { | |
for (let file = 0; file < 8; file++) { | |
const square = (String.fromCharCode(97 + file) + rank) as Square | |
let displayFile = file | |
let displayRank = rank | |
if (gameState.playerColor === 'b') { | |
displayFile = 7 - file | |
displayRank = 9 - rank | |
} | |
const piece = gameState.board.get(square) || null | |
const isSelected = gameState.selectedSquare === square | |
const isLegalMove = gameState.legalMoves.some(move => move.to === square) | |
const isDraggedSquare = draggedPiece?.square === square | |
const isLight = isSquareLight(square) | |
renderedSquares.push( | |
<div | |
key={square} | |
className={`chess-square ${isLight ? 'light' : 'dark'} ${isSelected ? 'selected' : ''} ${isLegalMove ? 'legal-move' : ''}`} | |
style={{ | |
position: 'absolute', | |
left: displayFile * 75, | |
top: (8 - displayRank) * 75, | |
width: 75, | |
height: 75 | |
}} | |
onClick={() => handleSquareClick(square)} | |
onMouseDown={() => handleMouseDown(square)} | |
onMouseUp={() => handleMouseUp(square)} | |
> | |
{piece && !isDraggedSquare && ( | |
<div className="piece-container"> | |
<ChessPiece piece={piece} size={75} /> | |
</div> | |
)} | |
{isLegalMove && ( | |
<div className="legal-move-indicator" /> | |
)} | |
</div> | |
) | |
} | |
} | |
return renderedSquares | |
} | |
const renderDraggedPiece = () => { | |
if (!draggedPiece) return null | |
return ( | |
<div | |
className="dragged-piece" | |
style={{ | |
left: mousePosition.x - 37.5, | |
top: mousePosition.y - 37.5, | |
position: 'fixed', | |
pointerEvents: 'none', | |
zIndex: 1000 | |
}} | |
> | |
<ChessPiece | |
piece={draggedPiece.piece} | |
size={75} | |
/> | |
</div> | |
) | |
} | |
return ( | |
<div | |
className="chess-board" | |
onMouseMove={handleMouseMove} | |
> | |
<div className="board-container"> | |
<div className="board-squares"> | |
{renderSquares()} | |
</div> | |
<div className="board-border" /> | |
<div className="file-labels"> | |
{Array.from({ length: 8 }, (_, i) => { | |
const fileIndex = gameState.playerColor === 'w' ? i : 7 - i | |
const fileLabel = String.fromCharCode(97 + fileIndex) | |
const noteName = audioEngine?.getFileNoteName(fileIndex) || '' | |
return ( | |
<div key={fileLabel} className="file-label"> | |
<span className="file-letter">{fileLabel}</span> | |
<span className="file-note">({noteName})</span> | |
</div> | |
) | |
})} | |
</div> | |
<div className="rank-labels"> | |
{Array.from({ length: 8 }, (_, i) => { | |
const rankIndex = gameState.playerColor === 'w' ? 8 - i : i + 1 | |
const octave = gameState.playerColor === 'w' ? 8 - i : i + 1 | |
return ( | |
<div key={rankIndex} className="rank-label"> | |
<span className="rank-number">{rankIndex}</span> | |
<span className="rank-octave">(♪{octave})</span> | |
</div> | |
) | |
})} | |
</div> | |
</div> | |
{renderDraggedPiece()} | |
</div> | |
) | |
} |