From ad6874ea6f1b1ef4fae07a06fc5e71fdbccc14a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asia=20Zio=C5=82a?= Date: Fri, 12 Feb 2021 18:27:47 +0100 Subject: [PATCH 1/3] [#27] check --- src/services/game-logic/Board.ts | 10 +++- src/services/game-logic/GameEngine.ts | 58 ++++++++++++++++--- src/services/game-logic/pieces/King.ts | 6 +- src/services/game-logic/pieces/Knight.ts | 3 +- .../services/game-logic/pieces/Knight.test.ts | 27 +-------- 5 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/services/game-logic/Board.ts b/src/services/game-logic/Board.ts index cae27f1..3b68c7f 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,6 +27,14 @@ class Board { return this.state[square.row][square.column]; } + public checkAllSquares = (f: any) => { + this.state.forEach((row) => { + row.forEach((square) => { + f(square); + }); + }); + }; + public movePiece(location: Square, destination: Square): void { const piece = this.getPiece(location); if (piece) this.movesHistory.push([piece, destination]); diff --git a/src/services/game-logic/GameEngine.ts b/src/services/game-logic/GameEngine.ts index 8599002..75564a3 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,59 @@ 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)); + }; + + private moveNotResultWithCheck(move: Square, piece: Piece | null, square: Square) { + const potentialMove: Square = { row: move.row, column: move.column }; + const potentialPiece = this.board.getPiece(potentialMove); + + this.movePiece(square, potentialMove); + piece!.hasMoved = true; + const isMovePossible = !this.isCheck(piece); + + this.movePiece(potentialMove, square); + 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: Square | undefined = this.getKingForCheck(piece)?.position; + const isChecked = this.findOponentLegalMoves(color, currentPlayerKing); + return isChecked; + } + + findOponentLegalMoves(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 { @@ -87,11 +134,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..a238742 100644 --- a/src/services/game-logic/pieces/King.ts +++ b/src/services/game-logic/pieces/King.ts @@ -23,7 +23,11 @@ class King extends Piece { 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/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', From 0c724942dec1e71a45d16c99aaa328bc360520a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asia=20Zio=C5=82a?= Date: Sat, 13 Feb 2021 13:10:14 +0100 Subject: [PATCH 2/3] check - tests --- src/services/game-logic/GameEngine.ts | 7 ++-- test/services/game-logic/Board.test.ts | 11 +++++++ test/services/game-logic/GameEngine.test.ts | 36 +++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/services/game-logic/GameEngine.ts b/src/services/game-logic/GameEngine.ts index 75564a3..f2be7ae 100644 --- a/src/services/game-logic/GameEngine.ts +++ b/src/services/game-logic/GameEngine.ts @@ -24,12 +24,11 @@ class GameEngine { .filter((move) => this.moveNotResultWithCheck(move, piece, square)); }; - private moveNotResultWithCheck(move: Square, piece: Piece | null, square: Square) { + moveNotResultWithCheck(move: Square, piece: Piece | null, square: Square) { const potentialMove: Square = { row: move.row, column: move.column }; const potentialPiece = this.board.getPiece(potentialMove); this.movePiece(square, potentialMove); - piece!.hasMoved = true; const isMovePossible = !this.isCheck(piece); this.movePiece(potentialMove, square); @@ -44,11 +43,11 @@ class GameEngine { private isCheck(piece: Piece | null): Boolean { const color = piece?.color === Colors.BLACK ? Colors.WHITE : Colors.BLACK; let currentPlayerKing: Square | undefined = this.getKingForCheck(piece)?.position; - const isChecked = this.findOponentLegalMoves(color, currentPlayerKing); + const isChecked = this.isKingUnderCheck(color, currentPlayerKing); return isChecked; } - findOponentLegalMoves(color: Colors, piecePosition: Square | undefined): boolean { + isKingUnderCheck(color: Colors, piecePosition: Square | undefined): boolean { let checked = false; this.board.checkAllSquares((square: Piece) => { if (square && square.color === color) { 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); +}); From f51c53b65f2f7610b57423a7a61481279a564242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asia=20Zio=C5=82a?= Date: Sun, 14 Feb 2021 10:57:05 +0100 Subject: [PATCH 3/3] review fixes --- src/controllers/GameController.ts | 2 +- src/services/game-logic/Board.ts | 8 ++++---- src/services/game-logic/GameEngine.ts | 27 +++++++++++++++----------- src/services/game-logic/pieces/King.ts | 1 - 4 files changed, 21 insertions(+), 17 deletions(-) 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 3b68c7f..0b2622a 100644 --- a/src/services/game-logic/Board.ts +++ b/src/services/game-logic/Board.ts @@ -27,17 +27,17 @@ class Board { return this.state[square.row][square.column]; } - public checkAllSquares = (f: any) => { + public checkAllSquares = (callback: any) => { this.state.forEach((row) => { row.forEach((square) => { - f(square); + callback(square); }); }); }; - public movePiece(location: Square, destination: Square): void { + 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 f2be7ae..2615695 100644 --- a/src/services/game-logic/GameEngine.ts +++ b/src/services/game-logic/GameEngine.ts @@ -24,14 +24,15 @@ class GameEngine { .filter((move) => this.moveNotResultWithCheck(move, piece, square)); }; - moveNotResultWithCheck(move: Square, piece: Piece | null, square: Square) { - const potentialMove: Square = { row: move.row, column: move.column }; + 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); + this.movePiece(square, potentialMove, true); + piece!.hasMoved = true; const isMovePossible = !this.isCheck(piece); - this.movePiece(potentialMove, square); + this.movePiece(potentialMove, square, true); piece!.hasMoved = false; if (potentialPiece) @@ -40,9 +41,9 @@ class GameEngine { return isMovePossible; } - private isCheck(piece: Piece | null): Boolean { + private isCheck(piece: Piece | null): boolean { const color = piece?.color === Colors.BLACK ? Colors.WHITE : Colors.BLACK; - let currentPlayerKing: Square | undefined = this.getKingForCheck(piece)?.position; + let currentPlayerKing = this.getKingForCheck(piece)?.position; const isChecked = this.isKingUnderCheck(color, currentPlayerKing); return isChecked; } @@ -62,7 +63,9 @@ class GameEngine { 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; + if (square && square?.color === piece?.color && square.name === PieceNames.KING) { + king = square; + } }); return king; }; @@ -77,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); } } @@ -98,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 ); } } diff --git a/src/services/game-logic/pieces/King.ts b/src/services/game-logic/pieces/King.ts index a238742..2cbc6d9 100644 --- a/src/services/game-logic/pieces/King.ts +++ b/src/services/game-logic/pieces/King.ts @@ -17,7 +17,6 @@ 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,