kokopu
Version:
A JavaScript/TypeScript library implementing the chess game rules and providing tools to read/write the standard chess file formats.
665 lines • 30.5 kB
JavaScript
"use strict";
/*!
* -------------------------------------------------------------------------- *
* *
* Kokopu - A JavaScript/TypeScript chess library. *
* <https://www.npmjs.com/package/kokopu> *
* Copyright (C) 2018-2026 Yoann Le Montagner <yo35 -at- melix.net> *
* *
* Kokopu is free software: you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* as published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* Kokopu is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General *
* Public License along with this program. If not, see *
* <http://www.gnu.org/licenses/>. *
* *
* -------------------------------------------------------------------------- */
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCheck = isCheck;
exports.isCheckmate = isCheckmate;
exports.isStalemate = isStalemate;
exports.isDead = isDead;
exports.hasMove = hasMove;
exports.moves = moves;
exports.isCaptureMandatory = isCaptureMandatory;
exports.isCastlingMoveLegal = isCastlingMoveLegal;
exports.isMoveLegal = isMoveLegal;
exports.play = play;
exports.isNullMoveLegal = isNullMoveLegal;
exports.playNullMove = playNullMove;
const attacks_1 = require("./attacks");
const base_types_impl_1 = require("./base_types_impl");
const legality_1 = require("./legality");
const move_descriptor_impl_1 = require("./move_descriptor_impl");
/**
* Displacement lookup per square index difference.
*/
const DISPLACEMENT_LOOKUP = [
/* eslint-disable @stylistic/indent, @stylistic/no-multi-spaces */
204, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 204, 0,
0, 204, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 204, 0, 0,
0, 0, 204, 0, 0, 0, 0, 60, 0, 0, 0, 0, 204, 0, 0, 0,
0, 0, 0, 204, 0, 0, 0, 60, 0, 0, 0, 204, 0, 0, 0, 0,
0, 0, 0, 0, 204, 0, 0, 60, 0, 0, 204, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 204, 768, 60, 768, 204, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 768, 2255, 2111, 2255, 768, 0, 0, 0, 0, 0, 0,
60, 60, 60, 60, 60, 60, 63, 0, 63, 60, 60, 60, 60, 60, 60, 0,
0, 0, 0, 0, 0, 768, 1231, 1087, 1231, 768, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 204, 768, 60, 768, 204, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 204, 0, 0, 60, 0, 0, 204, 0, 0, 0, 0, 0,
0, 0, 0, 204, 0, 0, 0, 60, 0, 0, 0, 204, 0, 0, 0, 0,
0, 0, 204, 0, 0, 0, 0, 60, 0, 0, 0, 0, 204, 0, 0, 0,
0, 204, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 204, 0, 0,
204, 0, 0, 0, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 204, 0,
/* eslint-enable */
];
/**
* Sliding direction
*/
const SLIDING_DIRECTION = [
/* eslint-disable @stylistic/indent, @stylistic/no-multi-spaces */
-17, 0, 0, 0, 0, 0, 0, -16, 0, 0, 0, 0, 0, 0, -15, 0,
0, -17, 0, 0, 0, 0, 0, -16, 0, 0, 0, 0, 0, -15, 0, 0,
0, 0, -17, 0, 0, 0, 0, -16, 0, 0, 0, 0, -15, 0, 0, 0,
0, 0, 0, -17, 0, 0, 0, -16, 0, 0, 0, -15, 0, 0, 0, 0,
0, 0, 0, 0, -17, 0, 0, -16, 0, 0, -15, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, -17, 0, -16, 0, -15, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, -17, -16, -15, 0, 0, 0, 0, 0, 0, 0,
-1, -1, -1, -1, -1, -1, -1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 15, 16, 17, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 15, 0, 16, 0, 17, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 15, 0, 0, 16, 0, 0, 17, 0, 0, 0, 0, 0,
0, 0, 0, 15, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 0,
0, 0, 15, 0, 0, 0, 0, 16, 0, 0, 0, 0, 17, 0, 0, 0,
0, 15, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 17, 0, 0,
15, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 17, 0,
/* eslint-enable */
];
/**
* Whether there is at least one piece with the given color in the given position.
*/
function hasAtLeastOnePiece(position, color) {
for (let sq = 0; sq < 120; sq += (sq & 0x7) === 7 ? 9 : 1) {
if (position.board[sq] !== -1 /* SpI.EMPTY */ && position.board[sq] % 2 === color) {
return true;
}
}
return false;
}
/**
* Return `true` if the king of the player to play HAS ROYAL POWER and IS ATTACKED.
*/
function isKingToMoveAttacked(position) {
return position.king[position.turn] >= 0 && (0, attacks_1.isAttacked)(position, position.king[position.turn], 1 - position.turn);
}
/**
* Whether the given position is legal and the player to play is in check.
*/
function isCheck(position) {
return (0, legality_1.isLegal)(position) && isKingToMoveAttacked(position);
}
/**
* Whether the given position is legal and the player to play is checkmated.
*/
function isCheckmate(position) {
if (!(0, legality_1.isLegal)(position) || hasMove(position)) {
return false;
}
if (position.variant === 5 /* GameVariantImpl.ANTICHESS */) {
return true;
}
else if (position.variant === 6 /* GameVariantImpl.HORDE */ && position.turn === 0 /* ColorImpl.WHITE */) {
return !hasAtLeastOnePiece(position, 0 /* ColorImpl.WHITE */);
}
else {
return isKingToMoveAttacked(position);
}
}
/**
* Whether the given position is legal and the player to play is stalemated.
*/
function isStalemate(position) {
if (!(0, legality_1.isLegal)(position) || hasMove(position)) {
return false;
}
if (position.variant === 5 /* GameVariantImpl.ANTICHESS */) {
return true;
}
else if (position.variant === 6 /* GameVariantImpl.HORDE */ && position.turn === 0 /* ColorImpl.WHITE */) {
return hasAtLeastOnePiece(position, 0 /* ColorImpl.WHITE */);
}
else {
return !isKingToMoveAttacked(position);
}
}
/**
* Whether the given position is legal and there is insufficient material on board for checkmate.
*/
function isDead(position, uscfRules) {
if (!(0, legality_1.isLegal)(position)) {
return false;
}
if (position.variant === 0 /* GameVariantImpl.REGULAR_CHESS */ || position.variant === 1 /* GameVariantImpl.CHESS960 */) {
return uscfRules ? isDeadWithUSCFRules(position) : isDeadWithFIDERules(position);
}
else {
return false;
}
}
function isDeadWithFIDERules(position) {
let bishopOrKnightAlreadyFound = false;
let bishopSquareColor = -1;
for (let sq = 0; sq < 120; sq += (sq & 0x7) === 7 ? 9 : 1) {
const cp = position.board[sq];
if (cp === -1 /* SpI.EMPTY */) {
continue;
}
const piece = Math.trunc(cp / 2);
// Ignore any bishop if another bishop has already been found on a square of the same color.
if (piece === 3 /* PieceImpl.BISHOP */) {
const currentSquareColor = (0, base_types_impl_1.squareColorImpl)(sq);
if (bishopSquareColor === currentSquareColor) {
continue;
}
bishopSquareColor = currentSquareColor;
}
// Ensure that at most 1 bishop or knight is encountered, whatever its color.
if (piece === 3 /* PieceImpl.BISHOP */ || piece === 4 /* PieceImpl.KNIGHT */) {
if (bishopOrKnightAlreadyFound) {
return false;
}
bishopOrKnightAlreadyFound = true;
}
// Not a dead position if a pawn, a rook or a queen is encountered.
else if (piece !== 0 /* PieceImpl.KING */) {
return false;
}
}
return true;
}
function isDeadWithUSCFRules(position) {
const materialScore = [0, 0];
const bishopSquareColor = [-1, -1];
for (let sq = 0; sq < 120; sq += (sq & 0x7) === 7 ? 9 : 1) {
const cp = position.board[sq];
if (cp === -1 /* SpI.EMPTY */) {
continue;
}
const color = cp % 2;
const piece = Math.trunc(cp / 2);
// Ignore any bishop if another bishop of the same color has already been found on a square of the same color.
if (piece === 3 /* PieceImpl.BISHOP */) {
const currentSquareColor = (0, base_types_impl_1.squareColorImpl)(sq);
if (bishopSquareColor[color] === currentSquareColor) {
continue;
}
bishopSquareColor[color] = currentSquareColor;
}
// Increase score by 1 for a knight, and by 2 for a bishop. A score of 3 (or more) for any player means that the position is not dead.
if (piece === 3 /* PieceImpl.BISHOP */ || piece === 4 /* PieceImpl.KNIGHT */) {
materialScore[color] += piece === 3 /* PieceImpl.BISHOP */ ? 2 : 1;
if (materialScore[color] >= 3) {
return false;
}
}
// Not a dead position if a pawn, a rook or a queen is encountered.
else if (piece !== 0 /* PieceImpl.KING */) {
return false;
}
}
return true;
}
/**
* Whether there is at least 1 possible move in the given position.
*
* @returns `false` if the position is not legal.
*/
function hasMove(position) {
class MoveFound {
}
try {
generateMoves(position, () => { throw new MoveFound(); });
return false;
}
catch (err) {
// istanbul ignore else
if (err instanceof MoveFound) {
return true;
}
else {
throw err;
}
}
}
/**
* Return all the legal moves in the given position.
*/
function moves(position) {
const result = [];
generateMoves(position, moveDescriptor => { result.push(moveDescriptor); });
return result;
}
/**
* Generate all the legal moves of the given position.
*/
function generateMoves(position, moveDescriptorConsumer) {
// Ensure that the position is legal.
if (!(0, legality_1.isLegal)(position)) {
return;
}
// In some variants, capture may be mandatory (typically in antichess).
const nonCaptureIsAllowed = !isCaptureMandatory(position);
// Generate castling moves
(0, legality_1.refreshEffectiveCastling)(position);
if (nonCaptureIsAllowed && position.effectiveCastling[position.turn] !== 0) {
const rankOffset = 112 * position.turn;
for (let file = 0; file < 8; ++file) {
if ((position.effectiveCastling[position.turn] & 1 << file) !== 0) {
const rookFrom = rankOffset + file;
const rookTo = rankOffset + (rookFrom < position.king[position.turn] ? 3 : 5);
const to = rankOffset + (rookFrom < position.king[position.turn] ? 2 : 6);
if (isCastlingLegal(position, position.king[position.turn], to, rookFrom, rookTo)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makeCastling(position.king[position.turn], to, rookFrom, rookTo, position.turn));
}
}
}
}
// Generate en-passant captures
(0, legality_1.refreshEffectiveEnPassant)(position);
if (position.effectiveEnPassant >= 0) {
const square3 = (5 - position.turn * 3) * 16 + position.effectiveEnPassant;
const square4 = (4 - position.turn) * 16 + position.effectiveEnPassant;
const capturingPawn = 5 /* PieceImpl.PAWN */ * 2 + position.turn;
if (((square4 - 1) & 0x88) === 0 && position.board[square4 - 1] === capturingPawn && (0, legality_1.isKingSafeAfterMove)(position, square4 - 1, square3, square4)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makeEnPassant(square4 - 1, square3, square4, position.turn));
}
if (((square4 + 1) & 0x88) === 0 && position.board[square4 + 1] === capturingPawn && (0, legality_1.isKingSafeAfterMove)(position, square4 + 1, square3, square4)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makeEnPassant(square4 + 1, square3, square4, position.turn));
}
}
// For all potential 'from' square...
for (let from = 0; from < 120; from += (from & 0x7) === 7 ? 9 : 1) {
// Nothing to do if the current square does not contain a piece of the right color.
const fromContent = position.board[from];
const movingPiece = Math.trunc(fromContent / 2);
if (fromContent === -1 /* SpI.EMPTY */ || fromContent % 2 !== position.turn) {
continue;
}
// Generate moves for pawns
if (movingPiece === 5 /* PieceImpl.PAWN */) {
// Regular capturing moves (en-passant not handled here)
for (const attackDirection of attacks_1.ATTACK_DIRECTIONS[fromContent]) {
const to = from + attackDirection;
if ((to & 0x88) === 0 && position.board[to] !== -1 /* SpI.EMPTY */ && position.board[to] % 2 !== position.turn && (0, legality_1.isKingSafeAfterMove)(position, from, to)) {
generateRegularPawnMoveOrPromotion(position, from, to, moveDescriptorConsumer);
}
}
// Non-capturing moves
if (nonCaptureIsAllowed) {
const moveDirection = 16 - position.turn * 32;
let to = from + moveDirection;
if (position.board[to] === -1 /* SpI.EMPTY */) {
if ((0, legality_1.isKingSafeAfterMove)(position, from, to)) {
generateRegularPawnMoveOrPromotion(position, from, to, moveDescriptorConsumer);
}
// 2-square pawn move
const firstSquareOfArea = position.turn * 96; // a1 for white, a7 for black (2-square pawn move is allowed from 1st row at horde chess)
if (from >= firstSquareOfArea && from < firstSquareOfArea + 24) {
to += moveDirection;
if (position.board[to] === -1 /* SpI.EMPTY */ && (0, legality_1.isKingSafeAfterMove)(position, from, to)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.make(from, to, fromContent, -1 /* SpI.EMPTY */));
}
}
}
}
}
// Generate moves for non-sliding non-pawn pieces
else if (movingPiece === 4 /* PieceImpl.KNIGHT */ || movingPiece === 0 /* PieceImpl.KING */) {
for (const attackDirection of attacks_1.ATTACK_DIRECTIONS[fromContent]) {
const to = from + attackDirection;
if ((to & 0x88) === 0) {
const toContent = position.board[to];
if ((toContent === -1 /* SpI.EMPTY */ ? nonCaptureIsAllowed : toContent % 2 !== position.turn) && (0, legality_1.isKingSafeAfterMove)(position, from, to)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.make(from, to, fromContent, toContent));
}
}
}
}
// Generate moves for sliding pieces
else {
for (const attackDirection of attacks_1.ATTACK_DIRECTIONS[fromContent]) {
for (let to = from + attackDirection; (to & 0x88) === 0; to += attackDirection) {
const toContent = position.board[to];
if ((toContent === -1 /* SpI.EMPTY */ ? nonCaptureIsAllowed : toContent % 2 !== position.turn) && (0, legality_1.isKingSafeAfterMove)(position, from, to)) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.make(from, to, fromContent, toContent));
}
if (toContent !== -1 /* SpI.EMPTY */) {
break;
}
}
}
}
}
}
/**
* Generate the move descriptors corresponding to a pawn move from `from` to `to`, excluding 2-square pawn moves and en-passant captures.
*/
function generateRegularPawnMoveOrPromotion(position, from, to, moveDescriptorConsumer) {
const toContent = position.board[to];
if (to < 8 || to >= 112) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, position.turn, toContent, 1 /* PieceImpl.QUEEN */));
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, position.turn, toContent, 2 /* PieceImpl.ROOK */));
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, position.turn, toContent, 3 /* PieceImpl.BISHOP */));
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, position.turn, toContent, 4 /* PieceImpl.KNIGHT */));
if (position.variant === 5 /* GameVariantImpl.ANTICHESS */) {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, position.turn, toContent, 0 /* PieceImpl.KING */));
}
}
else {
moveDescriptorConsumer(move_descriptor_impl_1.MoveDescriptorImpl.make(from, to, 5 /* PieceImpl.PAWN */ * 2 + position.turn, toContent));
}
}
/**
* For antichess, return `true` if the current player can capture something. For other variants, always returns `false`.
*
* Precondition: the position must be legal.
*/
function isCaptureMandatory(position) {
if (position.variant !== 5 /* GameVariantImpl.ANTICHESS */) {
return false;
}
// Look for regular captures
for (let sq = 0; sq < 120; sq += (sq & 0x7) === 7 ? 9 : 1) {
const cp = position.board[sq];
if (cp !== -1 /* SpI.EMPTY */ && cp % 2 !== position.turn && (0, attacks_1.isAttacked)(position, sq, position.turn)) {
return true;
}
}
// Look for "en-passant" captures
(0, legality_1.refreshEffectiveEnPassant)(position);
if (position.effectiveEnPassant >= 0) {
return true;
}
return false;
}
/**
* Delegated method for checking whether a castling move is legal or not. WARNING: in case of Chess960, `to` represents the origin square of the rook (KxR).
*
* Precondition: {@link refreshEffectiveCastling} must have been invoked beforehand.
*/
function isCastlingMoveLegal(position, from, to) {
let rookFrom;
let rookTo;
// Validate `from` and `to`, check the castling flags, and compute the origin and destination squares of the rook.
if (position.variant === 1 /* GameVariantImpl.CHESS960 */) {
const castleFile = to % 16;
const castleRank = Math.trunc(to / 16);
if (castleRank !== position.turn * 7 || (position.effectiveCastling[position.turn] & (1 << castleFile)) === 0) {
return false;
}
rookFrom = to;
rookTo = (from > to ? 3 : 5) + 112 * position.turn;
to = (from > to ? 2 : 6) + 112 * position.turn;
}
else if (to === 2 + position.turn * 112) { // queen-side castling
if ((position.effectiveCastling[position.turn] & 1) === 0) {
return false;
}
rookFrom = 112 * position.turn;
rookTo = 3 + 112 * position.turn;
}
else if (to === 6 + position.turn * 112) { // king-side castling
if ((position.effectiveCastling[position.turn] & (1 << 7)) === 0) {
return false;
}
rookFrom = 7 + 112 * position.turn;
rookTo = 5 + 112 * position.turn;
}
else {
return false;
}
// Generate the descriptor if the castling is actually legal.
return isCastlingLegal(position, from, to, rookFrom, rookTo) ? move_descriptor_impl_1.MoveDescriptorImpl.makeCastling(from, to, rookFrom, rookTo, position.turn) : false;
}
function isCastlingLegal(position, from, to, rookFrom, rookTo) {
// Free the king and rook square (mandatory for attack detection).
position.board[from] = -1 /* SpI.EMPTY */;
position.board[rookFrom] = -1 /* SpI.EMPTY */;
try {
// Ensure that each square on the trajectory is empty.
for (let sq = Math.min(from, to, rookFrom, rookTo); sq <= Math.max(from, to, rookFrom, rookTo); ++sq) {
if (position.board[sq] !== -1 /* SpI.EMPTY */) {
return false;
}
}
// The origin and destination squares of the king, and the square between them must not be attacked.
const byWho = 1 - position.turn;
for (let sq = Math.min(from, to); sq <= Math.max(from, to); ++sq) {
if ((0, attacks_1.isAttacked)(position, sq, byWho)) {
return false;
}
}
// OK, the castling is legal.
return true;
}
finally {
position.board[from] = 0 /* PieceImpl.KING */ * 2 + position.turn;
position.board[rookFrom] = 2 /* PieceImpl.ROOK */ * 2 + position.turn;
}
}
/**
* Core algorithm to determine whether a move is legal or not. The verification flow is the following:
*
* 1. Ensure that the position itself is legal.
* 2. Ensure that the origin square contains a piece (denoted as the moving-piece)
* whose color is the same than the color of the player about to play.
* 3. Special routine for castling detection.
* 4. Ensure that the displacement is geometrically correct, with respect to the moving piece.
* 5. Check the content of the destination square.
* 6. For the sliding pieces (and in case of a 2-square pawn move), ensure that there is no piece
* on the trajectory.
*
* The move is almost ensured to be legal at this point. The last condition to check
* is whether the king of the current player will be in check after the move or not.
*
* 7. Execute the displacement from the origin to the destination square, in such a way that
* it can be reversed. Only the state of the board is updated at this point.
* 8. Look for king attacks.
* 9. Reverse the displacement.
*/
function isMoveLegal(position, from, to) {
// Step (1)
if (!(0, legality_1.isLegal)(position)) {
return false;
}
// Step (2)
const fromContent = position.board[from];
const toContent = position.board[to];
const movingPiece = Math.trunc(fromContent / 2);
if (fromContent === -1 /* SpI.EMPTY */ || fromContent % 2 !== position.turn) {
return false;
}
// Miscellaneous variables
const displacement = to - from + 119;
let enPassantSquare = -1; // square where a pawn is taken if the move is "en-passant"
let isTwoSquarePawnMove = false;
const isPromotion = movingPiece === 5 /* PieceImpl.PAWN */ && (to < 8 || to >= 112);
const captureIsMandatory = isCaptureMandatory(position);
// Step (3) - Castling detection.
if (movingPiece === 0 /* PieceImpl.KING */ && !captureIsMandatory) {
(0, legality_1.refreshEffectiveCastling)(position);
if (position.effectiveCastling[position.turn] !== 0) {
const castlingDescriptor = isCastlingMoveLegal(position, from, to);
if (castlingDescriptor) {
return {
type: 'regular',
moveDescriptor: castlingDescriptor,
};
}
}
}
// Step (4)
if ((DISPLACEMENT_LOOKUP[displacement] & 1 << fromContent) === 0) {
if (movingPiece === 5 /* PieceImpl.PAWN */ && displacement === 151 - position.turn * 64) {
const firstSquareOfArea = position.turn * 96; // a1 for white, a7 for black (2-square pawn move is allowed from 1st row at horde chess)
if (from < firstSquareOfArea || from >= firstSquareOfArea + 24) {
return false;
}
isTwoSquarePawnMove = true;
}
else {
return false;
}
}
// Step (5) -> check the content of the destination square
if (movingPiece === 5 /* PieceImpl.PAWN */) {
(0, legality_1.refreshEffectiveEnPassant)(position);
if (displacement === 135 - position.turn * 32 || isTwoSquarePawnMove) { // non-capturing pawn move
if (captureIsMandatory || toContent !== -1 /* SpI.EMPTY */) {
return false;
}
}
else if (toContent === -1 /* SpI.EMPTY */) { // en-passant pawn move
if (to !== (5 - position.turn * 3) * 16 + position.effectiveEnPassant) {
return false;
}
enPassantSquare = (4 - position.turn) * 16 + position.effectiveEnPassant;
}
else { // regular capturing pawn move
if (toContent % 2 === position.turn) {
return false;
}
}
}
else { // piece move
if (toContent === -1 /* SpI.EMPTY */ ? captureIsMandatory : toContent % 2 === position.turn) {
return false;
}
}
// Step (6) -> For sliding pieces, ensure that there is nothing between the origin and the destination squares.
if (movingPiece === 3 /* PieceImpl.BISHOP */ || movingPiece === 2 /* PieceImpl.ROOK */ || movingPiece === 1 /* PieceImpl.QUEEN */) {
const direction = SLIDING_DIRECTION[displacement];
for (let sq = from + direction; sq !== to; sq += direction) {
if (position.board[sq] !== -1 /* SpI.EMPTY */) {
return false;
}
}
}
else if (isTwoSquarePawnMove) { // two-square pawn moves also require this test.
if (position.board[(from + to) / 2] !== -1 /* SpI.EMPTY */) {
return false;
}
}
// Steps (7) to (9) are delegated to `isKingSafeAfterMove`.
if (!(0, legality_1.isKingSafeAfterMove)(position, from, to, enPassantSquare)) {
return false;
}
if (isPromotion) {
return {
type: 'promotion',
moveDescriptorFactory: buildPromotionMoveDescriptor(from, to, position.variant, position.turn, toContent),
};
}
else {
return {
type: 'regular',
moveDescriptor: enPassantSquare >= 0 ?
move_descriptor_impl_1.MoveDescriptorImpl.makeEnPassant(from, to, enPassantSquare, position.turn) :
move_descriptor_impl_1.MoveDescriptorImpl.make(from, to, fromContent, toContent),
};
}
}
function buildPromotionMoveDescriptor(from, to, variant, color, capturedColoredPiece) {
return promotion => {
if (promotion === 5 /* PieceImpl.PAWN */ || (promotion === 0 /* PieceImpl.KING */ && variant !== 5 /* GameVariantImpl.ANTICHESS */)) {
return false;
}
return move_descriptor_impl_1.MoveDescriptorImpl.makePromotion(from, to, color, capturedColoredPiece, promotion);
};
}
/**
* Play the move corresponding to the given descriptor.
*/
function play(position, descriptor) {
(0, legality_1.refreshEffectiveCastling)(position);
// Update the board.
position.board[descriptor._from] = -1 /* SpI.EMPTY */; // WARNING: update `from` before `to` in case both squares are actually the same!
if (descriptor.isEnPassant()) {
position.board[descriptor._optionalSquare1] = -1 /* SpI.EMPTY */;
}
else if (descriptor.isCastling()) {
position.board[descriptor._optionalSquare1] = -1 /* SpI.EMPTY */;
position.board[descriptor._optionalSquare2] = descriptor._optionalColoredPiece;
}
position.board[descriptor._to] = descriptor._finalColoredPiece;
const movingPiece = Math.trunc(descriptor._movingColoredPiece / 2);
// Update the castling flags.
if (movingPiece === 0 /* PieceImpl.KING */) {
position.effectiveCastling[position.turn] = 0;
}
if (descriptor._from < 8) {
position.effectiveCastling[0 /* ColorImpl.WHITE */] &= ~(1 << descriptor._from);
}
if (descriptor._to < 8) {
position.effectiveCastling[0 /* ColorImpl.WHITE */] &= ~(1 << descriptor._to);
}
if (descriptor._from >= 112) {
position.effectiveCastling[1 /* ColorImpl.BLACK */] &= ~(1 << (descriptor._from % 16));
}
if (descriptor._to >= 112) {
position.effectiveCastling[1 /* ColorImpl.BLACK */] &= ~(1 << (descriptor._to % 16));
}
position.castling[0 /* ColorImpl.WHITE */] = position.effectiveCastling[0 /* ColorImpl.WHITE */];
position.castling[1 /* ColorImpl.BLACK */] = position.effectiveCastling[1 /* ColorImpl.BLACK */];
// Update the en-passant flag.
position.enPassant = -1;
position.effectiveEnPassant = -1;
if (movingPiece === 5 /* PieceImpl.PAWN */ && Math.abs(descriptor._from - descriptor._to) === 32) {
const firstSquareOf2ndRow = (1 + 5 * position.turn) * 16;
if (descriptor._from >= firstSquareOf2ndRow && descriptor._from < firstSquareOf2ndRow + 8) {
const otherPawn = descriptor._movingColoredPiece ^ 0x01;
if ((((descriptor._to - 1) & 0x88) === 0 && position.board[descriptor._to - 1] === otherPawn) ||
(((descriptor._to + 1) & 0x88) === 0 && position.board[descriptor._to + 1] === otherPawn)) {
position.enPassant = descriptor._to % 16;
position.effectiveEnPassant = null; // Only geometric conditions have been validated so far.
}
}
}
// Update the computed flags.
if (movingPiece === 0 /* PieceImpl.KING */ && position.king[position.turn] >= 0) {
position.king[position.turn] = descriptor._to;
}
// Toggle the turn flag.
position.turn = 1 - position.turn;
}
/**
* Determine if a null-move (i.e. switching the player about to play) can be played in the current position.
* A null-move is possible if the position is legal and if the current player about to play is not in check.
*/
function isNullMoveLegal(position) {
return (0, legality_1.isLegal)(position) && !isKingToMoveAttacked(position);
}
/**
* Play a null-move on the current position if it is legal.
*
* @returns `true` if the null-move has actually been played.
*/
function playNullMove(position) {
if (isNullMoveLegal(position)) {
position.turn = 1 - position.turn;
position.enPassant = -1;
position.effectiveEnPassant = -1;
return true;
}
else {
return false;
}
}
//# sourceMappingURL=move_generation.js.map