"use strict"; /** * Chess.Position contains current piece positions, player turn, etc; the game state. * It can generate a list of possible moves in the current game state, and apply moves to change state. * @constructor */ Chess.Position = function() { /** * @type {!Chess.Zobrist} */ this.hashKey = new Chess.Zobrist(0, 0); /** * Bitboards for each piece type, and for any white and black pieces. * TODO: replace with a Uin32Array or a straight array of numbers? (then implement 64 bit bitboard operations on top of that) * TODO: store kings as two indices? (there can only be one king per color) * @type {!Array.} */ this.bitboards = [ Chess.Bitboard.RANKS[1].dup().or(Chess.Bitboard.RANKS[6]), // pawns Chess.Bitboard.makeIndex(1).or(Chess.Bitboard.makeIndex(6)).or(Chess.Bitboard.makeIndex(57)).or(Chess.Bitboard.makeIndex(62)), // knights Chess.Bitboard.makeIndex(2).or(Chess.Bitboard.makeIndex(5)).or(Chess.Bitboard.makeIndex(58)).or(Chess.Bitboard.makeIndex(61)), // bishops Chess.Bitboard.makeIndex(0).or(Chess.Bitboard.makeIndex(7)).or(Chess.Bitboard.makeIndex(56)).or(Chess.Bitboard.makeIndex(63)), // rooks Chess.Bitboard.makeIndex(3).or(Chess.Bitboard.makeIndex(59)), // queens Chess.Bitboard.makeIndex(4).or(Chess.Bitboard.makeIndex(60)), // kings Chess.Bitboard.RANKS[0].dup().or(Chess.Bitboard.RANKS[1]), // white pieces Chess.Bitboard.RANKS[6].dup().or(Chess.Bitboard.RANKS[7]) // black pieces ]; /** * 64 entry lookup table, holds a piece or null for each board square * @type {!Array.} */ this.pieces = []; /** * @type {!Chess.PieceColor} */ this.turn = Chess.PieceColor.WHITE; /** * 1st bit: white can castle kingside * 2nd bit: black can castle kingside * 3rd bit: white can castle queenside * 4th bit: black can castle queenside * @type {number} 0-15 */ this.castlingRights = 15; /** * @type {number} 0-63 */ this.enPassantSquare = -1; /** * @type {number} * @see http://goo.gl/xGY6o (Fifty-move rule) */ this.halfmoveClock = 0; /** * @type {!Array.} */ this.madeMoves = []; /** * @type {!Array.} */ this.irreversibleHistory = []; this.fillPiecesFromBitboards(); this.updateHashKey(); /** * @type {!Array.} */ this.hashHistory = []; // TODO: checking pieces? // TODO: separate occupied squares bitboard? // TODO: store kings as indices instead of bitboards? }; /** * Chess.Bitboard for squares with white pieces in them * @const * @type {number} */ Chess.Position.ANY_WHITE = Chess.Piece.KING + 1; /** * Chess.Bitboard for squares with black pieces in them * @const * @type {number} */ Chess.Position.ANY_BLACK = Chess.Position.ANY_WHITE + 1; /** * Initial rook indices * @const * @type {!Array.} */ Chess.Position.ROOK_INDICES = [7, 63, 0, 56]; /** * Bitmasks for avoiding sliding piece wrapping. * @const * @type {!Array.} */ Chess.Position.SLIDING_MASKS = [Chess.Bitboard.makeFile(Chess.LAST_FILE).not(), Chess.Bitboard.ONE, Chess.Bitboard.makeFile(0).not()]; /** @enum {number} */ Chess.Position.Status = { NORMAL: 0, CHECKMATE: 1, STALEMATE_DRAW: 2, FIFTY_MOVE_RULE_DRAW: 3, THREEFOLD_REPETITION_RULE_DRAW: 4, INSUFFICIENT_MATERIAL_DRAW: 5 }; /** * Perft: performance test * @param {number} depth how many half-moves to make * @param {Chess.Position=} chessPosition start position * @return {number} how many leaf nodes does the game tree have at the depth */ Chess.Position.perft = function(depth, chessPosition) { if (!depth) { return 1; } if (!chessPosition) { chessPosition = new Chess.Position; } /** @type {number} */ var nodes = 0; chessPosition.getMoves(true).forEach(/** @param {!Chess.Move} move */ function(move) { if (chessPosition.makeMove(move)) { nodes += Chess.Position.perft(depth - 1, chessPosition); chessPosition.unmakeMove(); } }); return nodes; }; /** * @param {boolean=} pseudoLegal true: also return moves that may not be possible (leave king in check) * @param {boolean=} onlyCaptures * @return {!Array.} Possible moves in this chess position */ Chess.Position.prototype.getMoves = function(pseudoLegal, onlyCaptures) { var moves = this.generateMoves(!!onlyCaptures); return pseudoLegal ? moves : moves.filter(Chess.Position.prototype.isMoveLegal, this); }; /** * @param {!Chess.PieceColor} color Chess.PieceColor.WHITE or Chess.PieceColor.BLACK * @return {!Chess.Bitboard} Squares occupied by any piece of the specified color */ Chess.Position.prototype.getColorBitboard = function(color) { return this.bitboards[Chess.Position.ANY_WHITE + color]; }; /** * @param {!Chess.Piece} piece * @return {!Chess.Bitboard} Bits set where specified piece exists */ Chess.Position.prototype.getPieceBitboard = function(piece) { return this.bitboards[piece]; }; /** * @param {!Chess.Piece} piece * @param {!Chess.PieceColor} color * @return {!Chess.Bitboard} Bits set where specified piece with the specified color exists */ Chess.Position.prototype.getPieceColorBitboard = function(piece, color) { return this.bitboards[piece].dup().and(this.getColorBitboard(color)); }; /** * @param {!Chess.PieceColor} color * @return {number} 0-63 */ Chess.Position.prototype.getKingPosition = function(color) { return this.getPieceColorBitboard(Chess.Piece.KING, color).getLowestBitPosition(); }; /** @return {!Chess.Bitboard} Squares occupied by any piece */ Chess.Position.prototype.getOccupiedBitboard = function() { return this.bitboards[Chess.Position.ANY_WHITE].dup().or(this.bitboards[Chess.Position.ANY_BLACK]); }; /** @return {!Chess.Bitboard} Empty squares */ Chess.Position.prototype.getEmptyBitboard = function() { return this.getOccupiedBitboard().not(); }; /** @return {!Chess.PieceColor} */ Chess.Position.prototype.getTurnColor = function() { return this.turn; }; /** * @param {number} index 0-63 * @return {?Chess.Piece} */ Chess.Position.prototype.findPieceAtOrNull = function(index) { for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) { if (this.getPieceBitboard(piece).isSet(index)) { return piece; } } return null; }; /** * @param {number} index 0-63 * @return {?Chess.Piece} */ Chess.Position.prototype.getPieceAtOrNull = function(index) { return this.pieces[index]; }; /** Fills the piece lookup table from bitboards */ Chess.Position.prototype.fillPiecesFromBitboards = function() { this.pieces.length = 0; for (var index = 0; index < 64; ++index) { this.pieces.push(this.findPieceAtOrNull(index)); } }; /** Updates the hash key from turn, bitboards, castling rights and en passant square. Halfmove clock is not part of the hash */ Chess.Position.prototype.updateHashKey = function() { this.hashKey = new Chess.Zobrist(0, 0); if (this.getTurnColor()) { this.hashKey.updateTurn(); } for (var color = Chess.PieceColor.WHITE; color <= Chess.PieceColor.BLACK; ++color) { for (var piece = Chess.Piece.PAWN; piece <= Chess.Piece.KING; ++piece) { this.hashKey.updatePieceColorBitboard(piece, color, this.getPieceColorBitboard(piece, color)); } } this.hashKey.updateCastlingRights(this.castlingRights); this.hashKey.updateEnPassantSquare(this.enPassantSquare); }; /** * @return {boolean} */ Chess.Position.prototype.isKingInCheck = function() { return this.isAttacked(Chess.getOtherPieceColor(this.getTurnColor()), this.getKingPosition(this.getTurnColor())); }; /** * @param {!Chess.PieceColor} color white = attacks by white pieces * @param {!Chess.Bitboard} pawns * @return {!Chess.Bitboard} * N.B. no en passant attacks */ Chess.Position.makePawnAttackMask = function(color, pawns) { var white = (color === Chess.PieceColor.WHITE); var attacks1 = pawns.dup().and_not(Chess.Bitboard.FILES[0]).shiftLeft(white ? 7 : -9); var attacks2 = pawns.dup().and_not(Chess.Bitboard.FILES[Chess.LAST_FILE]).shiftLeft(white ? 9 : -7); return attacks1.or(attacks2); }; /** * @param {!Chess.Bitboard} fromBB * @param {!Chess.Bitboard} occupied * @param {number} rankDirection * @param {number} fileDirection * @return {!Chess.Bitboard} * TODO: Kogge-Stone: http://chessprogramming.wikispaces.com/Kogge-Stone+Algorithm */ Chess.Position.makeSlidingAttackMask = function(fromBB, occupied, rankDirection, fileDirection) { var bb = Chess.Bitboard.makeZero(); var direction = rankDirection * Chess.FILES + fileDirection; var mask = Chess.Position.SLIDING_MASKS[1 + fileDirection]; for (fromBB.shiftLeft(direction); !fromBB.and(mask).isEmpty(); fromBB.and_not(occupied).shiftLeft(direction)) { bb.or(fromBB); } return bb; }; /** * @param {!Chess.Bitboard} fromBB * @param {!Chess.Bitboard} occupied * @return {!Chess.Bitboard} */ Chess.Position.makeBishopAttackMask = function(fromBB, occupied) { return Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, 1).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, -1)).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, 1)).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, -1)); }; /** * @param {!Chess.Bitboard} fromBB * @param {!Chess.Bitboard} occupied * @return {!Chess.Bitboard} */ Chess.Position.makeRookAttackMask = function(fromBB, occupied) { return Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 0, 1).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 0, -1)).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, 1, 0)).or( Chess.Position.makeSlidingAttackMask(fromBB.dup(), occupied, -1, 0)); }; /** * @param {!Chess.PieceColor} color attacked by color * @param {number} index * @return {boolean} * @see http://goo.gl/UYzOw (Square Attacked By) */ Chess.Position.prototype.isAttacked = function(color, index) { var pawns = this.getPieceColorBitboard(Chess.Piece.PAWN, color); if (Chess.Position.makePawnAttackMask(color, pawns).isSet(index)) { return true; } var knights = this.getPieceColorBitboard(Chess.Piece.KNIGHT, color); if (!Chess.Bitboard.KNIGHT_MOVEMENTS[index].dup().and(knights).isEmpty()) { return true; } var king = this.getPieceColorBitboard(Chess.Piece.KING, color); if (!Chess.Bitboard.KING_MOVEMENTS[index].dup().and(king).isEmpty()) { return true; } var occupied = this.getOccupiedBitboard(); var queens = this.getPieceColorBitboard(Chess.Piece.QUEEN, color); var bq = this.getPieceColorBitboard(Chess.Piece.BISHOP, color).dup().or(queens); if (Chess.Position.makeBishopAttackMask(bq, occupied).isSet(index)) { return true; } var rq = this.getPieceColorBitboard(Chess.Piece.ROOK, color).dup().or(queens); if (Chess.Position.makeRookAttackMask(rq, occupied).isSet(index)) { return true; } return false; }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * @return {number} 0-3 index to castling rights */ Chess.Position.getCastlingIndex = function(color, kingSide) { return color + (kingSide ? 0 : 2); }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * @return {number} home square of the castling rook */ Chess.Position.getCastlingRookSquare = function(color, kingSide) { return Chess.Position.ROOK_INDICES[Chess.Position.getCastlingIndex(color, kingSide)]; }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * @return {boolean} */ Chess.Position.prototype.hasCastlingRight = function(color, kingSide) { return 0 !== (this.castlingRights & (1 << Chess.Position.getCastlingIndex(color, kingSide))); }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside */ Chess.Position.prototype.clearCastlingRight = function(color, kingSide) { this.hashKey.updateCastlingRights(this.castlingRights); this.castlingRights &= ~(1 << Chess.Position.getCastlingIndex(color, kingSide)); this.hashKey.updateCastlingRights(this.castlingRights); }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * @param {boolean} onlyLegal true = check that king's route is not attacked * @return {boolean} * TODO: allow pseudo-legal castle moves, i.e. don't check attacked until makeMove */ Chess.Position.prototype.canCastle = function(color, kingSide, onlyLegal) { if (!this.hasCastlingRight(color, kingSide)) { return false; } var direction = kingSide ? 1 : -1; var kingPosition = (color === Chess.PieceColor.WHITE) ? 4 : 60; var occupied = this.getOccupiedBitboard(); if (occupied.isSet(kingPosition + direction) || occupied.isSet(kingPosition + 2 * direction)) { return false; } if (!kingSide && occupied.isSet(kingPosition + 3 * direction)) { return false; } if (onlyLegal && !this.isCastlingLegal(color, kingSide)) { return false; } return true; }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * @return {boolean} */ Chess.Position.prototype.isCastlingLegal = function(color, kingSide) { var otherColor = Chess.getOtherPieceColor(color); var direction = kingSide ? 1 : -1; var kingPosition = (color === Chess.PieceColor.WHITE) ? 4 : 60; return !this.isAttacked(otherColor, kingPosition) && !this.isAttacked(otherColor, kingPosition + direction) && !this.isAttacked(otherColor, kingPosition + 2 * direction); }; /** @return {boolean} */ Chess.Position.prototype.canEnPassant = function() { return this.getEnPassantSquare() >= 0; }; /** @return {number} */ Chess.Position.prototype.getEnPassantSquare = function() { return this.enPassantSquare; }; /** @return {boolean} */ Chess.Position.prototype.isFiftyMoveRuleDraw = function() { return this.halfmoveClock >= 100; }; /** @return {boolean} */ Chess.Position.prototype.isThreefoldRepetitionRuleDraw = function() { var currentHashKey = this.hashKey; return this.hashHistory.reduce( /** * @param {number} previousValue * @param {!Chess.Zobrist} currentValue * @param {number} index (unused; please the Closure Compiler) * @param {Array} array (unused; please the Closure Compiler) * @return {number} */ function(previousValue, currentValue, index, array) { return previousValue + (currentValue.isEqual(currentHashKey) ? 1 : 0); }, 0) >= 3; }; /** * @return {boolean} * TODO: find a good source for how this is supposed to work */ Chess.Position.prototype.isInsufficientMaterialDraw = function() { if (!this.getPieceBitboard(Chess.Piece.PAWN).isEmpty()) { return false; } if (!this.getPieceBitboard(Chess.Piece.ROOK).isEmpty()) { return false; } if (!this.getPieceBitboard(Chess.Piece.QUEEN).isEmpty()) { return false; } // only kings, knights and bishops on the board var whiteCount = this.getColorBitboard(Chess.PieceColor.WHITE).popcnt(); var blackCount = this.getColorBitboard(Chess.PieceColor.BLACK).popcnt(); if (whiteCount + blackCount < 4) { // king vs king, king&bishop vs king, king&knight vs king return true; } if (!this.getPieceBitboard(Chess.Piece.KNIGHT).isEmpty()) { return false; } // only kings and bishops on the board var bishops = this.getPieceBitboard(Chess.Piece.BISHOP); if (bishops.dup().and(Chess.Bitboard.LIGHT_SQUARES).isEqual(bishops) || bishops.dup().and(Chess.Bitboard.DARK_SQUARES).isEqual(bishops)) { return true; } return false; }; /** @return {boolean} */ Chess.Position.prototype.isDraw = function() { return this.isFiftyMoveRuleDraw() || this.isThreefoldRepetitionRuleDraw() || this.isInsufficientMaterialDraw(); }; /** @return {!Chess.Position.Status} */ Chess.Position.prototype.getStatus = function() { if (!this.getMoves().length) { return this.isKingInCheck() ? Chess.Position.Status.CHECKMATE : Chess.Position.Status.STALEMATE_DRAW; } if (this.isFiftyMoveRuleDraw()) { return Chess.Position.Status.FIFTY_MOVE_RULE_DRAW; } if (this.isThreefoldRepetitionRuleDraw()) { return Chess.Position.Status.THREEFOLD_REPETITION_RULE_DRAW; } if (this.isInsufficientMaterialDraw()) { return Chess.Position.Status.INSUFFICIENT_MATERIAL_DRAW; } return Chess.Position.Status.NORMAL; }; /** * @param {boolean} onlyCaptures * @return {!Array.} pseudo-legal moves in this chess position * TODO: special-case move generation when king is check */ Chess.Position.prototype.generateMoves = function(onlyCaptures) { var moves = []; var turnColor = this.getTurnColor(); var opponentBB = this.getColorBitboard(Chess.getOtherPieceColor(turnColor)); var occupied = this.getOccupiedBitboard(); var chessPosition = this; // Pawn moves: double pushes, positional moves, captures, promotions, en passant /** * @param {!Chess.Bitboard} toMask * @param {number} movement * @param {!Chess.Move.Kind} kind */ function addPawnMoves(toMask, movement, kind) { while (!toMask.isEmpty()) { var index = toMask.extractLowestBitPosition(); moves.push(new Chess.Move(index - movement, index, kind, Chess.Piece.PAWN, chessPosition.getPieceAtOrNull(index))); } } /** * @param {!Chess.Bitboard} toMask * @param {number} movement * @param {boolean} capture */ function addPawnPromotions(toMask, movement, capture) { addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.QUEEN_PROMOTION_CAPTURE : Chess.Move.Kind.QUEEN_PROMOTION); addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.ROOK_PROMOTION_CAPTURE : Chess.Move.Kind.ROOK_PROMOTION); addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.BISHOP_PROMOTION_CAPTURE : Chess.Move.Kind.BISHOP_PROMOTION); addPawnMoves(toMask.dup(), movement, capture ? Chess.Move.Kind.KNIGHT_PROMOTION_CAPTURE : Chess.Move.Kind.KNIGHT_PROMOTION); } var fileDirection = 1 - 2 * turnColor; var rankDirection = Chess.FILES * fileDirection; var turnPawns = this.getPieceColorBitboard(Chess.Piece.PAWN, turnColor); var lastRow = Chess.Bitboard.RANKS[turnColor ? 0 : Chess.LAST_RANK]; if (!onlyCaptures) { // Double pawn pushes: pawns that are at their initial position, with nothing in the next two rows var doublePawnPushed = turnPawns.dup().and(Chess.Bitboard.RANKS[turnColor ? 6 : 1]).shiftLeft(2 * rankDirection).and_not(occupied).and_not(occupied.dup().shiftLeft(rankDirection)); addPawnMoves(doublePawnPushed, 2 * rankDirection, Chess.Move.Kind.DOUBLE_PAWN_PUSH); // Positional pawn moves: advance one square to an empty square; not to the last row // Pawn promotion: to the last row var positionalPawnMoved = turnPawns.dup().shiftLeft(rankDirection).and_not(occupied); addPawnMoves(positionalPawnMoved.dup().and_not(lastRow), rankDirection, Chess.Move.Kind.POSITIONAL); addPawnPromotions(positionalPawnMoved.dup().and(lastRow), rankDirection, false); } // Pawn captures: advance diagonally to the next row to a square occupied by an opponent piece; not to the last row. Also, don't wrap the board from left/right. // Pawn promotion w/ capture: to the last row var leftFile = Chess.Bitboard.FILES[turnColor ? Chess.LAST_FILE : 0]; var leftCaptureMovement = rankDirection - fileDirection; var pawnLeftCaptured = turnPawns.dup().and_not(leftFile).shiftLeft(leftCaptureMovement).and(opponentBB); addPawnMoves(pawnLeftCaptured.dup().and_not(lastRow), leftCaptureMovement, Chess.Move.Kind.CAPTURE); addPawnPromotions(pawnLeftCaptured.dup().and(lastRow), leftCaptureMovement, true); var rightFile = Chess.Bitboard.FILES[turnColor ? 0 : Chess.LAST_FILE]; var rightCaptureMovement = rankDirection + fileDirection; var pawnRightCaptured = turnPawns.dup().and_not(rightFile).shiftLeft(rightCaptureMovement).and(opponentBB); addPawnMoves(pawnRightCaptured.dup().and_not(lastRow), rightCaptureMovement, Chess.Move.Kind.CAPTURE); addPawnPromotions(pawnRightCaptured.dup().and(lastRow), rightCaptureMovement, true); // Pawn en passant captures: opponent has just double pawn pushed in the last move next to our pawn, we move diagonally behind the opponent pawn, capturing it if (this.canEnPassant()) { var pawnLeftEnPassant = Chess.Bitboard.makeIndex(this.getEnPassantSquare() + fileDirection).and(turnPawns).and_not(leftFile).shiftLeft(leftCaptureMovement); addPawnMoves(pawnLeftEnPassant, leftCaptureMovement, Chess.Move.Kind.EN_PASSANT_CAPTURE); var pawnRightEnPassant = Chess.Bitboard.makeIndex(this.getEnPassantSquare() - fileDirection).and(turnPawns).and_not(rightFile).shiftLeft(rightCaptureMovement); addPawnMoves(pawnRightEnPassant, rightCaptureMovement, Chess.Move.Kind.EN_PASSANT_CAPTURE); } // Positional and capture moves for knight, bishop, rook, queen, king /** * @param {number} from 0-63 * @param {!Chess.Bitboard} toMask * @param {!Chess.Piece} piece */ function addNormalMoves(from, toMask, piece) { while (!toMask.isEmpty()) { var to = toMask.extractLowestBitPosition(); moves.push(new Chess.Move(from, to, opponentBB.isSet(to) ? Chess.Move.Kind.CAPTURE : Chess.Move.Kind.POSITIONAL, piece, chessPosition.getPieceAtOrNull(to))); } } var mask = this.getColorBitboard(turnColor).dup().not(); if (onlyCaptures) { mask.and(opponentBB); } var turnKnights = this.getPieceColorBitboard(Chess.Piece.KNIGHT, turnColor).dup(); while (!turnKnights.isEmpty()) { var knightPosition = turnKnights.extractLowestBitPosition(); addNormalMoves(knightPosition, Chess.Bitboard.KNIGHT_MOVEMENTS[knightPosition].dup().and(mask), Chess.Piece.KNIGHT); } var turnQueens = this.getPieceColorBitboard(Chess.Piece.QUEEN, turnColor).dup(); while (!turnQueens.isEmpty()) { var queenPosition = turnQueens.extractLowestBitPosition(); addNormalMoves(queenPosition, Chess.Position.makeBishopAttackMask(Chess.Bitboard.makeIndex(queenPosition), occupied).or( Chess.Position.makeRookAttackMask(Chess.Bitboard.makeIndex(queenPosition), occupied)).and(mask), Chess.Piece.QUEEN); } var turnBishops = this.getPieceColorBitboard(Chess.Piece.BISHOP, turnColor).dup(); while (!turnBishops.isEmpty()) { var bishopPosition = turnBishops.extractLowestBitPosition(); addNormalMoves(bishopPosition, Chess.Position.makeBishopAttackMask(Chess.Bitboard.makeIndex(bishopPosition), occupied).and(mask), Chess.Piece.BISHOP); } var turnRooks = this.getPieceColorBitboard(Chess.Piece.ROOK, turnColor).dup(); while (!turnRooks.isEmpty()) { var rookPosition = turnRooks.extractLowestBitPosition(); addNormalMoves(rookPosition, Chess.Position.makeRookAttackMask(Chess.Bitboard.makeIndex(rookPosition), occupied).and(mask), Chess.Piece.ROOK); } var kingPosition = this.getKingPosition(turnColor); addNormalMoves(kingPosition, Chess.Bitboard.KING_MOVEMENTS[kingPosition].dup().and(mask), Chess.Piece.KING); if (!onlyCaptures) { // King & queen side castle if (this.canCastle(turnColor, true, true)) { moves.push(new Chess.Move(kingPosition, kingPosition + 2, Chess.Move.Kind.KING_CASTLE, Chess.Piece.KING, null)); } if (this.canCastle(turnColor, false, true)) { moves.push(new Chess.Move(kingPosition, kingPosition - 2, Chess.Move.Kind.QUEEN_CASTLE, Chess.Piece.KING, null)); } } return moves; }; /** * @param {!Chess.Piece} piece * @param {!Chess.PieceColor} color of the captured piece * @param {number} index */ Chess.Position.prototype.capturePiece = function(piece, color, index) { this.getPieceBitboard(piece).clearBit(index); this.getColorBitboard(color).clearBit(index); this.pieces[index] = null; this.hashKey.updatePieceColorSquare(piece, color, index); }; /** * @param {!Chess.Piece} piece * @param {!Chess.PieceColor} color of the captured piece * @param {number} index */ Chess.Position.prototype.unCapturePiece = function(piece, color, index) { this.getPieceBitboard(piece).setBit(index); this.getColorBitboard(color).setBit(index); this.pieces[index] = /** @type {!Chess.Piece} */(piece); this.hashKey.updatePieceColorSquare(piece, color, index); }; /** * @param {!Chess.Piece} piece * @param {!Chess.PieceColor} color * @param {number} from 0-63 * @param {number} to 0-63 */ Chess.Position.prototype.movePiece = function(piece, color, from, to) { var fromToBB = Chess.Bitboard.makeIndex(from).or(Chess.Bitboard.makeIndex(to)); this.getPieceBitboard(piece).xor(fromToBB); this.getColorBitboard(color).xor(fromToBB); this.pieces[from] = null; this.pieces[to] = /** @type {!Chess.Piece} */(piece); this.hashKey.updatePieceColorSquare(piece, color, from); this.hashKey.updatePieceColorSquare(piece, color, to); }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * N.B. only moves the rook, not the king */ Chess.Position.prototype.castleRook = function(color, kingSide) { var from = Chess.Position.getCastlingRookSquare(color, kingSide); var to = from + (kingSide ? -2 : 3); this.movePiece(Chess.Piece.ROOK, color, from, to); }; /** * @param {!Chess.PieceColor} color * @param {boolean} kingSide true = castle kingside, false = castle queenside * N.B. only moves the rook, not the king */ Chess.Position.prototype.unCastleRook = function(color, kingSide) { var to = Chess.Position.getCastlingRookSquare(color, kingSide); var from = to + (kingSide ? -2 : 3); this.movePiece(Chess.Piece.ROOK, color, from, to); }; /** * @param {!Chess.Piece} pieceOld * @param {!Chess.Piece} pieceNew * @param {!Chess.PieceColor} color * @param {number} index 0-63 * @see http://goo.gl/jkRj9 (Update by Move) */ Chess.Position.prototype.promotePiece = function(pieceOld, pieceNew, color, index) { this.getPieceBitboard(pieceOld).clearBit(index); this.getPieceBitboard(pieceNew).setBit(index); this.pieces[index] = /** @type {!Chess.Piece} */(pieceNew); this.hashKey.updatePieceColorSquare(pieceOld, color, index); this.hashKey.updatePieceColorSquare(pieceNew, color, index); }; /** * Changes the chess pieces according to move * @param {!Chess.Move} move to make */ Chess.Position.prototype.updatePieces = function(move) { if (move.isCapture()) { this.capturePiece(move.getCapturedPiece(), Chess.getOtherPieceColor(this.getTurnColor()), move.getCaptureSquare()); } if (move.isCastle()) { this.castleRook(this.getTurnColor(), move.getKind() === Chess.Move.Kind.KING_CASTLE); } this.movePiece(move.getPiece(), this.getTurnColor(), move.getFrom(), move.getTo()); if (move.isPromotion()) { this.promotePiece(Chess.Piece.PAWN, move.getPromotedPiece(), this.getTurnColor(), move.getTo()); } }; /** * Reverts the chess pieces to the positions before making move * @param {!Chess.Move} move to unmake */ Chess.Position.prototype.revertPieces = function(move) { if (move.isPromotion()) { this.promotePiece(move.getPromotedPiece(), Chess.Piece.PAWN, this.getTurnColor(), move.getTo()); } this.movePiece(move.getPiece(), this.getTurnColor(), move.getTo(), move.getFrom()); if (move.isCastle()) { this.unCastleRook(this.getTurnColor(), move.getKind() === Chess.Move.Kind.KING_CASTLE); } if (move.isCapture()) { this.unCapturePiece(move.getCapturedPiece(), Chess.getOtherPieceColor(this.getTurnColor()), move.getCaptureSquare()); } }; /** * Checks a pseudo-legal move's legality * @param {!Chess.Move} move to test * @return {boolean} */ Chess.Position.prototype.isMoveLegal = function(move) { this.updatePieces(move); var kingInCheck = this.isKingInCheck(); this.revertPieces(move); return !kingInCheck; }; /** * Changes the pieces according to the move, and adds the move to the move history list * @param {!Chess.Move} move to make * @return {boolean} true if the move was made */ Chess.Position.prototype.makeMove = function(move) { this.hashHistory.push(this.hashKey.dup()); this.updatePieces(move); if (this.isKingInCheck()) { this.revertPieces(move); this.hashHistory.pop(); return false; } this.madeMoves.push(move); this.irreversibleHistory.push(this.enPassantSquare); this.irreversibleHistory.push(this.castlingRights); this.irreversibleHistory.push(this.halfmoveClock); this.hashKey.updateEnPassantSquare(this.enPassantSquare); if (move.getKind() === Chess.Move.Kind.DOUBLE_PAWN_PUSH) { this.enPassantSquare = move.getTo(); } else { this.enPassantSquare = -1; } this.hashKey.updateEnPassantSquare(this.enPassantSquare); var turnColor = this.getTurnColor(); if (move.getPiece() === Chess.Piece.KING) { this.clearCastlingRight(turnColor, true); this.clearCastlingRight(turnColor, false); } else if (move.getPiece() === Chess.Piece.ROOK) { if (move.getFrom() === Chess.Position.getCastlingRookSquare(turnColor, true)) { this.clearCastlingRight(turnColor, true); } else if (move.getFrom() === Chess.Position.getCastlingRookSquare(turnColor, false)) { this.clearCastlingRight(turnColor, false); } } var otherColor = Chess.getOtherPieceColor(turnColor); if (move.getCapturedPiece() === Chess.Piece.ROOK) { if (move.getCaptureSquare() === Chess.Position.getCastlingRookSquare(otherColor, true)) { this.clearCastlingRight(otherColor, true); } else if (move.getCaptureSquare() === Chess.Position.getCastlingRookSquare(otherColor, false)) { this.clearCastlingRight(otherColor, false); } } if (move.isCapture() || move.getPiece() === Chess.Piece.PAWN) { this.halfmoveClock = 0; } else { ++this.halfmoveClock; } this.turn = otherColor; this.hashKey.updateTurn(); return true; }; /** * @return {number} number of moves made */ Chess.Position.prototype.getMadeMoveCount = function() { return this.madeMoves.length; }; /** * @return {boolean} if a move has been made */ Chess.Position.prototype.canUndo = function() { return !!this.getMadeMoveCount(); }; /** * @return {?Chess.Move} the latest move or null if the board was at the initial state */ Chess.Position.prototype.getLastMove = function() { if (!this.canUndo()) { return null; } return this.madeMoves[this.madeMoves.length - 1]; }; /** * Unmakes the latest move made * @return {?Chess.Move} the unmade move or null if the board was at the initial state */ Chess.Position.prototype.unmakeMove = function() { if (!this.canUndo()) { return null; } var move = /** @type {!Chess.Move} */(this.madeMoves.pop()); this.turn = Chess.getOtherPieceColor(this.getTurnColor()); this.hashKey.updateTurn(); this.revertPieces(move); this.halfMoveClock = /** @type {number} */(this.irreversibleHistory.pop()); this.hashKey.updateCastlingRights(this.castlingRights); this.castlingRights = /** @type {number} */(this.irreversibleHistory.pop()); this.hashKey.updateCastlingRights(this.castlingRights); this.hashKey.updateEnPassantSquare(this.enPassantSquare); this.enPassantSquare = /** @type {number} */(this.irreversibleHistory.pop()); this.hashKey.updateEnPassantSquare(this.enPassantSquare); this.hashHistory.pop(); return move; };