chess-easy
Version:
Chess engine that makes writing chessgame easier than writing a calculator
314 lines • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MovesGenerator = void 0;
const common_1 = require("../types/common");
const utils_1 = require("../utils");
const MoveMaker_1 = require("./MoveMaker");
class MovesGenerator {
constructor(gameState) {
this.startingPawnIndexes = { [common_1.Colors.BLACK]: 6, [common_1.Colors.WHITE]: 1 };
this.movesNext = common_1.Colors.WHITE;
this.kingField = "";
this.gameState = gameState;
}
getEnemyColor(color) {
return color === common_1.Colors.WHITE ? common_1.Colors.BLACK : common_1.Colors.WHITE;
}
getPossiblePawnMoves(row, column, color) {
var _a, _b;
const colorMultiplier = color === common_1.Colors.BLACK ? -1 : 1;
const possibleMoves = [];
const enemyColor = this.getEnemyColor(color);
const nextRow = row + colorMultiplier * 1;
if (nextRow > 7 || nextRow < 0) {
return [];
}
if (!this.gameState[nextRow][column]) {
possibleMoves.push((0, utils_1.indexesToField)(nextRow, column));
}
const rowPlusTwo = row + colorMultiplier * 2;
if (row === this.startingPawnIndexes[color] &&
!this.gameState[nextRow][column] &&
!this.gameState[rowPlusTwo][column]) {
possibleMoves.push((0, utils_1.indexesToField)(rowPlusTwo, column));
}
if (column < 7 &&
((_a = this.gameState[nextRow][column + 1]) === null || _a === void 0 ? void 0 : _a.color) === enemyColor) {
possibleMoves.push((0, utils_1.indexesToField)(nextRow, column + 1));
}
if (column > 0 &&
((_b = this.gameState[nextRow][column - 1]) === null || _b === void 0 ? void 0 : _b.color) === enemyColor) {
possibleMoves.push((0, utils_1.indexesToField)(nextRow, column - 1));
}
if (this.enPassantPossibility !== "-") {
const [enPassantColumn] = this.enPassantPossibility.split("");
const enPassantColumnIndex = (0, utils_1.mapLetterToColumnIndex)(enPassantColumn);
const isPieceColumnNextToEnPassant = Math.abs(enPassantColumnIndex - column) === 1;
const isPieceRowNextToEnPassant = color === common_1.Colors.WHITE ? row === 4 : row === 3;
if (isPieceColumnNextToEnPassant && isPieceRowNextToEnPassant) {
possibleMoves.push(this.enPassantPossibility);
}
}
return possibleMoves;
}
getPossibleFigureMoves(row, column, color, patterns, oneMove = false) {
var _a, _b;
const possibleMoves = [];
const enemyColor = this.getEnemyColor(color);
for (let n in patterns) {
let nextRow = row + patterns[n][0];
let nextColumn = column + patterns[n][1];
while (true) {
if (nextRow >= 0 &&
nextRow <= 7 &&
nextColumn >= 0 &&
nextColumn <= 7) {
if (((_a = this.gameState[nextRow][nextColumn]) === null || _a === void 0 ? void 0 : _a.color) === enemyColor) {
possibleMoves.push((0, utils_1.indexesToField)(nextRow, nextColumn));
break;
}
else if (((_b = this.gameState[nextRow][nextColumn]) === null || _b === void 0 ? void 0 : _b.color) === color) {
break;
}
else {
possibleMoves.push((0, utils_1.indexesToField)(nextRow, nextColumn));
}
}
else {
break;
}
nextRow += patterns[n][0];
nextColumn += patterns[n][1];
if (oneMove) {
break;
}
}
}
return possibleMoves;
}
getPossibleMoves(row, column, field) {
if (!field)
return [];
switch (field.piece) {
case common_1.ChessPieces.PAWN: {
return this.getPossiblePawnMoves(row, column, field.color);
}
case common_1.ChessPieces.KING: {
if (field.color === this.movesNext) {
this.kingField = (0, utils_1.indexesToField)(row, column);
}
return this.getPossibleFigureMoves(row, column, field.color, [
[0, 1],
[1, 0],
[1, 1],
[-1, -1],
[0, -1],
[-1, 0],
[-1, 1],
[1, -1],
], true);
}
case common_1.ChessPieces.QUEEN: {
return this.getPossibleFigureMoves(row, column, field.color, [
[1, 1],
[1, -1],
[-1, 1],
[-1, -1],
[0, 1],
[1, 0],
[-1, 0],
[0, -1],
]);
}
case common_1.ChessPieces.BISHOP: {
return this.getPossibleFigureMoves(row, column, field.color, [
[1, 1],
[1, -1],
[-1, 1],
[-1, -1],
]);
}
case common_1.ChessPieces.ROOK: {
return this.getPossibleFigureMoves(row, column, field.color, [
[0, 1],
[1, 0],
[-1, 0],
[0, -1],
]);
}
case common_1.ChessPieces.KNIGHT: {
return this.getPossibleFigureMoves(row, column, field.color, [
[2, 1],
[2, -1],
[-2, 1],
[-2, -1],
[1, 2],
[-1, 2],
[1, -2],
[-1, -2],
], true);
}
default: {
return [];
}
}
}
areFieldsBetweenPiecesInRowEmptyAndNotAttacked(row, column1, column2, enemyMoves) {
const fieldsToCheck = this.gameState[row].slice(column1, column2);
return fieldsToCheck.every((field, index) => !field &&
!this.isFieldAttacked((0, utils_1.indexesToField)(row, index + column1), enemyMoves));
}
addCastlingMoves(allPossibleMoves, movesNext, possibleEnemyMoves) {
const movesToUpdate = { ...allPossibleMoves };
if (this.castlingAvailability.length) {
const rowToCheck = movesNext === common_1.Colors.WHITE ? 0 : 7;
const isQueenSidePossible = this.castlingAvailability.includes(movesNext === common_1.Colors.WHITE ? "Q" : "q") &&
this.areFieldsBetweenPiecesInRowEmptyAndNotAttacked(rowToCheck, 1, 4, possibleEnemyMoves);
const isKingSidePossible = this.castlingAvailability.includes(movesNext === common_1.Colors.WHITE ? "K" : "k") &&
this.areFieldsBetweenPiecesInRowEmptyAndNotAttacked(rowToCheck, 5, 7, possibleEnemyMoves);
const rookColumns = [
...(isQueenSidePossible ? ["a"] : []),
...(isKingSidePossible ? ["h"] : []),
];
const kingField = `e${rowToCheck + 1}`;
rookColumns.forEach(column => {
const rookField = `${column}${rowToCheck + 1}`;
movesToUpdate[rookField] = [
...(movesToUpdate[rookField] || []),
kingField,
];
movesToUpdate[kingField] = [
...(movesToUpdate[kingField] || []),
rookField,
];
});
}
return movesToUpdate;
}
getAllPossibleBasicMoves(moves) {
const allPossibleMoves = {};
this.gameState.forEach((row, row_index) => {
row.forEach((field, column_index) => {
const piecePosition = `${(0, utils_1.mapColumnIndexToLetter)(column_index)}${row_index + 1}`;
if ((field === null || field === void 0 ? void 0 : field.color) !== moves) {
allPossibleMoves[piecePosition] = [];
return;
}
allPossibleMoves[piecePosition] = this.getPossibleMoves(row_index, column_index, field);
});
});
return allPossibleMoves;
}
isFieldAttacked(field, enemyMoves) {
return Object.values(enemyMoves).some(moves => moves.includes(field));
}
filterIllegalInCheckMoves(allMoves) {
const allFields = Object.keys(allMoves);
const moveMaker = new MoveMaker_1.MoveMaker(this.gameState, this.enPassantPossibility, this.movesNext, this.castlingAvailability);
allFields.forEach(field => {
const moves = allMoves[field];
const [column_from, row_from] = (0, utils_1.fieldToIndexes)(field);
const newMoves = moves.filter(move => {
var _a;
const newKingField = ((_a = this.gameState[row_from][column_from]) === null || _a === void 0 ? void 0 : _a.piece) === common_1.ChessPieces.KING
? move
: this.kingField;
const { gameState } = moveMaker.move(field, move);
const initialGameState = this.gameState;
this.gameState = gameState;
const currentEnemyMoves = this.getAllPossibleBasicMoves(this.getEnemyColor(this.movesNext));
this.gameState = initialGameState;
return !this.isFieldAttacked(newKingField, currentEnemyMoves);
});
allMoves[field] = newMoves;
});
return allMoves;
}
isInsufficientMaterial() {
const flattenGameState = this.gameState.flat();
const starterGameSituation = {
black: {
bishops: 0,
knights: 0,
otherPieces: 0,
},
white: {
bishops: 0,
knights: 0,
otherPieces: 0,
},
};
const gameSituation = flattenGameState.reduce((acc, curr) => {
if (curr) {
const { color, piece } = curr;
if (piece === common_1.ChessPieces.KING) {
return acc;
}
const colorToUpdate = color === common_1.Colors.BLACK ? "black" : "white";
if (piece === common_1.ChessPieces.KNIGHT) {
return {
...acc,
[colorToUpdate]: {
...acc[colorToUpdate],
knights: acc[colorToUpdate].knights + 1,
},
};
}
else if (piece === common_1.ChessPieces.BISHOP) {
return {
...acc,
[colorToUpdate]: {
...acc[colorToUpdate],
bishops: acc[colorToUpdate].bishops + 1,
},
};
}
else {
return {
...acc,
[colorToUpdate]: {
...acc[colorToUpdate],
otherPieces: acc[colorToUpdate].otherPieces + 1,
},
};
}
}
return acc;
}, starterGameSituation);
const areOtherPiecesPresent = gameSituation.black.otherPieces > 0 ||
gameSituation.white.otherPieces > 0;
if (areOtherPiecesPresent) {
return false;
}
const blackMinorPieces = gameSituation.black.bishops + gameSituation.black.knights;
const whiteMinorPieces = gameSituation.white.bishops + gameSituation.white.knights;
if (blackMinorPieces <= 1 && whiteMinorPieces <= 1) {
return true;
}
return ((gameSituation.white.knights === 2 && blackMinorPieces === 0) ||
(gameSituation.black.knights === 2 && whiteMinorPieces === 0));
}
getAllPossibleMoves(movesNext, castlingAvailability, enPassantPossibility) {
this.enPassantPossibility = enPassantPossibility;
this.castlingAvailability = castlingAvailability;
this.movesNext = movesNext;
const basicMoves = this.getAllPossibleBasicMoves(movesNext);
const enemyBasicMoves = this.getAllPossibleBasicMoves(this.getEnemyColor(movesNext));
const movesWithCastling = this.addCastlingMoves(basicMoves, movesNext, enemyBasicMoves);
const isCheck = this.isFieldAttacked(this.kingField, enemyBasicMoves);
const allMoves = this.filterIllegalInCheckMoves(movesWithCastling);
const canPlayerMove = Object.values(allMoves).some(moves => moves.length);
const isCheckmate = isCheck && !canPlayerMove;
const isStalemate = !isCheck && !canPlayerMove;
const isInsufficientMaterial = this.isInsufficientMaterial();
return {
allMoves: this.filterIllegalInCheckMoves(movesWithCastling),
isCheck,
isCheckmate,
isStalemate,
isInsufficientMaterial,
};
}
}
exports.MovesGenerator = MovesGenerator;
//# sourceMappingURL=MovesGenerator.js.map