/** * Piece.js - Base class for all chess pieces * Defines common interface and behavior */ export class Piece { /** * @param {string} color - 'white' or 'black' * @param {Position} position - {row, col} */ constructor(color, position) { this.color = color; this.position = position; this.type = null; // Set by subclasses this.hasMoved = false; this.value = 0; // Set by subclasses } /** * Get all valid moves (without check validation) * Must be implemented by subclasses * @param {Board} board - Game board * @returns {Position[]} Array of valid positions */ getValidMoves(board) { throw new Error(`getValidMoves must be implemented by ${this.constructor.name}`); } /** * Check if move to position is valid * @param {Board} board - Game board * @param {number} toRow - Target row * @param {number} toCol - Target column * @returns {boolean} True if valid */ isValidMove(board, toRow, toCol) { const validMoves = this.getValidMoves(board); return validMoves.some(move => move.row === toRow && move.col === toCol); } /** * Check if position is within board bounds * @param {number} row - Row index * @param {number} col - Column index * @returns {boolean} True if in bounds */ isInBounds(row, col) { return row >= 0 && row < 8 && col >= 0 && col < 8; } /** * Create deep copy of piece * @returns {Piece} Cloned piece */ clone() { const PieceClass = this.constructor; const cloned = new PieceClass(this.color, { ...this.position }); cloned.hasMoved = this.hasMoved; return cloned; } /** * Get Unicode symbol for piece * @returns {string} Unicode character */ getSymbol() { const symbols = { white: { king: '♔', queen: '♕', rook: '♖', bishop: '♗', knight: '♘', pawn: '♙' }, black: { king: '♚', queen: '♛', rook: '♜', bishop: '♝', knight: '♞', pawn: '♟' } }; return symbols[this.color]?.[this.type] || ''; } /** * Get FEN character for piece * @returns {string} FEN character */ toFENChar() { const chars = { king: 'k', queen: 'q', rook: 'r', bishop: 'b', knight: 'n', pawn: 'p' }; const char = chars[this.type] || ''; return this.color === 'white' ? char.toUpperCase() : char; } /** * Check if position has enemy piece * @param {Board} board - Game board * @param {number} row - Row index * @param {number} col - Column index * @returns {boolean} True if enemy piece present */ hasEnemyPiece(board, row, col) { const piece = board.getPiece(row, col); return piece !== null && piece.color !== this.color; } /** * Check if position is empty * @param {Board} board - Game board * @param {number} row - Row index * @param {number} col - Column index * @returns {boolean} True if empty */ isEmpty(board, row, col) { return board.getPiece(row, col) === null; } /** * Add sliding moves in given directions * @param {Board} board - Game board * @param {Array<[number, number]>} directions - Direction vectors * @returns {Position[]} Valid positions */ getSlidingMoves(board, directions) { const moves = []; for (const [dRow, dCol] of directions) { let currentRow = this.position.row + dRow; let currentCol = this.position.col + dCol; while (this.isInBounds(currentRow, currentCol)) { const targetPiece = board.getPiece(currentRow, currentCol); if (!targetPiece) { // Empty square moves.push({ row: currentRow, col: currentCol }); } else { // Piece in the way if (targetPiece.color !== this.color) { // Can capture opponent piece moves.push({ row: currentRow, col: currentCol }); } break; // Can't move further } currentRow += dRow; currentCol += dCol; } } return moves; } }