Restructured project from nested workspace pattern to flat single-repo layout. This eliminates redundant nesting and consolidates all project files under version control. ## Migration Summary **Before:** ``` alex/ (workspace, not versioned) ├── chess-game/ (git repo) │ ├── js/, css/, tests/ │ └── index.html └── docs/ (planning, not versioned) ``` **After:** ``` alex/ (git repo, everything versioned) ├── js/, css/, tests/ ├── index.html ├── docs/ (project documentation) ├── planning/ (historical planning docs) ├── .gitea/ (CI/CD) └── CLAUDE.md (configuration) ``` ## Changes Made ### Structure Consolidation - Moved all chess-game/ contents to root level - Removed redundant chess-game/ subdirectory - Flattened directory structure (eliminated one nesting level) ### Documentation Organization - Moved chess-game/docs/ → docs/ (project documentation) - Moved alex/docs/ → planning/ (historical planning documents) - Added CLAUDE.md (workspace configuration) - Added IMPLEMENTATION_PROMPT.md (original project prompt) ### Version Control Improvements - All project files now under version control - Planning documents preserved in planning/ folder - Merged .gitignore files (workspace + project) - Added .claude/ agent configurations ### File Updates - Updated .gitignore to include both workspace and project excludes - Moved README.md to root level - All import paths remain functional (relative paths unchanged) ## Benefits ✅ **Simpler Structure** - One level of nesting removed ✅ **Complete Versioning** - All documentation now in git ✅ **Standard Layout** - Matches open-source project conventions ✅ **Easier Navigation** - Direct access to all project files ✅ **CI/CD Compatible** - All workflows still functional ## Technical Validation - ✅ Node.js environment verified - ✅ Dependencies installed successfully - ✅ Dev server starts and responds - ✅ All core files present and accessible - ✅ Git repository functional ## Files Preserved **Implementation Files:** - js/ (3,517 lines of code) - css/ (4 stylesheets) - tests/ (87 test cases) - index.html - package.json **CI/CD Pipeline:** - .gitea/workflows/ci.yml - .gitea/workflows/release.yml **Documentation:** - docs/ (12+ documentation files) - planning/ (historical planning materials) - README.md **Configuration:** - jest.config.js, babel.config.cjs, playwright.config.js - .gitignore (merged) - CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
324 lines
8.3 KiB
JavaScript
324 lines
8.3 KiB
JavaScript
/**
|
|
* @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;
|