X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fposition.cpp;h=8f70b5dfd3ab3af4c644072396622c75257ce381;hp=e6261d3c661e9be9af618ec05e2e97c4aeab7d30;hb=b21a5e2f0638a55daeaa98ba95afc6c016ea0b6e;hpb=f5e28ef5127ff29310f9c2e8e321eb24214f2439 diff --git a/src/position.cpp b/src/position.cpp index e6261d3c..8f70b5df 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -17,26 +17,18 @@ along with this program. If not, see . */ - -//// -//// Includes -//// - -#include #include #include #include -#include #include #include #include "bitcount.h" #include "movegen.h" -#include "movepick.h" #include "position.h" #include "psqtab.h" #include "rkiss.h" -#include "san.h" +#include "thread.h" #include "tt.h" #include "ucioption.h" @@ -44,43 +36,6 @@ using std::string; using std::cout; using std::endl; -static inline bool isZero(char c) { return c == '0'; } - -struct PieceLetters : public std::map { - - PieceLetters() { - - operator[]('K') = WK; operator[]('k') = BK; - operator[]('Q') = WQ; operator[]('q') = BQ; - operator[]('R') = WR; operator[]('r') = BR; - operator[]('B') = WB; operator[]('b') = BB; - operator[]('N') = WN; operator[]('n') = BN; - operator[]('P') = WP; operator[]('p') = BP; - operator[](' ') = PIECE_NONE; operator[]('.') = PIECE_NONE_DARK_SQ; - } - - char from_piece(Piece p) const { - - std::map::const_iterator it; - for (it = begin(); it != end(); ++it) - if (it->second == p) - return it->first; - - assert(false); - return 0; - } -}; - - -//// -//// Constants and variables -//// - -/// Bonus for having the side to move (modified by Joona Kiiski) - -static const Score TempoValue = make_score(48, 22); - - Key Position::zobrist[2][8][64]; Key Position::zobEp[64]; Key Position::zobCastle[16]; @@ -89,16 +44,44 @@ Key Position::zobExclusion; Score Position::PieceSquareTable[16][64]; -static PieceLetters pieceLetters; +// Material values arrays, indexed by Piece +const Value Position::PieceValueMidgame[17] = { + VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame +}; + +const Value Position::PieceValueEndgame[17] = { + VALUE_ZERO, + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame +}; -// Material values used by SEE, indexed by PieceType +// Material values array used by SEE, indexed by PieceType const Value Position::seeValues[] = { - VALUE_ZERO, PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, - RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10 + VALUE_ZERO, + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, QueenValueMidgame*10 }; -/// Constructors +namespace { + + // Bonus for having the side to move (modified by Joona Kiiski) + const Score TempoValue = make_score(48, 22); + + // To convert a Piece to and from a FEN char + const string PieceToChar(".PNBRQK pnbrqk "); +} + + +/// CheckInfo c'tor CheckInfo::CheckInfo(const Position& pos) { @@ -129,9 +112,9 @@ Position::Position(const Position& pos, int th) { nodes = 0; } -Position::Position(const string& fen, int th) { +Position::Position(const string& fen, bool isChess960, int th) { - from_fen(fen); + from_fen(fen, isChess960); threadID = th; } @@ -152,14 +135,14 @@ void Position::detach() { /// string. This function is not very robust - make sure that input FENs are /// correct (this is assumed to be the responsibility of the GUI). -void Position::from_fen(const string& fen) { +void Position::from_fen(const string& fen, bool isChess960) { /* A FEN string defines a particular position using only the ASCII character set. A FEN string contains six fields. The separator between fields is a space. The fields are: 1) Piece placement (from white's perspective). Each rank is described, starting with rank 8 and ending - with rank 1; within each rank, the contents of each square are described from file a through file h. + with rank 1; within each rank, the contents of each square are described from file A through file H. Following the Standard Algebraic Notation (SAN), each piece is identified by a single letter taken from the standard English names. White pieces are designated using upper-case letters ("PNBRQK") while Black take lowercase ("pnbrqk"). Blank squares are noted using digits 1 through 8 (the number @@ -182,66 +165,64 @@ void Position::from_fen(const string& fen) { */ char token; + int hmc, fmn; + size_t p; + Square sq = SQ_A8; std::istringstream ss(fen); - Rank rank = RANK_8; - File file = FILE_A; clear(); + ss >> std::noskipws; // 1. Piece placement field - while (ss.get(token) && token != ' ') + while ((ss >> token) && !isspace(token)) { - if (isdigit(token)) + if ((p = PieceToChar.find(token)) != string::npos) { - file += File(token - '0'); // Skip the given number of files - continue; + put_piece(Piece(p), sq); + sq++; } + else if (isdigit(token)) + sq += Square(token - '0'); // Skip the given number of files else if (token == '/') - { - file = FILE_A; - rank--; - continue; - } - - if (pieceLetters.find(token) == pieceLetters.end()) + sq -= SQ_A3; // Jump back of 2 rows + else goto incorrect_fen; - - put_piece(pieceLetters[token], make_square(file, rank)); - file++; } // 2. Active color - if (!ss.get(token) || (token != 'w' && token != 'b')) + if (!(ss >> token) || (token != 'w' && token != 'b')) goto incorrect_fen; sideToMove = (token == 'w' ? WHITE : BLACK); - if (!ss.get(token) || token != ' ') + if (!(ss >> token) || !isspace(token)) goto incorrect_fen; // 3. Castling availability - while (ss.get(token) && token != ' ') - { - if (token == '-') - continue; - + while ((ss >> token) && !isspace(token)) if (!set_castling_rights(token)) goto incorrect_fen; - } - // 4. En passant square -- ignore if no capture is possible + // 4. En passant square char col, row; - if ( (ss.get(col) && (col >= 'a' && col <= 'h')) - && (ss.get(row) && (row == '3' || row == '6'))) + if ( ((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == '3' || row == '6'))) { - Square fenEpSquare = make_square(file_from_char(col), rank_from_char(row)); - Color them = opposite_color(sideToMove); + st->epSquare = make_square(file_from_char(col), rank_from_char(row)); - if (attacks_from(fenEpSquare, them) & pieces(PAWN, sideToMove)) - st->epSquare = fenEpSquare; + // Ignore if no capture is possible + Color them = opposite_color(sideToMove); + if (!(attacks_from(st->epSquare, them) & pieces(PAWN, sideToMove))) + st->epSquare = SQ_NONE; } - // 5-6. Halfmove clock and fullmove number are not parsed + // 5. Halfmove clock + if (ss >> std::skipws >> hmc) + st->rule50 = hmc; + + // 6. Fullmove number + if (ss >> fmn) + startPosPlyCounter = (fmn - 1) * 2 + int(sideToMove == BLACK); // Various initialisations castleRightsMask[make_square(initialKFile, RANK_1)] ^= WHITE_OO | WHITE_OOO; @@ -251,10 +232,7 @@ void Position::from_fen(const string& fen) { castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO; castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO; - isChess960 = initialKFile != FILE_E - || initialQRFile != FILE_A - || initialKRFile != FILE_H; - + chess960 = isChess960; find_checkers(); st->key = compute_key(); @@ -292,7 +270,7 @@ bool Position::set_castling_rights(char token) { for (Square sq = sqH; sq >= sqA; sq--) if (piece_on(sq) == rook) { - allow_oo(c); + set_castle_kingside(c); initialKRFile = square_file(sq); break; } @@ -302,7 +280,7 @@ bool Position::set_castling_rights(char token) { for (Square sq = sqA; sq <= sqH; sq++) if (piece_on(sq) == rook) { - allow_ooo(c); + set_castle_queenside(c); initialQRFile = square_file(sq); break; } @@ -312,16 +290,17 @@ bool Position::set_castling_rights(char token) { File rookFile = File(token - 'A') + FILE_A; if (rookFile < initialKFile) { - allow_ooo(c); + set_castle_queenside(c); initialQRFile = rookFile; } else { - allow_oo(c); + set_castle_kingside(c); initialKRFile = rookFile; } } - else return false; + else + return token == '-'; return true; } @@ -334,44 +313,47 @@ const string Position::to_fen() const { string fen; Square sq; - char emptyCnt = '0'; + char emptyCnt; - for (Rank rank = RANK_8; rank >= RANK_1; rank--) + for (Rank rank = RANK_8; rank >= RANK_1; rank--, fen += '/') { + emptyCnt = '0'; + for (File file = FILE_A; file <= FILE_H; file++) { sq = make_square(file, rank); if (square_is_occupied(sq)) { - fen += emptyCnt; - fen += pieceLetters.from_piece(piece_on(sq)); - emptyCnt = '0'; + if (emptyCnt != '0') + { + fen += emptyCnt; + emptyCnt = '0'; + } + fen += PieceToChar[piece_on(sq)]; } else emptyCnt++; } - fen += emptyCnt; - fen += '/'; - emptyCnt = '0'; + + if (emptyCnt != '0') + fen += emptyCnt; } - fen.erase(std::remove_if(fen.begin(), fen.end(), isZero), fen.end()); - fen.erase(--fen.end()); fen += (sideToMove == WHITE ? " w " : " b "); if (st->castleRights != CASTLES_NONE) { if (can_castle_kingside(WHITE)) - fen += isChess960 ? char(toupper(file_to_char(initialKRFile))) : 'K'; + fen += chess960 ? char(toupper(file_to_char(initialKRFile))) : 'K'; if (can_castle_queenside(WHITE)) - fen += isChess960 ? char(toupper(file_to_char(initialQRFile))) : 'Q'; + fen += chess960 ? char(toupper(file_to_char(initialQRFile))) : 'Q'; if (can_castle_kingside(BLACK)) - fen += isChess960 ? file_to_char(initialKRFile) : 'k'; + fen += chess960 ? file_to_char(initialKRFile) : 'k'; if (can_castle_queenside(BLACK)) - fen += isChess960 ? file_to_char(initialQRFile) : 'q'; + fen += chess960 ? file_to_char(initialQRFile) : 'q'; } else fen += '-'; @@ -386,14 +368,6 @@ const string Position::to_fen() const { void Position::print(Move move) const { const char* dottedLine = "\n+---+---+---+---+---+---+---+---+\n"; - static bool requestPending = false; - - // Check for reentrancy, as example when called from inside - // MovePicker that is used also here in move_to_san() - if (requestPending) - return; - - requestPending = true; if (move) { @@ -408,17 +382,16 @@ void Position::print(Move move) const { for (File file = FILE_A; file <= FILE_H; file++) { Square sq = make_square(file, rank); - char c = (color_of_piece_on(sq) == BLACK ? '=' : ' '); Piece piece = piece_on(sq); if (piece == PIECE_NONE && square_color(sq) == DARK) piece = PIECE_NONE_DARK_SQ; - cout << c << pieceLetters.from_piece(piece) << c << '|'; + char c = (color_of_piece_on(sq) == BLACK ? '=' : ' '); + cout << c << PieceToChar[piece] << c << '|'; } } cout << dottedLine << "Fen is: " << to_fen() << "\nKey is: " << st->key << endl; - requestPending = false; } @@ -492,6 +465,16 @@ Bitboard Position::attackers_to(Square s) const { | (attacks_from(s) & pieces(KING)); } +Bitboard Position::attackers_to(Square s, Bitboard occ) const { + + return (attacks_from(s, BLACK) & pieces(PAWN, WHITE)) + | (attacks_from(s, WHITE) & pieces(PAWN, BLACK)) + | (attacks_from(s) & pieces(KNIGHT)) + | (rook_attacks_bb(s, occ) & pieces(ROOK, QUEEN)) + | (bishop_attacks_bb(s, occ) & pieces(BISHOP, QUEEN)) + | (attacks_from(s) & pieces(KING)); +} + /// Position::attacks_from() computes a bitboard of all attacks /// of a given piece put in a given square. @@ -501,16 +484,24 @@ Bitboard Position::attacks_from(Piece p, Square s) const { switch (p) { - case WP: return attacks_from(s, WHITE); - case BP: return attacks_from(s, BLACK); - case WN: case BN: return attacks_from(s); case WB: case BB: return attacks_from(s); case WR: case BR: return attacks_from(s); case WQ: case BQ: return attacks_from(s); - case WK: case BK: return attacks_from(s); - default: break; + default: return StepAttacksBB[p][s]; + } +} + +Bitboard Position::attacks_from(Piece p, Square s, Bitboard occ) { + + assert(square_is_ok(s)); + + switch (p) + { + case WB: case BB: return bishop_attacks_bb(s, occ); + case WR: case BR: return rook_attacks_bb(s, occ); + case WQ: case BQ: return bishop_attacks_bb(s, occ) | rook_attacks_bb(s, occ); + default: return StepAttacksBB[p][s]; } - return false; } @@ -564,15 +555,11 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { assert(move_is_ok(m)); assert(pinned == pinned_pieces(side_to_move())); - // Castling moves are checked for legality during move generation. - if (move_is_castle(m)) - return true; - Color us = side_to_move(); Square from = move_from(m); assert(color_of_piece_on(from) == us); - assert(piece_on(king_square(us)) == piece_of_color_and_type(us, KING)); + assert(piece_on(king_square(us)) == make_piece(us, KING)); // En passant captures are a tricky special case. Because they are // rather uncommon, we do it simply by testing whether the king is attacked @@ -582,12 +569,12 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { Color them = opposite_color(us); Square to = move_to(m); Square capsq = make_square(square_file(to), square_rank(from)); - Bitboard b = occupied_squares(); Square ksq = king_square(us); + Bitboard b = occupied_squares(); assert(to == ep_square()); - assert(piece_on(from) == piece_of_color_and_type(us, PAWN)); - assert(piece_on(capsq) == piece_of_color_and_type(them, PAWN)); + assert(piece_on(from) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(them, PAWN)); assert(piece_on(to) == PIECE_NONE); clear_bit(&b, from); @@ -599,58 +586,178 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { } // If the moving piece is a king, check whether the destination - // square is attacked by the opponent. + // square is attacked by the opponent. Castling moves are checked + // for legality during move generation. if (type_of_piece_on(from) == KING) - return !(attackers_to(move_to(m)) & pieces_of_color(opposite_color(us))); + return move_is_castle(m) || !(attackers_to(move_to(m)) & pieces_of_color(opposite_color(us))); // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return ( !pinned - || !bit_is_set(pinned, from) - || (direction_between_squares(from, king_square(us)) == direction_between_squares(move_to(m), king_square(us)))); + return !pinned + || !bit_is_set(pinned, from) + || squares_aligned(from, move_to(m), king_square(us)); } -/// Position::pl_move_is_evasion() tests whether a pseudo-legal move is a legal evasion +/// Position::move_is_pl_slow() takes a position and a move and tests whether +/// the move is pseudo legal. This version is not very fast and should be used +/// only in non time-critical paths. -bool Position::pl_move_is_evasion(Move m, Bitboard pinned) const -{ - assert(is_check()); +bool Position::move_is_pl_slow(const Move m) const { - Color us = side_to_move(); + MoveStack mlist[MAX_MOVES]; + MoveStack *cur, *last; + + last = in_check() ? generate(*this, mlist) + : generate(*this, mlist); + + for (cur = mlist; cur != last; cur++) + if (cur->move == m) + return true; + + return false; +} + + +/// Fast version of Position::move_is_pl() that takes a position a move and a +/// bitboard of pinned pieces as input, and tests whether the move is pseudo legal. + +bool Position::move_is_pl(const Move m) const { + + assert(is_ok()); + + Color us = sideToMove; + Color them = opposite_color(sideToMove); Square from = move_from(m); Square to = move_to(m); + Piece pc = piece_on(from); - // King moves and en-passant captures are verified in pl_move_is_legal() - if (type_of_piece_on(from) == KING || move_is_ep(m)) - return pl_move_is_legal(m, pinned); + // Use a slower but simpler function for uncommon cases + if (move_is_special(m)) + return move_is_pl_slow(m); - Bitboard target = checkers(); - Square checksq = pop_1st_bit(&target); + // Is not a promotion, so promotion piece must be empty + if (promotion_piece_type(m) - 2 != PIECE_TYPE_NONE) + return false; - if (target) // double check ? + // If the from square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == PIECE_NONE || color_of_piece(pc) != us) return false; - // Our move must be a blocking evasion or a capture of the checking piece - target = squares_between(checksq, king_square(us)) | checkers(); - return bit_is_set(target, to) && pl_move_is_legal(m, pinned); + // The destination square cannot be occupied by a friendly piece + if (color_of_piece_on(to) == us) + return false; + + // Handle the special case of a pawn move + if (type_of_piece(pc) == PAWN) + { + // Move direction must be compatible with pawn color + int direction = to - from; + if ((us == WHITE) != (direction > 0)) + return false; + + // We have already handled promotion moves, so destination + // cannot be on the 8/1th rank. + if (square_rank(to) == RANK_8 || square_rank(to) == RANK_1) + return false; + + // Proceed according to the square delta between the origin and + // destination squares. + switch (direction) + { + case DELTA_NW: + case DELTA_NE: + case DELTA_SW: + case DELTA_SE: + // Capture. The destination square must be occupied by an enemy + // piece (en passant captures was handled earlier). + if (color_of_piece_on(to) != them) + return false; + + // From and to files must be one file apart, avoids a7h5 + if (abs(square_file(from) - square_file(to)) != 1) + return false; + break; + + case DELTA_N: + case DELTA_S: + // Pawn push. The destination square must be empty. + if (!square_is_empty(to)) + return false; + break; + + case DELTA_NN: + // Double white pawn push. The destination square must be on the fourth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if ( square_rank(to) != RANK_4 + || !square_is_empty(to) + || !square_is_empty(from + DELTA_N)) + return false; + break; + + case DELTA_SS: + // Double black pawn push. The destination square must be on the fifth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if ( square_rank(to) != RANK_5 + || !square_is_empty(to) + || !square_is_empty(from + DELTA_S)) + return false; + break; + + default: + return false; + } + } + else if (!bit_is_set(attacks_from(pc, from), to)) + return false; + + if (in_check()) + { + // In case of king moves under check we have to remove king so to catch + // as invalid moves like b1a1 when opposite queen is on c1. + if (type_of_piece_on(from) == KING) + { + Bitboard b = occupied_squares(); + clear_bit(&b, from); + if (attackers_to(move_to(m), b) & pieces_of_color(opposite_color(us))) + return false; + } + else + { + Bitboard target = checkers(); + Square checksq = pop_1st_bit(&target); + + if (target) // double check ? In this case a king move is required + return false; + + // Our move must be a blocking evasion or a capture of the checking piece + target = squares_between(checksq, king_square(us)) | checkers(); + if (!bit_is_set(target, move_to(m))) + return false; + } + } + + return true; } -/// Position::move_is_check() tests whether a pseudo-legal move is a check +/// Position::move_gives_check() tests whether a pseudo-legal move is a check -bool Position::move_is_check(Move m) const { +bool Position::move_gives_check(Move m) const { - return move_is_check(m, CheckInfo(*this)); + return move_gives_check(m, CheckInfo(*this)); } -bool Position::move_is_check(Move m, const CheckInfo& ci) const { +bool Position::move_gives_check(Move m, const CheckInfo& ci) const { assert(is_ok()); assert(move_is_ok(m)); assert(ci.dcCandidates == discovered_check_candidates(side_to_move())); assert(color_of_piece_on(move_from(m)) == side_to_move()); - assert(piece_on(ci.ksq) == piece_of_color_and_type(opposite_color(side_to_move()), KING)); + assert(piece_on(ci.ksq) == make_piece(opposite_color(side_to_move()), KING)); Square from = move_from(m); Square to = move_to(m); @@ -665,7 +772,7 @@ bool Position::move_is_check(Move m, const CheckInfo& ci) const { { // For pawn and king moves we need to verify also direction if ( (pt != PAWN && pt != KING) - ||(direction_between_squares(from, ci.ksq) != direction_between_squares(to, ci.ksq))) + || !squares_aligned(from, to, ci.ksq)) return true; } @@ -681,7 +788,7 @@ bool Position::move_is_check(Move m, const CheckInfo& ci) const { { clear_bit(&b, from); - switch (move_promotion_piece(m)) + switch (promotion_piece_type(m)) { case KNIGHT: return bit_is_set(attacks_from(to), ci.ksq); @@ -736,20 +843,44 @@ bool Position::move_is_check(Move m, const CheckInfo& ci) const { } +/// Position::do_setup_move() makes a permanent move on the board. It should +/// be used when setting up a position on board. You can't undo the move. + +void Position::do_setup_move(Move m) { + + StateInfo newSt; + + do_move(m, newSt); + + // Reset "game ply" in case we made a non-reversible move. + // "game ply" is used for repetition detection. + if (st->rule50 == 0) + st->gamePly = 0; + + // Update the number of plies played from the starting position + startPosPlyCounter++; + + // Our StateInfo newSt is about going out of scope so copy + // its content before it disappears. + detach(); +} + + /// Position::do_move() makes a move, and saves all information necessary -/// to a StateInfo object. The move is assumed to be legal. -/// Pseudo-legal moves should be filtered out before this function is called. +/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +/// moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt) { CheckInfo ci(*this); - do_move(m, newSt, ci, move_is_check(m, ci)); + do_move(m, newSt, ci, move_gives_check(m, ci)); } void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveIsCheck) { assert(is_ok()); assert(move_is_ok(m)); + assert(&newSt != st); nodes++; Key key = st->key; @@ -765,8 +896,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI Value npMaterial[2]; }; - if (&newSt != st) - memcpy(&newSt, st, sizeof(ReducedStateInfo)); + memcpy(&newSt, st, sizeof(ReducedStateInfo)); newSt.previous = st; st = &newSt; @@ -803,7 +933,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI assert(color_of_piece_on(from) == us); assert(color_of_piece_on(to) == them || square_is_empty(to)); - assert(!(ep || pm) || piece == piece_of_color_and_type(us, PAWN)); + assert(!(ep || pm) || piece == make_piece(us, PAWN)); assert(!pm || relative_rank(us, to) == RANK_8); if (capture) @@ -819,13 +949,12 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI st->epSquare = SQ_NONE; } - // Update castle rights, try to shortcut a common case - int cm = castleRightsMask[from] & castleRightsMask[to]; - if (cm != ALL_CASTLES && ((cm & st->castleRights) != st->castleRights)) + // Update castle rights if needed + if ( st->castleRights != CASTLES_NONE + && (castleRightsMask[from] & castleRightsMask[to]) != ALL_CASTLES) { key ^= zobCastle[st->castleRights]; - st->castleRights &= castleRightsMask[from]; - st->castleRights &= castleRightsMask[to]; + st->castleRights &= castleRightsMask[from] & castleRightsMask[to]; key ^= zobCastle[st->castleRights]; } @@ -855,7 +984,6 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI // Update pawn hash key and prefetch in L1/L2 cache st->pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; - prefetchPawn(st->pawnKey, threadID); // Set en passant square, only if moved pawn can be captured if ((to ^ from) == 16) @@ -869,14 +997,14 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI if (pm) // promotion ? { - PieceType promotion = move_promotion_piece(m); + PieceType promotion = promotion_piece_type(m); assert(promotion >= KNIGHT && promotion <= QUEEN); // Insert promoted piece instead of pawn clear_bit(&(byTypeBB[PAWN]), to); set_bit(&(byTypeBB[promotion]), to); - board[to] = piece_of_color_and_type(us, promotion); + board[to] = make_piece(us, promotion); // Update piece counts pieceCount[us][promotion]++; @@ -908,6 +1036,10 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI } } + // Prefetch pawn and material hash tables + Threads[threadID].pawnTable.prefetch(st->pawnKey); + Threads[threadID].materialTable.prefetch(st->materialKey); + // Update incremental scores st->value += pst_delta(piece, from, to); @@ -970,7 +1102,7 @@ void Position::do_capture_move(Key& key, PieceType capture, Color them, Square t assert(to == st->epSquare); assert(relative_rank(opposite_color(them), to) == RANK_6); assert(piece_on(to) == PIECE_NONE); - assert(piece_on(capsq) == piece_of_color_and_type(them, PAWN)); + assert(piece_on(capsq) == make_piece(them, PAWN)); board[capsq] = PIECE_NONE; } @@ -1034,8 +1166,8 @@ void Position::do_castle_move(Move m) { Square rfrom = move_to(m); // HACK: See comment at beginning of function Square kto, rto; - assert(piece_on(kfrom) == piece_of_color_and_type(us, KING)); - assert(piece_on(rfrom) == piece_of_color_and_type(us, ROOK)); + assert(piece_on(kfrom) == make_piece(us, KING)); + assert(piece_on(rfrom) == make_piece(us, ROOK)); // Find destination squares for king and rook if (rfrom > kfrom) // O-O @@ -1064,8 +1196,8 @@ void Position::do_castle_move(Move m) { set_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares // Update board array - Piece king = piece_of_color_and_type(us, KING); - Piece rook = piece_of_color_and_type(us, ROOK); + Piece king = make_piece(us, KING); + Piece rook = make_piece(us, ROOK); board[kfrom] = board[rfrom] = PIECE_NONE; board[kto] = king; board[rto] = rook; @@ -1141,15 +1273,15 @@ void Position::undo_move(Move m) { assert(!pm || relative_rank(us, to) == RANK_8); assert(!ep || to == st->previous->epSquare); assert(!ep || relative_rank(us, to) == RANK_6); - assert(!ep || piece_on(to) == piece_of_color_and_type(us, PAWN)); + assert(!ep || piece_on(to) == make_piece(us, PAWN)); if (pm) // promotion ? { - PieceType promotion = move_promotion_piece(m); + PieceType promotion = promotion_piece_type(m); pt = PAWN; assert(promotion >= KNIGHT && promotion <= QUEEN); - assert(piece_on(to) == piece_of_color_and_type(us, promotion)); + assert(piece_on(to) == make_piece(us, promotion)); // Replace promoted piece with a pawn clear_bit(&(byTypeBB[promotion]), to); @@ -1174,7 +1306,7 @@ void Position::undo_move(Move m) { do_move_bb(&(byTypeBB[pt]), move_bb); do_move_bb(&(byTypeBB[0]), move_bb); // HACK: byTypeBB[0] == occupied squares - board[from] = piece_of_color_and_type(us, pt); + board[from] = make_piece(us, pt); board[to] = PIECE_NONE; // Update piece list @@ -1196,7 +1328,7 @@ void Position::undo_move(Move m) { set_bit(&(byTypeBB[st->capturedType]), capsq); set_bit(&(byTypeBB[0]), capsq); - board[capsq] = piece_of_color_and_type(them, st->capturedType); + board[capsq] = make_piece(them, st->capturedType); // Update piece count pieceCount[them][st->capturedType]++; @@ -1243,8 +1375,8 @@ void Position::undo_castle_move(Move m) { rto = relative_square(us, SQ_D1); } - assert(piece_on(kto) == piece_of_color_and_type(us, KING)); - assert(piece_on(rto) == piece_of_color_and_type(us, ROOK)); + assert(piece_on(kto) == make_piece(us, KING)); + assert(piece_on(rto) == make_piece(us, ROOK)); // Remove pieces from destination squares: clear_bit(&(byColorBB[us]), kto); @@ -1264,8 +1396,8 @@ void Position::undo_castle_move(Move m) { // Update board board[rto] = board[kto] = PIECE_NONE; - board[rfrom] = piece_of_color_and_type(us, ROOK); - board[kfrom] = piece_of_color_and_type(us, KING); + board[rfrom] = make_piece(us, ROOK); + board[kfrom] = make_piece(us, KING); // Update piece lists pieceList[us][KING][index[kto]] = kfrom; @@ -1287,7 +1419,7 @@ void Position::undo_castle_move(Move m) { void Position::do_null_move(StateInfo& backupSt) { assert(is_ok()); - assert(!is_check()); + assert(!in_check()); // Back up the information necessary to undo the null move to the supplied // StateInfo object. @@ -1324,7 +1456,7 @@ void Position::do_null_move(StateInfo& backupSt) { void Position::undo_null_move() { assert(is_ok()); - assert(!is_check()); + assert(!in_check()); // Restore information from the our backup StateInfo object StateInfo* backupSt = st->previous; @@ -1347,12 +1479,6 @@ void Position::undo_null_move() { /// move, and one which takes a 'from' and a 'to' square. The function does /// not yet understand promotions captures. -int Position::see(Move m) const { - - assert(move_is_ok(m)); - return see(move_from(m), move_to(m)); -} - int Position::see_sign(Move m) const { assert(move_is_ok(m)); @@ -1366,49 +1492,47 @@ int Position::see_sign(Move m) const { if (midgame_value_of_piece_on(to) >= midgame_value_of_piece_on(from)) return 1; - return see(from, to); + return see(m); } -int Position::see(Square from, Square to) const { +int Position::see(Move m) const { - Bitboard occ, attackers, stmAttackers, b; + Square from, to; + Bitboard occupied, attackers, stmAttackers, b; int swapList[32], slIndex = 1; PieceType capturedType, pt; Color stm; - assert(square_is_ok(from)); - assert(square_is_ok(to)); - - capturedType = type_of_piece_on(to); + assert(move_is_ok(m)); - // King cannot be recaptured - if (capturedType == KING) - return seeValues[capturedType]; + // As castle moves are implemented as capturing the rook, they have + // SEE == RookValueMidgame most of the times (unless the rook is under + // attack). + if (move_is_castle(m)) + return 0; - occ = occupied_squares(); + from = move_from(m); + to = move_to(m); + capturedType = type_of_piece_on(to); + occupied = occupied_squares(); // Handle en passant moves if (st->epSquare == to && type_of_piece_on(from) == PAWN) { - Square capQq = (side_to_move() == WHITE) ? (to - DELTA_N) : (to - DELTA_S); + Square capQq = (side_to_move() == WHITE ? to - DELTA_N : to - DELTA_S); assert(capturedType == PIECE_TYPE_NONE); assert(type_of_piece_on(capQq) == PAWN); // Remove the captured pawn - clear_bit(&occ, capQq); + clear_bit(&occupied, capQq); capturedType = PAWN; } // Find all attackers to the destination square, with the moving piece // removed, but possibly an X-ray attacker added behind it. - clear_bit(&occ, from); - attackers = (rook_attacks_bb(to, occ) & pieces(ROOK, QUEEN)) - | (bishop_attacks_bb(to, occ) & pieces(BISHOP, QUEEN)) - | (attacks_from(to) & pieces(KNIGHT)) - | (attacks_from(to) & pieces(KING)) - | (attacks_from(to, WHITE) & pieces(PAWN, BLACK)) - | (attacks_from(to, BLACK) & pieces(PAWN, WHITE)); + clear_bit(&occupied, from); + attackers = attackers_to(to, occupied); // If the opponent has no attackers we are finished stm = opposite_color(color_of_piece_on(from)); @@ -1432,28 +1556,28 @@ int Position::see(Square from, Square to) const { for (pt = PAWN; !(stmAttackers & pieces(pt)); pt++) assert(pt < KING); - // Remove the attacker we just found from the 'attackers' bitboard, + // Remove the attacker we just found from the 'occupied' bitboard, // and scan for new X-ray attacks behind the attacker. b = stmAttackers & pieces(pt); - occ ^= (b & (~b + 1)); - attackers |= (rook_attacks_bb(to, occ) & pieces(ROOK, QUEEN)) - | (bishop_attacks_bb(to, occ) & pieces(BISHOP, QUEEN)); + occupied ^= (b & (~b + 1)); + attackers |= (rook_attacks_bb(to, occupied) & pieces(ROOK, QUEEN)) + | (bishop_attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)); - attackers &= occ; // Cut out pieces we've already done + attackers &= occupied; // Cut out pieces we've already done // Add the new entry to the swap list assert(slIndex < 32); swapList[slIndex] = -swapList[slIndex - 1] + seeValues[capturedType]; slIndex++; - // Remember the value of the capturing piece, and change the side to move - // before beginning the next iteration + // Remember the value of the capturing piece, and change the side to + // move before beginning the next iteration. capturedType = pt; stm = opposite_color(stm); stmAttackers = attackers & pieces_of_color(stm); - // Stop after a king capture - if (pt == KING && stmAttackers) + // Stop before processing a king capture + if (capturedType == KING && stmAttackers) { assert(slIndex < 32); swapList[slIndex++] = QueenValueMidgame*10; @@ -1462,7 +1586,7 @@ int Position::see(Square from, Square to) const { } while (stmAttackers); // Having built the swap list, we negamax through it to find the best - // achievable score from the point of view of the side to move + // achievable score from the point of view of the side to move. while (--slIndex) swapList[slIndex-1] = Min(-swapList[slIndex], swapList[slIndex-1]); @@ -1503,24 +1627,8 @@ void Position::clear() { } -/// Position::reset_game_ply() simply sets gamePly to 0. It is used from the -/// UCI interface code, whenever a non-reversible move is made in a -/// 'position fen moves m1 m2 ...' command. This makes it possible -/// for the program to handle games of arbitrary length, as long as the GUI -/// handles draws by the 50 move rule correctly. - -void Position::reset_game_ply() { - - st->gamePly = 0; -} - -void Position::inc_startpos_ply_counter() { - - startPosPlyCounter++; -} - /// Position::put_piece() puts a piece on the given square of the board, -/// updating the board array, bitboards, and piece counts. +/// updating the board array, pieces list, bitboards, and piece counts. void Position::put_piece(Piece p, Square s) { @@ -1528,32 +1636,12 @@ void Position::put_piece(Piece p, Square s) { PieceType pt = type_of_piece(p); board[s] = p; - index[s] = pieceCount[c][pt]; + index[s] = pieceCount[c][pt]++; pieceList[c][pt][index[s]] = s; set_bit(&(byTypeBB[pt]), s); set_bit(&(byColorBB[c]), s); - set_bit(&byTypeBB[0], s); // HACK: byTypeBB[0] contains all occupied squares. - - pieceCount[c][pt]++; -} - - -/// Position::allow_oo() gives the given side the right to castle kingside. -/// Used when setting castling rights during parsing of FEN strings. - -void Position::allow_oo(Color c) { - - st->castleRights |= (1 + int(c)); -} - - -/// Position::allow_ooo() gives the given side the right to castle queenside. -/// Used when setting castling rights during parsing of FEN strings. - -void Position::allow_ooo(Color c) { - - st->castleRights |= (4 + 4*int(c)); + set_bit(&(byTypeBB[0]), s); // HACK: byTypeBB[0] contains all occupied squares. } @@ -1564,7 +1652,7 @@ void Position::allow_ooo(Color c) { Key Position::compute_key() const { - Key result = 0; + Key result = zobCastle[st->castleRights]; for (Square s = SQ_A1; s <= SQ_H8; s++) if (square_is_occupied(s)) @@ -1573,7 +1661,6 @@ Key Position::compute_key() const { if (ep_square() != SQ_NONE) result ^= zobEp[ep_square()]; - result ^= zobCastle[st->castleRights]; if (side_to_move() == BLACK) result ^= zobSideToMove; @@ -1589,18 +1676,14 @@ Key Position::compute_key() const { Key Position::compute_pawn_key() const { - Key result = 0; Bitboard b; - Square s; + Key result = 0; for (Color c = WHITE; c <= BLACK; c++) { b = pieces(PAWN, c); while (b) - { - s = pop_1st_bit(&b); - result ^= zobrist[c][PAWN][s]; - } + result ^= zobrist[c][PAWN][pop_1st_bit(&b)]; } return result; } @@ -1614,11 +1697,13 @@ Key Position::compute_pawn_key() const { Key Position::compute_material_key() const { + int count; Key result = 0; + for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= QUEEN; pt++) { - int count = piece_count(c, pt); + count = piece_count(c, pt); for (int i = 0; i < count; i++) result ^= zobrist[c][pt][i]; } @@ -1632,20 +1717,15 @@ Key Position::compute_material_key() const { /// updated by do_move and undo_move when the program is running in debug mode. Score Position::compute_value() const { - Score result = SCORE_ZERO; Bitboard b; - Square s; + Score result = SCORE_ZERO; for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= KING; pt++) { b = pieces(pt, c); while (b) - { - s = pop_1st_bit(&b); - assert(piece_on(s) == piece_of_color_and_type(c, pt)); - result += pst(c, pt, s); - } + result += pst(c, pt, pop_1st_bit(&b)); } result += (side_to_move() == WHITE ? TempoValue / 2 : -TempoValue / 2); @@ -1654,7 +1734,7 @@ Score Position::compute_value() const { /// Position::compute_non_pawn_material() computes the total non-pawn middle -/// game material score for the given side. Material scores are updated +/// game material value for the given side. Material values are updated /// incrementally during the search, this function is only used while /// initializing a new Position object. @@ -1663,16 +1743,8 @@ Value Position::compute_non_pawn_material(Color c) const { Value result = VALUE_ZERO; for (PieceType pt = KNIGHT; pt <= QUEEN; pt++) - { - Bitboard b = pieces(pt, c); - while (b) - { - assert(piece_on(first_1(b)) == piece_of_color_and_type(c, pt)); + result += piece_count(c, pt) * PieceValueMidgame[pt]; - pop_1st_bit(&b); - result += PieceValueMidgame[pt]; - } - } return result; } @@ -1680,7 +1752,7 @@ Value Position::compute_non_pawn_material(Color c) const { /// Position::is_draw() tests whether the position is drawn by material, /// repetition, or the 50 moves rule. It does not detect stalemates, this /// must be done by the search. - +template bool Position::is_draw() const { // Draw by material? @@ -1689,68 +1761,30 @@ bool Position::is_draw() const { return true; // Draw by the 50 moves rule? - if (st->rule50 > 99 && (st->rule50 > 100 || !is_mate())) + if (st->rule50 > 99 && !is_mate()) return true; // Draw by repetition? - for (int i = 4, e = Min(Min(st->gamePly, st->rule50), st->pliesFromNull); i <= e; i += 2) - if (history[st->gamePly - i] == st->key) - return true; + if (!SkipRepetition) + for (int i = 4, e = Min(Min(st->gamePly, st->rule50), st->pliesFromNull); i <= e; i += 2) + if (history[st->gamePly - i] == st->key) + return true; return false; } +// Explicit template instantiations +template bool Position::is_draw() const; +template bool Position::is_draw() const; + /// Position::is_mate() returns true or false depending on whether the /// side to move is checkmated. bool Position::is_mate() const { - MoveStack moves[MOVES_MAX]; - return is_check() && (generate_moves(*this, moves) == moves); -} - - -/// Position::has_mate_threat() tests whether the side to move is under -/// a threat of being mated in one from the current position. - -bool Position::has_mate_threat() { - - MoveStack mlist[MOVES_MAX], *last, *cur; - StateInfo st1, st2; - bool mateFound = false; - - // If we are under check it's up to evasions to do the job - if (is_check()) - return false; - - // First pass the move to our opponent doing a null move - do_null_move(st1); - - // Then generate pseudo-legal moves that give check - last = generate_non_capture_checks(*this, mlist); - last = generate_captures(*this, last); - - // Loop through the moves, and see if one of them gives mate - Bitboard pinned = pinned_pieces(sideToMove); - CheckInfo ci(*this); - for (cur = mlist; cur != last && !mateFound; cur++) - { - Move move = cur->move; - if ( !pl_move_is_legal(move, pinned) - || !move_is_check(move, ci)) - continue; - - do_move(move, st2, ci, true); - - if (is_mate()) - mateFound = true; - - undo_move(move); - } - - undo_null_move(); - return mateFound; + MoveStack moves[MAX_MOVES]; + return in_check() && generate(*this, moves) == moves; } @@ -1759,20 +1793,20 @@ bool Position::has_mate_threat() { void Position::init_zobrist() { - RKISS RKiss; int i,j, k; + RKISS rk; for (i = 0; i < 2; i++) for (j = 0; j < 8; j++) for (k = 0; k < 64; k++) - zobrist[i][j][k] = Key(RKiss.rand64()); + zobrist[i][j][k] = rk.rand(); for (i = 0; i < 64; i++) - zobEp[i] = Key(RKiss.rand64()); + zobEp[i] = rk.rand(); for (i = 0; i < 16; i++) - zobCastle[i] = Key(RKiss.rand64()); + zobCastle[i] = rk.rand(); - zobSideToMove = Key(RKiss.rand64()); - zobExclusion = Key(RKiss.rand64()); + zobSideToMove = rk.rand(); + zobExclusion = rk.rand(); } @@ -1794,13 +1828,15 @@ void Position::init_piece_square_tables() { } -/// Position::flipped_copy() makes a copy of the input position, but with -/// the white and black sides reversed. This is only useful for debugging, -/// especially for finding evaluation symmetry bugs. +/// Position::flip() flips position with the white and black sides reversed. This +/// is only useful for debugging especially for finding evaluation symmetry bugs. + +void Position::flip() { -void Position::flipped_copy(const Position& pos) { + assert(is_ok()); - assert(pos.is_ok()); + // Make a copy of current position before to start changing + const Position pos(*this, threadID); clear(); threadID = pos.thread(); @@ -1808,16 +1844,16 @@ void Position::flipped_copy(const Position& pos) { // Board for (Square s = SQ_A1; s <= SQ_H8; s++) if (!pos.square_is_empty(s)) - put_piece(Piece(int(pos.piece_on(s)) ^ 8), flip_square(s)); + put_piece(Piece(pos.piece_on(s) ^ 8), flip_square(s)); // Side to move sideToMove = opposite_color(pos.side_to_move()); // Castling rights - if (pos.can_castle_kingside(WHITE)) allow_oo(BLACK); - if (pos.can_castle_queenside(WHITE)) allow_ooo(BLACK); - if (pos.can_castle_kingside(BLACK)) allow_oo(WHITE); - if (pos.can_castle_queenside(BLACK)) allow_ooo(WHITE); + if (pos.can_castle_kingside(WHITE)) set_castle_kingside(BLACK); + if (pos.can_castle_queenside(WHITE)) set_castle_queenside(BLACK); + if (pos.can_castle_kingside(BLACK)) set_castle_kingside(WHITE); + if (pos.can_castle_queenside(BLACK)) set_castle_queenside(WHITE); initialKFile = pos.initialKFile; initialKRFile = pos.initialKRFile; @@ -1859,18 +1895,20 @@ void Position::flipped_copy(const Position& pos) { bool Position::is_ok(int* failedStep) const { // What features of the position should be verified? - static const bool debugBitboards = false; - static const bool debugKingCount = false; - static const bool debugKingCapture = false; - static const bool debugCheckerCount = false; - static const bool debugKey = false; - static const bool debugMaterialKey = false; - static const bool debugPawnKey = false; - static const bool debugIncrementalEval = false; - static const bool debugNonPawnMaterial = false; - static const bool debugPieceCounts = false; - static const bool debugPieceList = false; - static const bool debugCastleSquares = false; + const bool debugAll = false; + + const bool debugBitboards = debugAll || false; + const bool debugKingCount = debugAll || false; + const bool debugKingCapture = debugAll || false; + const bool debugCheckerCount = debugAll || false; + const bool debugKey = debugAll || false; + const bool debugMaterialKey = debugAll || false; + const bool debugPawnKey = debugAll || false; + const bool debugIncrementalEval = debugAll || false; + const bool debugNonPawnMaterial = debugAll || false; + const bool debugPieceCounts = debugAll || false; + const bool debugPieceList = debugAll || false; + const bool debugCastleSquares = debugAll || false; if (failedStep) *failedStep = 1; @@ -1995,25 +2033,26 @@ bool Position::is_ok(int* failedStep) const { if (failedStep) (*failedStep)++; if (debugPieceList) - { for (Color c = WHITE; c <= BLACK; c++) for (PieceType pt = PAWN; pt <= KING; pt++) for (int i = 0; i < pieceCount[c][pt]; i++) { - if (piece_on(piece_list(c, pt, i)) != piece_of_color_and_type(c, pt)) + if (piece_on(piece_list(c, pt, i)) != make_piece(c, pt)) return false; if (index[piece_list(c, pt, i)] != i) return false; } - } if (failedStep) (*failedStep)++; - if (debugCastleSquares) { - for (Color c = WHITE; c <= BLACK; c++) { - if (can_castle_kingside(c) && piece_on(initial_kr_square(c)) != piece_of_color_and_type(c, ROOK)) + if (debugCastleSquares) + { + for (Color c = WHITE; c <= BLACK; c++) + { + if (can_castle_kingside(c) && piece_on(initial_kr_square(c)) != make_piece(c, ROOK)) return false; - if (can_castle_queenside(c) && piece_on(initial_qr_square(c)) != piece_of_color_and_type(c, ROOK)) + + if (can_castle_queenside(c) && piece_on(initial_qr_square(c)) != make_piece(c, ROOK)) return false; } if (castleRightsMask[initial_kr_square(WHITE)] != (ALL_CASTLES ^ WHITE_OO))