Compare commits
1 Commits
b44f071630
...
82479fb8c7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82479fb8c7 |
@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
- name: Archive test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: coverage/
|
||||
@ -153,7 +153,7 @@ jobs:
|
||||
cat quality-report.md
|
||||
|
||||
- name: Upload quality report
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: quality-report
|
||||
path: quality-report.md
|
||||
|
||||
@ -127,7 +127,7 @@ jobs:
|
||||
EOF
|
||||
|
||||
- name: Upload release artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-artifacts
|
||||
path: |
|
||||
|
||||
@ -63,12 +63,9 @@ export class Board {
|
||||
* @param {number} row - Row index (0-7)
|
||||
* @param {number} col - Column index (0-7)
|
||||
* @returns {Piece|null} Piece or null if empty
|
||||
* @throws {Error} If position is out of bounds
|
||||
*/
|
||||
getPiece(row, col) {
|
||||
if (!this.isInBounds(row, col)) {
|
||||
throw new Error(`Position (${row}, ${col}) is out of bounds`);
|
||||
}
|
||||
if (!this.isInBounds(row, col)) return null;
|
||||
return this.grid[row][col];
|
||||
}
|
||||
|
||||
@ -94,11 +91,11 @@ export class Board {
|
||||
* @param {number} fromCol - Source column
|
||||
* @param {number} toRow - Destination row
|
||||
* @param {number} toCol - Destination column
|
||||
* @returns {Object} Result with captured piece
|
||||
* @returns {Piece|null} Captured piece if any
|
||||
*/
|
||||
movePiece(fromRow, fromCol, toRow, toCol) {
|
||||
const piece = this.getPiece(fromRow, fromCol);
|
||||
if (!piece) return { captured: null };
|
||||
if (!piece) return null;
|
||||
|
||||
const captured = this.getPiece(toRow, toCol);
|
||||
|
||||
@ -109,7 +106,7 @@ export class Board {
|
||||
// Mark piece as moved
|
||||
piece.hasMoved = true;
|
||||
|
||||
return { captured };
|
||||
return captured;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,8 +184,7 @@ export class Board {
|
||||
/**
|
||||
* Find king position for given color
|
||||
* @param {string} color - 'white' or 'black'
|
||||
* @returns {Position} King position
|
||||
* @throws {Error} If king not found
|
||||
* @returns {Position|null} King position or null
|
||||
*/
|
||||
findKing(color) {
|
||||
for (let row = 0; row < 8; row++) {
|
||||
@ -199,7 +195,7 @@ export class Board {
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`${color} king not found on board`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,26 +217,4 @@ export class Board {
|
||||
|
||||
return pieces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pieces on the board
|
||||
* @param {string} color - Optional color filter
|
||||
* @returns {Array<Piece>} Array of pieces
|
||||
*/
|
||||
getAllPieces(color = null) {
|
||||
if (color) {
|
||||
return this.getPiecesByColor(color);
|
||||
}
|
||||
|
||||
const pieces = [];
|
||||
for (let row = 0; row < 8; row++) {
|
||||
for (let col = 0; col < 8; col++) {
|
||||
const piece = this.grid[row][col];
|
||||
if (piece) {
|
||||
pieces.push(piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,11 +15,9 @@ export class King extends Piece {
|
||||
* 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) {
|
||||
getValidMoves(board) {
|
||||
const moves = [];
|
||||
|
||||
// All 8 directions, but only one square
|
||||
@ -37,112 +35,19 @@ export class King extends Piece {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const targetPiece = board.getPiece(targetRow, targetCol);
|
||||
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;
|
||||
// Can move to empty square or capture opponent piece
|
||||
if (!targetPiece || targetPiece.color !== this.color) {
|
||||
moves.push({ row: targetRow, col: targetCol });
|
||||
}
|
||||
}
|
||||
|
||||
// Add castling moves if gameState provided
|
||||
if (gameState) {
|
||||
const castlingMoves = this.getCastlingMoves(board, gameState);
|
||||
moves.push(...castlingMoves);
|
||||
}
|
||||
// Castling is handled in SpecialMoves.js
|
||||
|
||||
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
|
||||
@ -159,58 +64,38 @@ export class King extends Piece {
|
||||
|
||||
const row = this.position.row;
|
||||
|
||||
// Cannot castle if currently in check
|
||||
if (this.isSquareAttacked(board, this.position.row, this.position.col)) {
|
||||
return moves;
|
||||
}
|
||||
// Kingside castling (king to g-file)
|
||||
const kingsideRook = board.getPiece(row, 7);
|
||||
if (kingsideRook &&
|
||||
kingsideRook.type === 'rook' &&
|
||||
kingsideRook.color === this.color &&
|
||||
!kingsideRook.hasMoved) {
|
||||
|
||||
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' });
|
||||
}
|
||||
}
|
||||
// Check if squares between king and rook are empty
|
||||
if (this.isEmpty(board, row, 5) &&
|
||||
this.isEmpty(board, row, 6)) {
|
||||
moves.push({ row, col: 6 }); // King moves to g-file
|
||||
}
|
||||
} 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) {
|
||||
// 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' });
|
||||
}
|
||||
}
|
||||
// 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)) {
|
||||
moves.push({ row, col: 2 }); // King moves to c-file
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip if out of bounds
|
||||
}
|
||||
|
||||
// Additional validation (not in check, doesn't pass through check)
|
||||
// is handled in MoveValidator.js
|
||||
|
||||
return moves;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,24 +14,18 @@ export class Pawn extends Piece {
|
||||
/**
|
||||
* Get valid moves for pawn
|
||||
* @param {Board} board - Game board
|
||||
* @param {GameState} gameState - Optional game state for en passant
|
||||
* @returns {Position[]} Array of valid positions
|
||||
*/
|
||||
getValidMoves(board, gameState = null) {
|
||||
getValidMoves(board) {
|
||||
const moves = [];
|
||||
const direction = this.color === 'white' ? -1 : 1;
|
||||
const startRow = this.color === 'white' ? 6 : 1;
|
||||
const promotionRank = this.color === 'white' ? 0 : 7;
|
||||
|
||||
// Forward one square
|
||||
const oneForward = this.position.row + direction;
|
||||
if (this.isInBounds(oneForward, this.position.col) &&
|
||||
this.isEmpty(board, oneForward, this.position.col)) {
|
||||
const move = { row: oneForward, col: this.position.col };
|
||||
if (oneForward === promotionRank) {
|
||||
move.promotion = true;
|
||||
}
|
||||
moves.push(move);
|
||||
moves.push({ row: oneForward, col: this.position.col });
|
||||
|
||||
// Forward two squares from starting position
|
||||
if (this.position.row === startRow) {
|
||||
@ -50,22 +44,12 @@ export class Pawn extends Piece {
|
||||
|
||||
if (this.isInBounds(captureRow, captureCol)) {
|
||||
if (this.hasEnemyPiece(board, captureRow, captureCol)) {
|
||||
const move = { row: captureRow, col: captureCol };
|
||||
if (captureRow === promotionRank) {
|
||||
move.promotion = true;
|
||||
}
|
||||
moves.push(move);
|
||||
moves.push({ row: captureRow, col: captureCol });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// En passant
|
||||
if (gameState && gameState.lastMove) {
|
||||
const enPassantMoves = this.getEnPassantMoves(board, gameState);
|
||||
for (const enPassantMove of enPassantMoves) {
|
||||
moves.push({ ...enPassantMove, enPassant: true });
|
||||
}
|
||||
}
|
||||
// En passant is handled in SpecialMoves.js
|
||||
|
||||
return moves;
|
||||
}
|
||||
@ -111,7 +95,7 @@ export class Pawn extends Piece {
|
||||
adjacentPiece.color !== this.color) {
|
||||
|
||||
// Check if this pawn just moved two squares
|
||||
const lastMove = gameState.lastMove || (gameState.getLastMove && gameState.getLastMove());
|
||||
const lastMove = gameState.getLastMove();
|
||||
if (lastMove &&
|
||||
lastMove.piece === adjacentPiece &&
|
||||
Math.abs(lastMove.to.row - lastMove.from.row) === 2) {
|
||||
|
||||
@ -13,7 +13,6 @@ export class Piece {
|
||||
this.position = position;
|
||||
this.type = null; // Set by subclasses
|
||||
this.hasMoved = false;
|
||||
this.value = 0; // Set by subclasses
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -9,7 +9,6 @@ export class Queen extends Piece {
|
||||
constructor(color, position) {
|
||||
super(color, position);
|
||||
this.type = 'queen';
|
||||
this.value = 9;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -11,14 +11,6 @@ export class Rook extends Piece {
|
||||
this.type = 'rook';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if rook can castle
|
||||
* @returns {boolean} True if not moved
|
||||
*/
|
||||
canCastle() {
|
||||
return !this.hasMoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid moves for rook
|
||||
* Rook moves horizontally or vertically any number of squares
|
||||
|
||||
@ -9,7 +9,6 @@ describe('Board', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
});
|
||||
|
||||
describe('Initialization', () => {
|
||||
|
||||
@ -245,8 +245,7 @@ describe('Bishop', () => {
|
||||
|
||||
describe('Initial Position', () => {
|
||||
test('bishops on initial board have no moves', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
board = new Board(); // Reset to initial position
|
||||
|
||||
const whiteBishop1 = board.getPiece(7, 2);
|
||||
const whiteBishop2 = board.getPiece(7, 5);
|
||||
@ -261,7 +260,6 @@ describe('Bishop', () => {
|
||||
|
||||
test('bishop can move after pawn advances', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
|
||||
// Move pawn to open diagonal
|
||||
board.movePiece(6, 3, 4, 3); // d2 to d4
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
* King piece comprehensive tests - includes castling, check evasion, and movement restrictions
|
||||
*/
|
||||
|
||||
import { King } from '../../../js/pieces/King.js';
|
||||
|
||||
@ -234,8 +234,7 @@ describe('Knight', () => {
|
||||
});
|
||||
|
||||
test('knight starting positions from initial board', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
board = new Board(); // Reset to initial position
|
||||
|
||||
const whiteKnight1 = board.getPiece(7, 1);
|
||||
const whiteKnight2 = board.getPiece(7, 6);
|
||||
|
||||
@ -230,7 +230,6 @@ describe('Queen', () => {
|
||||
describe('Initial Position', () => {
|
||||
test('queens on initial board have no moves', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
|
||||
const whiteQueen = board.getPiece(7, 3);
|
||||
const blackQueen = board.getPiece(0, 3);
|
||||
@ -245,7 +244,6 @@ describe('Queen', () => {
|
||||
|
||||
test('queen mobility increases as game progresses', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
|
||||
const whiteQueen = board.getPiece(7, 3);
|
||||
const initialMoves = whiteQueen.getValidMoves(board);
|
||||
|
||||
@ -219,7 +219,6 @@ describe('Rook', () => {
|
||||
describe('Initial Position', () => {
|
||||
test('rooks on initial board have no moves', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
|
||||
const whiteRook1 = board.getPiece(7, 0);
|
||||
const whiteRook2 = board.getPiece(7, 7);
|
||||
@ -234,7 +233,6 @@ describe('Rook', () => {
|
||||
|
||||
test('rook can move after pieces clear', () => {
|
||||
board = new Board();
|
||||
board.setupInitialPosition();
|
||||
|
||||
// Remove knight to open path
|
||||
board.setPiece(7, 1, null);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user