/** * King.js - King piece implementation * Handles one-square movement and castling */ import { Piece } from './Piece.js'; export class King extends Piece { constructor(color, position) { super(color, position); this.type = 'king'; } /** * Get valid moves for king * King moves one square in any direction * @param {Board} board - Game board * @param {Board} boardForCheck - Optional board for check validation * @param {GameState} gameState - Optional game state for castling * @returns {Position[]} Array of valid positions */ getValidMoves(board, boardForCheck = null, gameState = null) { const moves = []; // All 8 directions, but only one square const directions = [ [-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1] ]; for (const [dRow, dCol] of directions) { const targetRow = this.position.row + dRow; const targetCol = this.position.col + dCol; if (!this.isInBounds(targetRow, targetCol)) { continue; } try { const targetPiece = board.getPiece(targetRow, targetCol); // Can move to empty square or capture opponent piece if (!targetPiece || targetPiece.color !== this.color) { // Check if move would put king in check if (boardForCheck && this.isSquareAttacked(board, targetRow, targetCol)) { continue; } moves.push({ row: targetRow, col: targetCol }); } } catch (e) { // Out of bounds continue; } } // Add castling moves if gameState provided if (gameState) { const castlingMoves = this.getCastlingMoves(board, gameState); moves.push(...castlingMoves); } return moves; } /** * Check if a square is attacked by opponent pieces * @param {Board} board - Game board * @param {number} row - Target row * @param {number} col - Target column * @returns {boolean} True if square is attacked */ isSquareAttacked(board, row, col) { const opponentColor = this.color === 'white' ? 'black' : 'white'; // Check all opponent pieces for (let r = 0; r < 8; r++) { for (let c = 0; c < 8; c++) { try { const piece = board.getPiece(r, c); if (piece && piece.color === opponentColor) { // Special handling for king (only check one square around) if (piece.type === 'king') { const rowDiff = Math.abs(r - row); const colDiff = Math.abs(c - col); if (rowDiff <= 1 && colDiff <= 1) { return true; } } else if (piece.type === 'rook') { // Check rook attacks (horizontal/vertical lines) if (r === row || c === col) { // Check if path is clear if (this.isPathClear(board, r, c, row, col)) { return true; } } } else if (piece.getValidMoves) { // Check if this piece can attack the target square const moves = piece.getValidMoves(board); if (moves.some(m => m.row === row && m.col === col)) { return true; } } } } catch (e) { // Skip invalid positions continue; } } } return false; } /** * Check if path between two positions is clear * @param {Board} board - Game board * @param {number} fromRow - Start row * @param {number} fromCol - Start column * @param {number} toRow - End row * @param {number} toCol - End column * @returns {boolean} True if path is clear */ isPathClear(board, fromRow, fromCol, toRow, toCol) { const rowStep = toRow === fromRow ? 0 : (toRow > fromRow ? 1 : -1); const colStep = toCol === fromCol ? 0 : (toCol > fromCol ? 1 : -1); let currentRow = fromRow + rowStep; let currentCol = fromCol + colStep; while (currentRow !== toRow || currentCol !== toCol) { try { if (board.getPiece(currentRow, currentCol) !== null) { return false; } } catch (e) { return false; } currentRow += rowStep; currentCol += colStep; } return true; } /** * Get castling move positions * @param {Board} board - Game board * @param {GameState} gameState - Game state * @returns {Position[]} Castling target positions */ getCastlingMoves(board, gameState) { const moves = []; // Can't castle if king has moved if (this.hasMoved) { return moves; } const row = this.position.row; // Cannot castle if currently in check if (this.isSquareAttacked(board, this.position.row, this.position.col)) { return moves; } try { // Kingside castling (king to g-file) const kingsideRook = board.getPiece(row, 7); if (kingsideRook && kingsideRook.type === 'rook' && kingsideRook.color === this.color && !kingsideRook.hasMoved) { // Check if squares between king and rook are empty if (this.isEmpty(board, row, 5) && this.isEmpty(board, row, 6)) { // Cannot castle through check - check f1/f8 and g1/g8 if (!this.isSquareAttacked(board, row, 5) && !this.isSquareAttacked(board, row, 6)) { moves.push({ row, col: 6, castling: 'kingside' }); } } } } catch (e) { // Skip if out of bounds } try { // Queenside castling (king to c-file) const queensideRook = board.getPiece(row, 0); if (queensideRook && queensideRook.type === 'rook' && queensideRook.color === this.color && !queensideRook.hasMoved) { // Check if squares between king and rook are empty if (this.isEmpty(board, row, 1) && this.isEmpty(board, row, 2) && this.isEmpty(board, row, 3)) { // Cannot castle through check - check d1/d8 and c1/c8 if (!this.isSquareAttacked(board, row, 3) && !this.isSquareAttacked(board, row, 2)) { moves.push({ row, col: 2, castling: 'queenside' }); } } } } catch (e) { // Skip if out of bounds } return moves; } }