/** * @file move-validation-flow.js * @description Complete example of move validation workflow * Shows how all components work together */ /** * MOVE VALIDATION FLOW * ==================== * * When a player attempts to move a piece, the system must validate * the move through multiple levels: * * LEVEL 1: Piece Movement Rules * - Can the piece move to that square according to its movement pattern? * - Example: Can a knight move from e4 to f6? * * LEVEL 2: Path Obstruction * - For sliding pieces, is the path clear? * - Knights skip this check (they jump) * * LEVEL 3: Capture Validation * - If capturing, is there an enemy piece at destination? * - Can't capture own pieces * * LEVEL 4: King Safety * - Does this move expose our king to check? * - This is the most complex validation * * LEVEL 5: Special Rules * - Castling requirements * - En passant validity * - Pawn promotion */ import MoveValidator from '../engine/MoveValidator.js'; import CheckDetector from '../engine/CheckDetector.js'; /** * Example validation workflow */ class MoveValidationExample { constructor(board, gameState) { this.board = board; this.gameState = gameState; this.validator = new MoveValidator(); this.checkDetector = new CheckDetector(); } /** * Complete move validation example * * @param {Piece} piece - Piece to move * @param {Object} from - Start position {row, col} * @param {Object} to - End position {row, col} * @returns {Object} Validation result with details */ validateMove(piece, from, to) { const result = { valid: false, reason: '', details: {} }; // LEVEL 1: Basic piece movement const validMoves = piece.getValidMoves(this.board); const isPieceMove = validMoves.some(move => move.row === to.row && move.col === to.col ); if (!isPieceMove) { result.reason = 'Invalid move for this piece type'; result.details.validMoves = validMoves; return result; } // LEVEL 2: Path obstruction (for sliding pieces) if (this._isSlidingPiece(piece)) { if (!this._isPathClear(from, to)) { result.reason = 'Path is blocked'; return result; } } // LEVEL 3: Capture validation const targetPiece = this.board.getPieceAt(to); if (targetPiece) { if (targetPiece.color === piece.color) { result.reason = 'Cannot capture own piece'; return result; } result.details.capture = targetPiece; } // LEVEL 4: King safety check // This is the critical check - would this move expose king? if (this._wouldExposeKing(piece, from, to)) { result.reason = 'Move would expose king to check'; return result; } // LEVEL 5: Special moves validation if (piece.type === 'king' && this._isCastlingMove(from, to)) { if (!this._validateCastling(piece, from, to)) { result.reason = 'Invalid castling'; return result; } result.details.castling = true; } if (piece.type === 'pawn' && this._isEnPassant(from, to)) { if (!this._validateEnPassant(piece, from, to)) { result.reason = 'Invalid en passant'; return result; } result.details.enPassant = true; } // All checks passed! result.valid = true; result.reason = 'Move is legal'; return result; } /** * The critical check: Does this move expose our king? * * Algorithm: * 1. Clone the board * 2. Execute the move on the clone * 3. Check if our king is in check on the cloned board * 4. If yes, move is illegal * * @private */ _wouldExposeKing(piece, from, to) { // Clone board to test move const testBoard = this.board.clone(); // Execute move on test board testBoard.movePiece(from, to); // Check if our king is in check after this move const ourColor = piece.color; const isInCheck = this.checkDetector.isKingInCheck(ourColor, testBoard); return isInCheck; } /** * Check if path is clear for sliding pieces * * @private */ _isPathClear(from, to) { // Get all squares between from and to const path = this._getPathBetween(from, to); // Check if any square is occupied return path.every(pos => !this.board.getPieceAt(pos)); } /** * Validate castling move * * Requirements: * 1. King hasn't moved * 2. Rook hasn't moved * 3. Squares between are empty * 4. King is not in check * 5. King doesn't pass through check * 6. King doesn't end in check * * @private */ _validateCastling(king, from, to) { // Check 1: King hasn't moved if (king.hasMoved) { return false; } // Check 2: Not currently in check if (this.checkDetector.isKingInCheck(king.color, this.board)) { return false; } // Determine kingside or queenside const isKingside = to.col > from.col; const rookCol = isKingside ? 7 : 0; const rook = this.board.getPieceAt({ row: from.row, col: rookCol }); // Check 3: Rook exists and hasn't moved if (!rook || rook.type !== 'rook' || rook.hasMoved) { return false; } // Check 4: Squares between are empty const path = this._getPathBetween(from, to); if (!path.every(pos => !this.board.getPieceAt(pos))) { return false; } // Check 5: King doesn't pass through check for (const pos of path) { const testBoard = this.board.clone(); testBoard.movePiece(from, pos); if (this.checkDetector.isKingInCheck(king.color, testBoard)) { return false; } } return true; } /** * Validate en passant capture * * Requirements: * 1. Target square is the en passant square from game state * 2. Enemy pawn is in correct position * 3. Enemy pawn just moved two squares * * @private */ _validateEnPassant(pawn, from, to) { const enPassantTarget = this.gameState.enPassantTarget; if (!enPassantTarget) { return false; } // Check if target matches en passant square if (to.row !== enPassantTarget.row || to.col !== enPassantTarget.col) { return false; } // Verify enemy pawn is in position const enemyPawnRow = pawn.color === 'white' ? to.row + 1 : to.row - 1; const enemyPawn = this.board.getPieceAt({ row: enemyPawnRow, col: to.col }); if (!enemyPawn || enemyPawn.type !== 'pawn' || enemyPawn.color === pawn.color) { return false; } return true; } // Helper methods _isSlidingPiece(piece) { return ['rook', 'bishop', 'queen'].includes(piece.type); } _isCastlingMove(from, to) { return Math.abs(to.col - from.col) === 2; } _isEnPassant(from, to) { // En passant is diagonal move to empty square return Math.abs(to.col - from.col) === 1 && !this.board.getPieceAt(to); } _getPathBetween(from, to) { // Returns array of positions between from and to (exclusive) // Implementation omitted for brevity return []; } } /** * USAGE EXAMPLE * ============= */ // Setup const board = new Board(); const gameState = new GameState(); const validator = new MoveValidationExample(board, gameState); // Attempt to move a piece const pawn = board.getPieceAt({ row: 6, col: 4 }); const from = { row: 6, col: 4 }; const to = { row: 4, col: 4 }; // Validate const result = validator.validateMove(pawn, from, to); if (result.valid) { console.log('Move is legal:', result.reason); if (result.details.capture) { console.log('Captures:', result.details.capture); } // Execute the move } else { console.log('Move is illegal:', result.reason); // Show error to user } /** * COMMON PITFALLS * =============== * * 1. INFINITE RECURSION * - Don't call isKingInCheck inside getValidMoves * - Use two-pass validation: basic moves → filter exposing king * * 2. FORGETTING TO CLONE * - Always clone board before testing moves * - Modifying original board breaks game state * * 3. MOVE ORDER DEPENDENCY * - Some checks must come before others * - King safety check must be last (most expensive) * * 4. EN PASSANT STATE * - Must be cleared after any move that isn't en passant capture * - Only valid immediately after opponent's two-square pawn move * * 5. CASTLING EDGE CASES * - Check ALL conditions * - Most common bug: forgetting to check if king passes through check */ export default MoveValidationExample;