/** * @file sliding-piece-pattern.js * @description Pattern for implementing sliding pieces (Rook, Bishop, Queen) * These pieces slide along lines until blocked */ import Piece from '../models/Piece.js'; import { DIRECTIONS } from '../utils/Constants.js'; import { isValidPosition } from '../utils/Helpers.js'; /** * @class Rook * @extends Piece * @description Example of sliding piece implementation * * Pattern applies to: * - Rook: DIRECTIONS.ORTHOGONAL (vertical and horizontal) * - Bishop: DIRECTIONS.DIAGONAL * - Queen: [...DIRECTIONS.ORTHOGONAL, ...DIRECTIONS.DIAGONAL] */ class Rook extends Piece { constructor(color, position) { super(color, position, 'rook'); } /** * Gets all valid moves for this rook * Uses sliding piece pattern * * @param {Board} board - Current board state * @returns {Array} Array of valid positions */ getValidMoves(board) { const moves = []; // Rook moves in 4 orthogonal directions const directions = DIRECTIONS.ORTHOGONAL; // For each direction, slide until blocked for (const direction of directions) { const directionMoves = this._getMovesInDirection(board, direction); moves.push(...directionMoves); } return moves; } /** * Gets all moves in a specific direction * * @private * @param {Board} board - Current board state * @param {Object} direction - Direction vector {row, col} * @returns {Array} Valid positions in this direction */ _getMovesInDirection(board, direction) { const moves = []; const { row, col } = this.position; let currentRow = row + direction.row; let currentCol = col + direction.col; // Slide in direction until we hit edge or piece while (isValidPosition(currentRow, currentCol)) { const currentPos = { row: currentRow, col: currentCol }; const piece = board.getPieceAt(currentPos); if (!piece) { // Empty square - can move here and continue moves.push(currentPos); } else if (piece.color !== this.color) { // Enemy piece - can capture but can't continue moves.push(currentPos); break; } else { // Friendly piece - can't move here, stop break; } // Continue sliding currentRow += direction.row; currentCol += direction.col; } return moves; } clone() { const clone = new Rook(this.color, { ...this.position }); clone.hasMoved = this.hasMoved; return clone; } } /** * @class Bishop * @extends Piece * @description Bishop using same sliding pattern with diagonal directions */ class Bishop extends Piece { constructor(color, position) { super(color, position, 'bishop'); } getValidMoves(board) { const moves = []; const directions = DIRECTIONS.DIAGONAL; // Only difference from Rook! for (const direction of directions) { const directionMoves = this._getMovesInDirection(board, direction); moves.push(...directionMoves); } return moves; } _getMovesInDirection(board, direction) { // Identical to Rook implementation const moves = []; const { row, col } = this.position; let currentRow = row + direction.row; let currentCol = col + direction.col; while (isValidPosition(currentRow, currentCol)) { const currentPos = { row: currentRow, col: currentCol }; const piece = board.getPieceAt(currentPos); if (!piece) { moves.push(currentPos); } else if (piece.color !== this.color) { moves.push(currentPos); break; } else { break; } currentRow += direction.row; currentCol += direction.col; } return moves; } clone() { const clone = new Bishop(this.color, { ...this.position }); clone.hasMoved = this.hasMoved; return clone; } } /** * @class Queen * @extends Piece * @description Queen combines Rook + Bishop movements */ class Queen extends Piece { constructor(color, position) { super(color, position, 'queen'); } getValidMoves(board) { const moves = []; // Queen moves in all 8 directions const directions = [...DIRECTIONS.ORTHOGONAL, ...DIRECTIONS.DIAGONAL]; for (const direction of directions) { const directionMoves = this._getMovesInDirection(board, direction); moves.push(...directionMoves); } return moves; } _getMovesInDirection(board, direction) { // Identical to Rook/Bishop implementation const moves = []; const { row, col } = this.position; let currentRow = row + direction.row; let currentCol = col + direction.col; while (isValidPosition(currentRow, currentCol)) { const currentPos = { row: currentRow, col: currentCol }; const piece = board.getPieceAt(currentPos); if (!piece) { moves.push(currentPos); } else if (piece.color !== this.color) { moves.push(currentPos); break; } else { break; } currentRow += direction.row; currentCol += direction.col; } return moves; } clone() { const clone = new Queen(this.color, { ...this.position }); clone.hasMoved = this.hasMoved; return clone; } } /** * PATTERN SUMMARY: * * All sliding pieces use the same algorithm: * 1. Define direction vectors * 2. For each direction: * a. Start at piece position * b. Step in direction * c. Check if position is valid * d. If empty: add move, continue * e. If enemy: add move, stop * f. If friendly: stop * * OPTIMIZATION TIP: * Extract _getMovesInDirection to a shared utility function * to avoid code duplication: * * // In a SlidingPieceHelper.js file: * export function getSlidingMoves(piece, board, directions) { * const moves = []; * for (const direction of directions) { * moves.push(...getMovesInDirection(piece, board, direction)); * } * return moves; * } * * // Then in pieces: * getValidMoves(board) { * return getSlidingMoves(this, board, DIRECTIONS.ORTHOGONAL); * } */ export { Rook, Bishop, Queen };