Spaces:
Running
Running
| import { motion } from 'framer-motion'; | |
| export default function ChessBoard({ board, onSquareClick, selectedPos, validMoves, lastMove, turn }) { | |
| const files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; | |
| const ranks = ['8', '7', '6', '5', '4', '3', '2', '1']; | |
| return ( | |
| <div className="relative"> | |
| {/* Board Border Decoration */} | |
| <div className="absolute -inset-4 bg-black z-0 hidden sm:block" /> | |
| <div className="grid grid-cols-8 grid-rows-8 border-4 border-black bg-black gap-0 relative z-10 shadow-piece"> | |
| {board.map((row, rowIndex) => ( | |
| row.map((piece, colIndex) => { | |
| const isBlackSquare = (rowIndex + colIndex) % 2 === 1; | |
| const isSelected = selectedPos?.r === rowIndex && selectedPos?.c === colIndex; | |
| const isValidMove = validMoves.some(m => m.r === rowIndex && m.c === colIndex); | |
| const isLastMoveFrom = lastMove?.from.r === rowIndex && lastMove?.from.c === colIndex; | |
| const isLastMoveTo = lastMove?.to.r === rowIndex && lastMove?.to.c === colIndex; | |
| const isCheck = false; // Simplified for this demo | |
| return ( | |
| <div | |
| key={`${rowIndex}-${colIndex}`} | |
| onClick={() => onSquareClick(rowIndex, colIndex)} | |
| className={` | |
| chess-square | |
| ${isBlackSquare ? 'bg-[#769656]' : 'bg-[#eeeed2]'} | |
| ${isSelected ? '!bg-comic-yellow ring-4 ring-inset ring-black z-20' : ''} | |
| ${(isLastMoveFrom || isLastMoveTo) && !isSelected ? 'bg-comic-blue/40' : ''} | |
| `} | |
| > | |
| {/* Coordinate Labels */} | |
| {colIndex === 0 && ( | |
| <span className={`absolute top-0.5 left-1 text-[10px] font-bold ${isBlackSquare ? 'text-[#eeeed2]' : 'text-[#769656]'}`}> | |
| {ranks[rowIndex]} | |
| </span> | |
| )} | |
| {rowIndex === 7 && ( | |
| <span className={`absolute bottom-0 right-1 text-[10px] font-bold ${isBlackSquare ? 'text-[#eeeed2]' : 'text-[#769656]'}`}> | |
| {files[colIndex]} | |
| </span> | |
| )} | |
| {/* Valid Move Indicator */} | |
| {isValidMove && !piece && ( | |
| <div className="w-4 h-4 rounded-full bg-black/20" /> | |
| )} | |
| {isValidMove && piece && ( | |
| <div className="absolute inset-0 ring-4 ring-inset ring-comic-red/50 rounded-none" /> | |
| )} | |
| {/* Piece */} | |
| {piece && ( | |
| <motion.div | |
| initial={{ scale: 0.8, opacity: 0 }} | |
| animate={{ scale: 1, opacity: 1 }} | |
| className={` | |
| piece-3d | |
| ${isWhitePiece(piece) ? 'text-white drop-shadow-[2px_2px_0_rgba(0,0,0,1)]' : 'text-black drop-shadow-[1px_1px_0_rgba(255,255,255,0.5)]'} | |
| ${turn === 'white' && isWhitePiece(piece) ? 'cursor-grab active:cursor-grabbing' : ''} | |
| ${turn === 'black' && isBlackPiece(piece) ? 'cursor-grab active:cursor-grabbing' : ''} | |
| `} | |
| style={{ | |
| filter: isWhitePiece(piece) | |
| ? 'drop-shadow(2px 4px 6px rgba(0,0,0,0.4))' | |
| : 'drop-shadow(2px 4px 6px rgba(0,0,0,0.2))' | |
| > | |
| {getPieceSymbol(piece)} | |
| </motion.div> | |
| )} | |
| </div> | |
| ); | |
| }) | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const isWhitePiece = (p) => p === p.toUpperCase(); | |
| const isBlackPiece = (p) => p === p.toLowerCase(); | |
| const getPieceSymbol = (p) => { | |
| const map = { | |
| 'K': 'β', 'Q': 'β', 'R': 'β', 'B': 'β', 'N': 'β', 'P': 'β', | |
| 'k': 'β', 'q': 'β', 'r': 'β', 'b': 'β', 'n': 'β', 'p': 'β', | |
| }; | |
| return map[p] || ''; | |
| }; |