Created ESLint configuration and auto-fixed code style violations to resolve CI/CD pipeline linting failures. ## Problem CI/CD pipeline was failing at the linting step: ``` ESLint couldn't find a configuration file. ``` The project had ESLint installed but no configuration, causing the lint step in the pipeline to fail. ## Solution ### 1. Created .eslintrc.json **Configuration Details:** - Environment: Browser, ES2021, Node - Extends: eslint:recommended - Parser: ES Modules, latest ECMAScript - Rules: - 4-space indentation - Unix line endings - Single quotes (with escape allowance) - Semicolons required - Unused vars as warnings (prefixed with _ ignored) - Console allowed (common in development) ### 2. Auto-Fixed Code Issues Ran `eslint --fix` to automatically resolve: - ✅ 16 indentation errors (standardized to 4 spaces) - ✅ Formatting inconsistencies - ✅ Code style violations **Remaining:** - 6 warnings for unused parameters (acceptable, won't fail CI) - These are interface parameters maintained for consistency ## Files Modified - .eslintrc.json (new) - ESLint configuration - js/engine/MoveValidator.js - indentation fixes - js/engine/SpecialMoves.js - indentation fixes - js/main.js - style fixes - js/pieces/King.js - formatting - js/pieces/Piece.js - formatting ## Verification ```bash npm run lint ✖ 6 problems (0 errors, 6 warnings) ``` ✅ No errors - pipeline will pass ⚠️ 6 warnings - informational only, don't fail build ## Impact ✅ CI/CD linting step will now succeed ✅ Consistent code style across project ✅ Automated style checking on all commits ✅ Better code readability and maintainability 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
290 lines
9.8 KiB
JavaScript
290 lines
9.8 KiB
JavaScript
/**
|
|
* MoveValidator.js - Chess move validation engine
|
|
* Validates moves including check constraints
|
|
*/
|
|
|
|
export class MoveValidator {
|
|
/**
|
|
* Check if move is legal (including check validation)
|
|
* @param {Board} board - Game board
|
|
* @param {Piece} piece - Piece to move
|
|
* @param {number} toRow - Target row
|
|
* @param {number} toCol - Target column
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {boolean} True if legal
|
|
*/
|
|
static isMoveLegal(board, piece, toRow, toCol, gameState) {
|
|
// 1. Check if move is in piece's valid moves
|
|
if (!piece.isValidMove(board, toRow, toCol)) {
|
|
return false;
|
|
}
|
|
|
|
// 2. Simulate move to check if it leaves king in check
|
|
const simulatedBoard = this.simulateMove(board, piece, toRow, toCol);
|
|
|
|
// 3. Verify own king is not in check after move
|
|
if (this.isKingInCheck(simulatedBoard, piece.color)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Simulate a move on a cloned board
|
|
* @param {Board} board - Original board
|
|
* @param {Piece} piece - Piece to move
|
|
* @param {number} toRow - Target row
|
|
* @param {number} toCol - Target column
|
|
* @returns {Board} Board with simulated move
|
|
*/
|
|
static simulateMove(board, piece, toRow, toCol) {
|
|
const clonedBoard = board.clone();
|
|
const fromRow = piece.position.row;
|
|
const fromCol = piece.position.col;
|
|
|
|
clonedBoard.movePiece(fromRow, fromCol, toRow, toCol);
|
|
|
|
return clonedBoard;
|
|
}
|
|
|
|
/**
|
|
* Check if king is in check
|
|
* @param {Board} board - Game board
|
|
* @param {string} color - King color ('white' or 'black')
|
|
* @returns {boolean} True if in check
|
|
*/
|
|
static isKingInCheck(board, color) {
|
|
// Find king position
|
|
const kingPos = board.findKing(color);
|
|
if (!kingPos) return false;
|
|
|
|
// Check if any opponent piece can attack king
|
|
const opponentColor = color === 'white' ? 'black' : 'white';
|
|
|
|
for (let row = 0; row < 8; row++) {
|
|
for (let col = 0; col < 8; col++) {
|
|
const piece = board.getPiece(row, col);
|
|
|
|
if (piece && piece.color === opponentColor) {
|
|
// Get piece's valid moves (without recursion into check validation)
|
|
const validMoves = piece.getValidMoves(board);
|
|
|
|
// Check if king position is in attack range
|
|
if (validMoves.some(move =>
|
|
move.row === kingPos.row && move.col === kingPos.col)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if position is checkmate
|
|
* @param {Board} board - Game board
|
|
* @param {string} color - Player color
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {boolean} True if checkmate
|
|
*/
|
|
static isCheckmate(board, color, gameState) {
|
|
// Must be in check for checkmate
|
|
if (!this.isKingInCheck(board, color)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if any legal move exists
|
|
return !this.hasAnyLegalMove(board, color, gameState);
|
|
}
|
|
|
|
/**
|
|
* Check if position is stalemate
|
|
* @param {Board} board - Game board
|
|
* @param {string} color - Player color
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {boolean} True if stalemate
|
|
*/
|
|
static isStalemate(board, color, gameState) {
|
|
// Must NOT be in check for stalemate
|
|
if (this.isKingInCheck(board, color)) {
|
|
return false;
|
|
}
|
|
|
|
// No legal moves available
|
|
return !this.hasAnyLegalMove(board, color, gameState);
|
|
}
|
|
|
|
/**
|
|
* Check if player has any legal move
|
|
* @param {Board} board - Game board
|
|
* @param {string} color - Player color
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {boolean} True if at least one legal move exists
|
|
*/
|
|
static hasAnyLegalMove(board, color, gameState) {
|
|
// Check all pieces of this color
|
|
for (let row = 0; row < 8; row++) {
|
|
for (let col = 0; col < 8; col++) {
|
|
const piece = board.getPiece(row, col);
|
|
|
|
if (piece && piece.color === color) {
|
|
// Get all valid moves for this piece
|
|
const validMoves = piece.getValidMoves(board);
|
|
|
|
// Check if any move is legal (doesn't leave king in check)
|
|
for (const move of validMoves) {
|
|
if (this.isMoveLegal(board, piece, move.row, move.col, gameState)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check special moves for pawns and kings
|
|
if (piece.type === 'pawn' && piece.getEnPassantMoves) {
|
|
const enPassantMoves = piece.getEnPassantMoves(board, gameState);
|
|
for (const move of enPassantMoves) {
|
|
if (this.isMoveLegal(board, piece, move.row, move.col, gameState)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (piece.type === 'king' && piece.getCastlingMoves) {
|
|
const castlingMoves = piece.getCastlingMoves(board, gameState);
|
|
for (const move of castlingMoves) {
|
|
if (this.canCastleToPosition(board, piece, move.col, gameState)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get all legal moves for a piece
|
|
* @param {Board} board - Game board
|
|
* @param {Piece} piece - Piece to check
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {Position[]} Array of legal positions
|
|
*/
|
|
static getLegalMoves(board, piece, gameState) {
|
|
const legalMoves = [];
|
|
|
|
// Get valid moves (piece-specific rules)
|
|
const validMoves = piece.getValidMoves(board);
|
|
|
|
// Filter by check constraint
|
|
for (const move of validMoves) {
|
|
if (this.isMoveLegal(board, piece, move.row, move.col, gameState)) {
|
|
legalMoves.push(move);
|
|
}
|
|
}
|
|
|
|
// Add special moves
|
|
if (piece.type === 'pawn' && piece.getEnPassantMoves) {
|
|
const enPassantMoves = piece.getEnPassantMoves(board, gameState);
|
|
for (const move of enPassantMoves) {
|
|
if (this.isMoveLegal(board, piece, move.row, move.col, gameState)) {
|
|
legalMoves.push(move);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (piece.type === 'king' && piece.getCastlingMoves) {
|
|
const castlingMoves = piece.getCastlingMoves(board, gameState);
|
|
for (const move of castlingMoves) {
|
|
if (this.canCastleToPosition(board, piece, move.col, gameState)) {
|
|
legalMoves.push(move);
|
|
}
|
|
}
|
|
}
|
|
|
|
return legalMoves;
|
|
}
|
|
|
|
/**
|
|
* Check if castling to position is legal
|
|
* @param {Board} board - Game board
|
|
* @param {King} king - King piece
|
|
* @param {number} targetCol - Target column (2 or 6)
|
|
* @param {GameState} gameState - Game state
|
|
* @returns {boolean} True if castling is legal
|
|
*/
|
|
static canCastleToPosition(board, king, targetCol, gameState) {
|
|
// King can't be in check
|
|
if (this.isKingInCheck(board, king.color)) {
|
|
return false;
|
|
}
|
|
|
|
const row = king.position.row;
|
|
const direction = targetCol > king.position.col ? 1 : -1;
|
|
|
|
// King can't pass through check
|
|
for (let col = king.position.col + direction;
|
|
col !== targetCol + direction;
|
|
col += direction) {
|
|
|
|
const simulatedBoard = board.clone();
|
|
simulatedBoard.movePiece(king.position.row, king.position.col, row, col);
|
|
|
|
if (this.isKingInCheck(simulatedBoard, king.color)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check for insufficient material (automatic draw)
|
|
* @param {Board} board - Game board
|
|
* @returns {boolean} True if insufficient material
|
|
*/
|
|
static isInsufficientMaterial(board) {
|
|
const pieces = {
|
|
white: board.getPiecesByColor('white'),
|
|
black: board.getPiecesByColor('black')
|
|
};
|
|
|
|
// King vs King
|
|
if (pieces.white.length === 1 && pieces.black.length === 1) {
|
|
return true;
|
|
}
|
|
|
|
// King and Bishop vs King or King and Knight vs King
|
|
for (const color of ['white', 'black']) {
|
|
if (pieces[color].length === 2) {
|
|
const nonKing = pieces[color].find(p => p.type !== 'king');
|
|
if (nonKing && (nonKing.type === 'bishop' || nonKing.type === 'knight')) {
|
|
const otherColor = color === 'white' ? 'black' : 'white';
|
|
if (pieces[otherColor].length === 1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// King and Bishop vs King and Bishop (same color squares)
|
|
if (pieces.white.length === 2 && pieces.black.length === 2) {
|
|
const whiteBishop = pieces.white.find(p => p.type === 'bishop');
|
|
const blackBishop = pieces.black.find(p => p.type === 'bishop');
|
|
|
|
if (whiteBishop && blackBishop) {
|
|
const whiteSquareColor = (whiteBishop.position.row + whiteBishop.position.col) % 2;
|
|
const blackSquareColor = (blackBishop.position.row + blackBishop.position.col) % 2;
|
|
|
|
if (whiteSquareColor === blackSquareColor) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|