/** checks draw by fiftymoves rule and threefold repetition */ public boolean isDraw() { if (fiftyMovesRule >= 100) { return true; } int repetitions = 0; // logger.debug("My keys key0=" + key[0] + " " + " key1=" + key[1]); for (int i = 0; i < (moveNumber - 1); i++) { if (keyHistory[i][0] == key[0] && keyHistory[i][1] == key[1]) { repetitions++; } if (repetitions >= 2) { // with the last one they are 3 return true; } } // Draw by no material to mate return (pawns == 0 && rooks == 0 && queens == 0) && ((bishops == 0 && knights == 0) || // (bishops == 0 && BitboardUtils.popCount(knights) == 1) || // (knights == 0 && BitboardUtils.popCount(bishops) == 1)); }
/** Converts board to its fen notation */ public String getFen() { StringBuilder sb = new StringBuilder(); long i = BitboardUtils.A8; int j = 0; while (i != 0) { char p = getPieceAt(i); if (p == '.') { j++; } if ((j != 0) && (p != '.' || ((i & BitboardUtils.b_r) != 0))) { sb.append(j); j = 0; } if (p != '.') { sb.append(p); } if ((i != 1) && (i & BitboardUtils.b_r) != 0) { sb.append("/"); } i >>>= 1; } sb.append(" "); sb.append((getTurn() ? "w" : "b")); sb.append(" "); if (getWhiteKingsideCastling()) { sb.append("K"); } if (getWhiteQueensideCastling()) { sb.append("Q"); } if (getBlackKingsideCastling()) { sb.append("k"); } if (getBlackQueensideCastling()) { sb.append("q"); } if (!getWhiteQueensideCastling() && !getWhiteKingsideCastling() && !getBlackQueensideCastling() && !getBlackKingsideCastling()) { sb.append("-"); } sb.append(" "); sb.append((getPassantSquare() != 0 ? BitboardUtils.square2Algebraic(getPassantSquare()) : "-")); sb.append(" "); sb.append(fiftyMovesRule); sb.append(" "); sb.append((moveNumber >> 1) + 1); // 0,1->1.. 2,3->2 return sb.toString(); }
/** The SWAP algorithm https://chessprogramming.wikispaces.com/SEE+-+The+Swap+Algorithm */ public int see(int fromIndex, int toIndex, int pieceMoved, int targetPiece) { int d = 0; long mayXray = pawns | bishops | rooks | queens; // not kings nor knights long fromSquare = 0x1L << fromIndex; long all = getAll(); long attacks = bbAttacks.getIndexAttacks(this, toIndex); long fromCandidates; seeGain[d] = SEE_PIECE_VALUES[targetPiece]; do { long side = (d & 1) == 0 ? getOthers() : getMines(); d++; // next depth and side // speculative store, if defended seeGain[d] = SEE_PIECE_VALUES[pieceMoved] - seeGain[d - 1]; attacks ^= fromSquare; // reset bit in set to traverse all ^= fromSquare; // reset bit in temporary occupancy (for X-Rays) if ((fromSquare & mayXray) != 0) { attacks |= bbAttacks.getXrayAttacks(this, toIndex, all); } // Gets the next attacker if ((fromCandidates = attacks & pawns & side) != 0) { pieceMoved = Piece.PAWN; } else if ((fromCandidates = attacks & knights & side) != 0) { pieceMoved = Piece.KNIGHT; } else if ((fromCandidates = attacks & bishops & side) != 0) { pieceMoved = Piece.BISHOP; } else if ((fromCandidates = attacks & rooks & side) != 0) { pieceMoved = Piece.ROOK; } else if ((fromCandidates = attacks & queens & side) != 0) { pieceMoved = Piece.QUEEN; } else if ((fromCandidates = attacks & kings & side) != 0) { pieceMoved = Piece.KING; } fromSquare = BitboardUtils.lsb(fromCandidates); } while (fromSquare != 0); while (--d != 0) { seeGain[d - 1] = -Math.max(-seeGain[d - 1], seeGain[d]); } return seeGain[0]; }
/** * Moves and also updates the board's zobrist key verify legality, if not legal undo move and * return false 0 is the null move */ public boolean doMove(int move, boolean verify, boolean fillSanInfo) { if (move == -1) { return false; } // Save history saveHistory(move, fillSanInfo); int fromIndex = Move.getFromIndex(move); int toIndex = Move.getToIndex(move); long from = Move.getFromSquare(move); long to = Move.getToSquare(move); long moveMask = from | to; // Move is as easy as xor with this mask (exceptions are promotions, captures and // en-passant captures) int moveType = Move.getMoveType(move); int pieceMoved = Move.getPieceMoved(move); boolean capture = Move.isCapture(move); boolean turn = getTurn(); int color = (turn ? 0 : 1); // Count consecutive moves without capture or without pawn move fiftyMovesRule++; moveNumber++; // Count Ply moves // Remove passant flags: from the zobrist key if ((flags & FLAGS_PASSANT) != 0) { key[1 - color] ^= ZobristKey.passantFile[BitboardUtils.getFile(flags & FLAGS_PASSANT)]; } // and from the flags flags &= ~FLAGS_PASSANT; if (move != 0) { assert (from & getMines()) != 0 : "Origin square not valid"; // Is it is a capture, remove pieces in destination square if (capture) { fiftyMovesRule = 0; // En-passant pawn captures remove captured pawn, put the pawn in to int toIndexCapture = toIndex; if (moveType == Move.TYPE_PASSANT) { to = (getTurn() ? (to >>> 8) : (to << 8)); toIndexCapture += (getTurn() ? -8 : 8); } key[1 - color] ^= ZobristKey.getKeyPieceIndex(toIndexCapture, getPieceAt(to)); whites &= ~to; blacks &= ~to; pawns &= ~to; queens &= ~to; rooks &= ~to; bishops &= ~to; knights &= ~to; } // Pawn movements switch (pieceMoved) { case Piece.PAWN: fiftyMovesRule = 0; // Set new passant flags if pawn is advancing two squares (marks // the destination square where the pawn can be captured) // Set only passant flags when the other side can capture if (((from << 16) & to) != 0 && (bbAttacks.pawnUpwards[toIndex - 8] & pawns & getOthers()) != 0) { // white flags |= (from << 8); } if (((from >>> 16) & to) != 0 && (bbAttacks.pawnDownwards[toIndex + 8] & pawns & getOthers()) != 0) { // blask flags |= (from >>> 8); } if ((flags & FLAGS_PASSANT) != 0) { key[color] ^= ZobristKey.passantFile[BitboardUtils.getFile(flags & FLAGS_PASSANT)]; } if (moveType == Move.TYPE_PROMOTION_QUEEN || moveType == Move.TYPE_PROMOTION_KNIGHT || moveType == Move.TYPE_PROMOTION_BISHOP || moveType == Move.TYPE_PROMOTION_ROOK) { // Promotions: // change // the piece pawns &= ~from; key[color] ^= ZobristKey.pawn[color][fromIndex]; switch (moveType) { case Move.TYPE_PROMOTION_QUEEN: queens |= to; key[color] ^= ZobristKey.queen[color][toIndex]; break; case Move.TYPE_PROMOTION_KNIGHT: knights |= to; key[color] ^= ZobristKey.knight[color][toIndex]; break; case Move.TYPE_PROMOTION_BISHOP: bishops |= to; key[color] ^= ZobristKey.bishop[color][toIndex]; break; case Move.TYPE_PROMOTION_ROOK: rooks |= to; key[color] ^= ZobristKey.rook[color][toIndex]; break; } } else { pawns ^= moveMask; key[color] ^= ZobristKey.pawn[color][fromIndex] ^ ZobristKey.pawn[color][toIndex]; } break; case Piece.ROOK: rooks ^= moveMask; key[color] ^= ZobristKey.rook[color][fromIndex] ^ ZobristKey.rook[color][toIndex]; break; case Piece.BISHOP: bishops ^= moveMask; key[color] ^= ZobristKey.bishop[color][fromIndex] ^ ZobristKey.bishop[color][toIndex]; break; case Piece.KNIGHT: knights ^= moveMask; key[color] ^= ZobristKey.knight[color][fromIndex] ^ ZobristKey.knight[color][toIndex]; break; case Piece.QUEEN: queens ^= moveMask; key[color] ^= ZobristKey.queen[color][fromIndex] ^ ZobristKey.queen[color][toIndex]; break; case Piece.KING: // if castling, moves rooks too if (moveType == Move.TYPE_KINGSIDE_CASTLING || moveType == Move.TYPE_QUEENSIDE_CASTLING) { // {White Kingside, White Queenside, Black Kingside, Black Queenside} int j = (color << 1) + (moveType == Move.TYPE_QUEENSIDE_CASTLING ? 1 : 0); toIndex = CASTLING_KING_DESTINY_INDEX[j]; int originRookIndex = BitboardUtils.square2Index(castlingRooks[j]); int destinyRookIndex = CASTLING_ROOK_DESTINY_INDEX[j]; // Recalculate move mask for chess960 castlings moveMask = from ^ (1L << toIndex); long rookMoveMask = (1L << originRookIndex) ^ (1L << destinyRookIndex); key[color] ^= ZobristKey.rook[color][originRookIndex] ^ ZobristKey.rook[color][destinyRookIndex]; if (getTurn()) { whites ^= rookMoveMask; } else { blacks ^= rookMoveMask; } rooks ^= rookMoveMask; } kings ^= moveMask; key[color] ^= ZobristKey.king[color][fromIndex] ^ ZobristKey.king[color][toIndex]; break; } // Move pieces in colour fields if (getTurn()) { whites ^= moveMask; } else { blacks ^= moveMask; } // Tests to disable castling if ((flags & FLAG_WHITE_KINGSIDE_CASTLING) != 0 && // ((turn && pieceMoved == Piece.KING) || from == castlingRooks[0] || to == castlingRooks[0])) { flags &= ~FLAG_WHITE_KINGSIDE_CASTLING; key[0] ^= ZobristKey.whiteKingSideCastling; } if ((flags & FLAG_WHITE_QUEENSIDE_CASTLING) != 0 && // ((turn && pieceMoved == Piece.KING) || from == castlingRooks[1] || to == castlingRooks[1])) { flags &= ~FLAG_WHITE_QUEENSIDE_CASTLING; key[0] ^= ZobristKey.whiteQueenSideCastling; } if ((flags & FLAG_BLACK_KINGSIDE_CASTLING) != 0 && // ((!turn && pieceMoved == Piece.KING) || from == castlingRooks[2] || to == castlingRooks[2])) { flags &= ~FLAG_BLACK_KINGSIDE_CASTLING; key[1] ^= ZobristKey.blackKingSideCastling; } if ((flags & FLAG_BLACK_QUEENSIDE_CASTLING) != 0 && // ((!turn && pieceMoved == Piece.KING) || from == castlingRooks[3] || to == castlingRooks[3])) { flags &= ~FLAG_BLACK_QUEENSIDE_CASTLING; key[1] ^= ZobristKey.blackQueenSideCastling; } } // Change turn flags ^= FLAG_TURN; key[0] ^= ZobristKey.whiteMove; if (verify) { if (isValid()) { setCheckFlags(); if (fillSanInfo) { if (isMate()) { // Append # when mate sanMoves.put(moveNumber - 1, sanMoves.get(moveNumber - 1).replace("+", "#")); } } } else { undoMove(); return false; } } else { // Trust move check flag if (Move.isCheck(move)) { flags |= FLAG_CHECK; } else { flags &= ~FLAG_CHECK; } } return true; }
/** Sets fen without destroying move history. If lastMove = null destroy the move history */ public void setFenMove(String fen, String lastMove) { long tmpWhites = 0; long tmpBlacks = 0; long tmpPawns = 0; long tmpRooks = 0; long tmpQueens = 0; long tmpBishops = 0; long tmpKnights = 0; long tmpKings = 0; long tmpFlags; int tmpFiftyMovesRule = 0; long tmpCastlingRooks[] = {0, 0, 0, 0}; int fenMoveNumber = 0; int i = 0; long j = BitboardUtils.A8; String[] tokens = fen.split("[ \\t\\n\\x0B\\f\\r]+"); String board = tokens[0]; while ((i < board.length()) && (j != 0)) { char p = board.charAt(i++); if (p != '/') { int number = 0; try { number = Integer.parseInt(String.valueOf(p)); } catch (Exception ignored) { } for (int k = 0; k < (number == 0 ? 1 : number); k++) { tmpWhites = (tmpWhites & ~j) | ((number == 0) && (p == Character.toUpperCase(p)) ? j : 0); tmpBlacks = (tmpBlacks & ~j) | ((number == 0) && (p == Character.toLowerCase(p)) ? j : 0); tmpPawns = (tmpPawns & ~j) | (Character.toUpperCase(p) == 'P' ? j : 0); tmpRooks = (tmpRooks & ~j) | (Character.toUpperCase(p) == 'R' ? j : 0); tmpQueens = (tmpQueens & ~j) | (Character.toUpperCase(p) == 'Q' ? j : 0); tmpBishops = (tmpBishops & ~j) | (Character.toUpperCase(p) == 'B' ? j : 0); tmpKnights = (tmpKnights & ~j) | (Character.toUpperCase(p) == 'N' ? j : 0); tmpKings = (tmpKings & ~j) | (Character.toUpperCase(p) == 'K' ? j : 0); j >>>= 1; if (j == 0) { break; // security } } } } // Now the rest ... String turn = tokens[1]; tmpFlags = 0; if ("b".equals(turn)) { tmpFlags |= FLAG_TURN; } if (tokens.length > 2) { // Set castling rights supporting XFEN to disambiguate positions in Chess960 String castlings = tokens[2]; chess960 = false; // Squares to the sides of the kings {White Kingside, White Queenside, Black Kingside, Black // Queenside} long whiteKingLateralSquares[] = { BitboardUtils.b_d & ((tmpKings & tmpWhites) - 1), BitboardUtils.b_d & ~(((tmpKings & tmpWhites) - 1) | tmpKings & tmpWhites), BitboardUtils.b_u & ((tmpKings & tmpBlacks) - 1), BitboardUtils.b_u & ~(((tmpKings & tmpBlacks) - 1) | tmpKings & tmpBlacks) }; // Squares where we can find a castling rook long possibleCastlingRookSquares[] = {0, 0, 0, 0}; for (int k = 0; k < castlings.length(); k++) { char c = castlings.charAt(k); switch (c) { case 'K': possibleCastlingRookSquares[0] = whiteKingLateralSquares[0]; break; case 'Q': possibleCastlingRookSquares[1] = whiteKingLateralSquares[1]; break; case 'k': possibleCastlingRookSquares[2] = whiteKingLateralSquares[2]; break; case 'q': possibleCastlingRookSquares[3] = whiteKingLateralSquares[3]; break; default: // Shredder-FEN receives the name of the file where the castling rook is int whiteFile = "ABCDEFGH".indexOf(c); int blackFile = "abcdefgh".indexOf(c); if (whiteFile >= 0) { long rookSquare = BitboardUtils.b_d & BitboardUtils.FILE[whiteFile]; if ((rookSquare & whiteKingLateralSquares[0]) != 0) { possibleCastlingRookSquares[0] = rookSquare; } else if ((rookSquare & whiteKingLateralSquares[1]) != 0) { possibleCastlingRookSquares[1] = rookSquare; } } else if (blackFile >= 0) { long rookSquare = BitboardUtils.b_u & BitboardUtils.FILE[blackFile]; if ((rookSquare & whiteKingLateralSquares[2]) != 0) { possibleCastlingRookSquares[2] = rookSquare; } else if ((rookSquare & whiteKingLateralSquares[3]) != 0) { possibleCastlingRookSquares[3] = rookSquare; } } } } // Now store the squares of the castling rooks tmpCastlingRooks[0] = BitboardUtils.lsb(tmpRooks & tmpWhites & possibleCastlingRookSquares[0]); tmpCastlingRooks[1] = BitboardUtils.msb(tmpRooks & tmpWhites & possibleCastlingRookSquares[1]); tmpCastlingRooks[2] = BitboardUtils.lsb(tmpRooks & tmpBlacks & possibleCastlingRookSquares[2]); tmpCastlingRooks[3] = BitboardUtils.msb(tmpRooks & tmpBlacks & possibleCastlingRookSquares[3]); // Set the castling flags and detect Chess960 if (tmpCastlingRooks[0] != 0) { tmpFlags |= FLAG_WHITE_KINGSIDE_CASTLING; if ((tmpWhites & tmpKings) != 1L << 3 || tmpCastlingRooks[0] != 1L) { chess960 = true; } } if (tmpCastlingRooks[1] != 0) { tmpFlags |= FLAG_WHITE_QUEENSIDE_CASTLING; if ((tmpWhites & tmpKings) != 1L << 3 || tmpCastlingRooks[1] != 1L << 7) { chess960 = true; } } if (tmpCastlingRooks[2] != 0) { tmpFlags |= FLAG_BLACK_KINGSIDE_CASTLING; if ((tmpBlacks & tmpKings) != 1L << 59 || tmpCastlingRooks[2] != 1L << 56) { chess960 = true; } } if (tmpCastlingRooks[3] != 0) { tmpFlags |= FLAG_BLACK_QUEENSIDE_CASTLING; if ((tmpBlacks & tmpKings) != 1L << 59 || tmpCastlingRooks[3] != 1L << 63) { chess960 = true; } } // END FEN castlings if (tokens.length > 3) { String passant = tokens[3]; tmpFlags |= FLAGS_PASSANT & BitboardUtils.algebraic2Square(passant); if (tokens.length > 4) { try { tmpFiftyMovesRule = Integer.parseInt(tokens[4]); } catch (Exception e) { tmpFiftyMovesRule = 0; } if (tokens.length > 5) { String moveNumberString = tokens[5]; int aux = Integer.parseInt(moveNumberString); fenMoveNumber = ((aux > 0 ? aux - 1 : aux) << 1) + ((tmpFlags & FLAG_TURN) == 0 ? 0 : 1); if (fenMoveNumber < 0) { fenMoveNumber = 0; } } } } } // try to apply the last move to see if we are advancing or undoing moves if ((moveNumber + 1) == fenMoveNumber && lastMove != null) { doMove(Move.getFromString(this, lastMove, true)); } else if (fenMoveNumber < moveNumber) { for (int k = moveNumber; k > fenMoveNumber; k--) { undoMove(); } } // Check if board changed or if we can keep the history if (whites != tmpWhites // || blacks != tmpBlacks // || pawns != tmpPawns // || rooks != tmpRooks // || queens != tmpQueens // || bishops != tmpBishops // || knights != tmpKnights // || kings != tmpKings // || (flags & FLAG_TURN) != (tmpFlags & FLAG_TURN)) { // board reset sanMoves.clear(); initialFen = fen; initialMoveNumber = fenMoveNumber; moveNumber = fenMoveNumber; outBookMove = Integer.MAX_VALUE; whites = tmpWhites; blacks = tmpBlacks; pawns = tmpPawns; rooks = tmpRooks; queens = tmpQueens; bishops = tmpBishops; knights = tmpKnights; kings = tmpKings; fiftyMovesRule = tmpFiftyMovesRule; // Flags are not completed till verify, so skip checking flags = tmpFlags; castlingRooks[0] = tmpCastlingRooks[0]; castlingRooks[1] = tmpCastlingRooks[1]; castlingRooks[2] = tmpCastlingRooks[2]; castlingRooks[3] = tmpCastlingRooks[3]; // Set zobrist key and check flags key = ZobristKey.getKey(this); setCheckFlags(); // and save history resetHistory(); saveHistory(0, false); } else { if (moveNumber < outBookMove) { outBookMove = Integer.MAX_VALUE; } } }