diff --git a/src/controllers/GameController.ts b/src/controllers/GameController.ts index 0504d12..498e7b4 100644 --- a/src/controllers/GameController.ts +++ b/src/controllers/GameController.ts @@ -36,7 +36,7 @@ class GameController { if (isLegalMove) { this.playSound(this.activeSquare, square); - this.gameEngine.movePiece(this.activeSquare, square); + this.gameEngine.movePiece(this.activeSquare, square, false); this.boardView.render(this.gameEngine.board); this.changePlayer(); } diff --git a/src/services/game-logic/Board.ts b/src/services/game-logic/Board.ts index cae27f1..0b2622a 100644 --- a/src/services/game-logic/Board.ts +++ b/src/services/game-logic/Board.ts @@ -11,8 +11,8 @@ import { Queen } from './pieces/Queen'; class Board { public static BOARD_SIZE = 8; - private state: Array>; private movesHistory: [Piece, Square][] = []; + public state: Array>; constructor() { this.state = new Array(Board.BOARD_SIZE); @@ -27,9 +27,17 @@ class Board { return this.state[square.row][square.column]; } - public movePiece(location: Square, destination: Square): void { + public checkAllSquares = (callback: any) => { + this.state.forEach((row) => { + row.forEach((square) => { + callback(square); + }); + }); + }; + + public movePiece(location: Square, destination: Square, potentialMove: boolean): void { const piece = this.getPiece(location); - if (piece) this.movesHistory.push([piece, destination]); + if (!potentialMove && piece) this.movesHistory.push([piece, destination]); this.state[location.row][location.column]?.move(destination); this.state[destination.row][destination.column] = this.state[location.row][location.column]; diff --git a/src/services/game-logic/GameEngine.ts b/src/services/game-logic/GameEngine.ts index 8599002..2615695 100644 --- a/src/services/game-logic/GameEngine.ts +++ b/src/services/game-logic/GameEngine.ts @@ -1,6 +1,7 @@ -import { PieceNames, Constants } from '../../enums'; +import { PieceNames, Constants, Colors } from '../../enums'; import { Square } from '../../models/Square'; import { Board } from './Board'; +import { King } from './pieces/King'; import { Piece } from './pieces/Piece'; class GameEngine { @@ -12,13 +13,61 @@ class GameEngine { getLegalMoves = (square: Square): Square[] => { const piece = this.board.getPiece(square); - let legalMoves = piece?.getPossibleMoves(this.board).filter(this.isOnBoard) ?? []; + let legalMoves = piece?.getPossibleMoves(this.board) ?? []; if (piece?.name === PieceNames.KING) { legalMoves = legalMoves.filter( (move) => !this.isCastling(move, piece) || this.isCastlingLegal(move, piece) ); } - return legalMoves.filter((destination) => !this.isOccupiedBySameColor(destination, piece)); + return legalMoves + .filter((destination) => !this.isOccupiedBySameColor(destination, piece)) + .filter((move) => this.moveNotResultWithCheck(move, piece, square)); + }; + + moveNotResultWithCheck(move: Square, piece: Piece | null, square: Square): boolean { + const potentialMove = new Square(move.row, move.column); + const potentialPiece = this.board.getPiece(potentialMove); + + this.movePiece(square, potentialMove, true); + piece!.hasMoved = true; + const isMovePossible = !this.isCheck(piece); + + this.movePiece(potentialMove, square, true); + piece!.hasMoved = false; + + if (potentialPiece) + this.board.state[potentialPiece.position.row][potentialPiece.position.column] = potentialPiece; + + return isMovePossible; + } + + private isCheck(piece: Piece | null): boolean { + const color = piece?.color === Colors.BLACK ? Colors.WHITE : Colors.BLACK; + let currentPlayerKing = this.getKingForCheck(piece)?.position; + const isChecked = this.isKingUnderCheck(color, currentPlayerKing); + return isChecked; + } + + isKingUnderCheck(color: Colors, piecePosition: Square | undefined): boolean { + let checked = false; + this.board.checkAllSquares((square: Piece) => { + if (square && square.color === color) { + checked = square + .getPossibleMoves(this.board) + .some((move) => move.row === piecePosition?.row && move.column === piecePosition.column); + } + }); + return checked; + } + + getKingForCheck = (piece: Piece | null): King | null => { + let king = null; + this.board.checkAllSquares((square: Piece) => { + if (square && square?.color === piece?.color && square.name === PieceNames.KING) { + king = square; + } + }); + return king; }; public runSpecialRoutines(location: Square, destination: Square): void { @@ -31,11 +80,11 @@ class GameEngine { } } - public movePiece(location: Square, destination: Square): void { + public movePiece(location: Square, destination: Square, potentialMove: boolean): void { const piece = this.board.getPiece(location); if (piece) { this.runSpecialRoutines(piece?.position, destination); - this.board.movePiece(location, destination); + this.board.movePiece(location, destination, potentialMove); } } @@ -52,12 +101,14 @@ class GameEngine { if (destination.column - location.column === Constants.KINGSIDE_CASTLING) { this.board.movePiece( { row: location.row, column: Constants.KINGSIDE_ROOK_COLUMN }, - { row: location.row, column: Constants.KINGSIDE_ROOK_DESTINATION_COLUMN } + { row: location.row, column: Constants.KINGSIDE_ROOK_DESTINATION_COLUMN }, + false ); } else if (destination.column - location.column === Constants.QUEENSIDE_CASTLING) { this.board.movePiece( { row: location.row, column: Constants.QUEENSIDE_ROOK_COLUMN }, - { row: location.row, column: Constants.QUEENSIDE_ROOK_DESTINATION_COLUMN } + { row: location.row, column: Constants.QUEENSIDE_ROOK_DESTINATION_COLUMN }, + false ); } } @@ -87,11 +138,6 @@ class GameEngine { return this.getLegalMoves(from).some(({ row, column }) => row === to.row && column === to.column); }; // might be useful for 'check' - private isOnBoard = (square: Square): boolean => { - // here or in knight.ts (for now knights can get outside the board) - return square.row >= 0 && square.row < 8 && square.column >= 0 && square.column < 8; - }; - private isOccupiedBySameColor = (square: Square, piece: Piece | null): boolean => this.board.getPiece(square)?.color === piece?.color; } diff --git a/src/services/game-logic/pieces/King.ts b/src/services/game-logic/pieces/King.ts index e1b1078..2cbc6d9 100644 --- a/src/services/game-logic/pieces/King.ts +++ b/src/services/game-logic/pieces/King.ts @@ -17,13 +17,16 @@ class King extends Piece { [1, -1] ]; if (!this.hasMoved) positions = [...positions, [0, 2], [0, -2]]; - const [row, column] = positions; const possibleMoves = positions.map(([row, column]) => ({ row: this.position.row + row, column: this.position.column + column })); - return possibleMoves; + + const kingPossibleMovesOnBoard = possibleMoves.filter( + (move) => move.row >= 0 && move.row < 8 && move.column >= 0 && move.column < 8 + ); + return kingPossibleMovesOnBoard; } } diff --git a/src/services/game-logic/pieces/Knight.ts b/src/services/game-logic/pieces/Knight.ts index 07631e1..f049a6c 100644 --- a/src/services/game-logic/pieces/Knight.ts +++ b/src/services/game-logic/pieces/Knight.ts @@ -16,7 +16,8 @@ class Knight extends Piece { destination.push(new Square(row + possibleRowMoves[i], column + possibleColumnMoves[i])); } - return destination; + const knightPossibleMoves = destination.filter((d) => d.row >= 0 && d.row < 8 && d.column >= 0 && d.column < 8); + return knightPossibleMoves; } } diff --git a/test/services/game-logic/Board.test.ts b/test/services/game-logic/Board.test.ts index 373f968..989f2d1 100644 --- a/test/services/game-logic/Board.test.ts +++ b/test/services/game-logic/Board.test.ts @@ -18,3 +18,14 @@ describe('Testing Board.setup()', () => { }); }); }); + +test(`should run function on all squares`, () => { + let arr = []; + const board = new Board(); + + board.checkAllSquares((square) => { + arr.push(square); + }); + + expect(arr.length).toBe(32); +}); diff --git a/test/services/game-logic/GameEngine.test.ts b/test/services/game-logic/GameEngine.test.ts index 6959333..e759e74 100644 --- a/test/services/game-logic/GameEngine.test.ts +++ b/test/services/game-logic/GameEngine.test.ts @@ -1,5 +1,10 @@ import { Square } from '../../../src/models/Square'; import { GameEngine } from '../../../src/services/game-logic/GameEngine'; +import { Colors } from '../../../src/enums'; +import { Board } from '../../../src/services/game-logic/Board'; +import { King } from '../../../src/services/game-logic/pieces/King'; + +const gameEngine = new GameEngine(); describe('Testing GameEngine.getLegalMoves() while playing Pawn', () => { test(`Pawn shall return 2 capturing moves `, () => { @@ -16,6 +21,7 @@ describe('Testing GameEngine.getLegalMoves() while playing Pawn', () => { // Test expect(legalMoves).toEqual(expected); }); + test(`Extra En Passat move while playing white`, () => { // Setup const gameEngine = new GameEngine(); @@ -32,6 +38,7 @@ describe('Testing GameEngine.getLegalMoves() while playing Pawn', () => { // Test expect(legalMoves).toEqual(expected); }); + test(`White En Passat removes Black Pawn`, () => { // Setup const gameEngine = new GameEngine(); @@ -49,3 +56,32 @@ describe('Testing GameEngine.getLegalMoves() while playing Pawn', () => { expect(removedPiece).toBe(null); }); }); + +test(`should target king`, () => { + const piece = gameEngine.board.getPiece(new Square(1, 4)); + const king = gameEngine.getKingForCheck(piece); + const expected = new Square(0, 4); + + expect(king instanceof King).toBe(true); + expect(king.position).toStrictEqual(expected); +}); + +test(`should return true when check`, () => { + const color = Colors.BLACK; + const piece = new King({ column: 7, row: 3 }, Colors.WHITE); + + const isKingChecked = gameEngine.isKingUnderCheck(color, piece.position); + + expect(isKingChecked).toBe(true); +}); + +test(`should verify if oponent move result with check`, () => { + const board = new Board(); + const square = { row: 6, column: 3 }; + const move = new Square(4, 3); + const piece = board.getPiece(square); + + const notCheck = gameEngine.moveNotResultWithCheck(move, piece, square); + + expect(notCheck).toBe(true); +}); diff --git a/test/services/game-logic/pieces/Knight.test.ts b/test/services/game-logic/pieces/Knight.test.ts index 98f27ae..2f895c0 100644 --- a/test/services/game-logic/pieces/Knight.test.ts +++ b/test/services/game-logic/pieces/Knight.test.ts @@ -3,35 +3,12 @@ import { Square } from '../../../../src/models/Square'; import { Knight } from '../../../../src/services/game-logic/pieces/Knight'; test.each([ - [ - Colors.WHITE, - 7, - 5, - [ - new Square(9, 6), - new Square(8, 7), - new Square(6, 7), - new Square(5, 6), - new Square(5, 4), - new Square(6, 3), - new Square(8, 3), - new Square(9, 4) - ] - ], + [Colors.WHITE, 7, 5, [new Square(6, 7), new Square(5, 6), new Square(5, 4), new Square(6, 3)]], [ Colors.BLACK, 3, 6, - [ - new Square(5, 7), - new Square(4, 8), - new Square(2, 8), - new Square(1, 7), - new Square(1, 5), - new Square(2, 4), - new Square(4, 4), - new Square(5, 5) - ] + [new Square(5, 7), new Square(1, 7), new Square(1, 5), new Square(2, 4), new Square(4, 4), new Square(5, 5)] ] ])( 'should return knights possible moves',