musical-chess / src /components /ChessBoard.tsx
Maximus Powers
final
3568151
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>
)
}