UNPKG

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
"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