X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fposition.cpp;h=3f6400a80daeda5ae7f8af243726f138ff322a4b;hp=f0301e4ec9308af1a78394c5728b0246b1af30a3;hb=c9d7e99de682516c560009b550c41da9ae2008b8;hpb=2bb555025fc94fde3972b644bdbd27f245475213 diff --git a/src/position.cpp b/src/position.cpp index f0301e4e..3f6400a8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -17,12 +17,6 @@ along with this program. If not, see . */ - -//// -//// Includes -//// - -#include #include #include #include @@ -32,11 +26,9 @@ #include "bitcount.h" #include "movegen.h" -#include "movepick.h" #include "position.h" #include "psqtab.h" #include "rkiss.h" -#include "san.h" #include "tt.h" #include "ucioption.h" @@ -44,11 +36,6 @@ using std::string; using std::cout; using std::endl; - -//// -//// Position's static data definitions -//// - Key Position::zobrist[2][8][64]; Key Position::zobEp[64]; Key Position::zobCastle[16]; @@ -89,8 +76,6 @@ namespace { // Bonus for having the side to move (modified by Joona Kiiski) const Score TempoValue = make_score(48, 22); - bool isZero(char c) { return c == '0'; } - struct PieceLetters : public std::map { PieceLetters() { @@ -152,9 +137,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; } @@ -175,14 +160,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 @@ -205,32 +190,26 @@ void Position::from_fen(const string& fen) { */ char token; + int hmc, fmn; std::istringstream ss(fen); - Rank rank = RANK_8; - File file = FILE_A; + Square sq = SQ_A8; clear(); // 1. Piece placement field while (ss.get(token) && token != ' ') { - if (isdigit(token)) + if (pieceLetters.find(token) != pieceLetters.end()) { - file += File(token - '0'); // Skip the given number of files - continue; + put_piece(pieceLetters[token], 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 @@ -244,27 +223,29 @@ void Position::from_fen(const string& fen) { // 3. Castling availability while (ss.get(token) && token != ' ') - { - if (token == '-') - continue; - 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'))) { - 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 >> 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; @@ -274,10 +255,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(); @@ -344,7 +322,8 @@ bool Position::set_castling_rights(char token) { initialKRFile = rookFile; } } - else return false; + else + return token == '-'; return true; } @@ -359,7 +338,7 @@ const string Position::to_fen() const { Square sq; char emptyCnt = '0'; - for (Rank rank = RANK_8; rank >= RANK_1; rank--) + for (Rank rank = RANK_8; rank >= RANK_1; rank--, fen += '/') { for (File file = FILE_A; file <= FILE_H; file++) { @@ -367,34 +346,38 @@ const string Position::to_fen() const { if (square_is_occupied(sq)) { - fen += emptyCnt; + if (emptyCnt != '0') + { + fen += emptyCnt; + emptyCnt = '0'; + } fen += pieceLetters.from_piece(piece_on(sq)); - emptyCnt = '0'; } else emptyCnt++; } - fen += emptyCnt; - fen += '/'; - emptyCnt = '0'; + + if (emptyCnt != '0') + { + fen += emptyCnt; + emptyCnt = '0'; + } } - 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 += '-'; @@ -409,14 +392,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) { @@ -431,17 +406,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; + char c = (color_of_piece_on(sq) == BLACK ? '=' : ' '); cout << c << pieceLetters.from_piece(piece) << c << '|'; } } cout << dottedLine << "Fen is: " << to_fen() << "\nKey is: " << st->key << endl; - requestPending = false; } @@ -527,7 +501,7 @@ Bitboard Position::attacks_from(Piece p, Square s) const { case WB: case BB: return attacks_from(s); case WR: case BR: return attacks_from(s); case WQ: case BQ: return attacks_from(s); - default: return StepAttackBB[p][s]; + default: return StepAttacksBB[p][s]; } } @@ -540,7 +514,7 @@ Bitboard Position::attacks_from(Piece p, Square s, Bitboard occ) { 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 StepAttackBB[p][s]; + default: return StepAttacksBB[p][s]; } } @@ -613,8 +587,8 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { 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); @@ -629,7 +603,7 @@ bool Position::pl_move_is_legal(Move m, Bitboard pinned) const { 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)); // If the moving piece is a king, check whether the destination // square is attacked by the opponent. @@ -669,6 +643,115 @@ bool Position::pl_move_is_evasion(Move m, Bitboard pinned) const return bit_is_set(target, to) && pl_move_is_legal(m, pinned); } +/// Position::move_is_legal() takes a position and a (not necessarily pseudo-legal) +/// move and tests whether the move is legal. This version is not very fast and +/// should be used only in non time-critical paths. + +bool Position::move_is_legal(const Move m) const { + + MoveStack mlist[MAX_MOVES]; + MoveStack *cur, *last = generate(*this, mlist); + + for (cur = mlist; cur != last; cur++) + if (cur->move == m) + return pl_move_is_legal(m, pinned_pieces(sideToMove)); + + return false; +} + + +/// Fast version of Position::move_is_legal() that takes a position a move and +/// a bitboard of pinned pieces as input, and tests whether the move is legal. + +bool Position::move_is_legal(const Move m, Bitboard pinned) const { + + assert(is_ok()); + assert(pinned == pinned_pieces(sideToMove)); + + Color us = sideToMove; + Color them = opposite_color(sideToMove); + Square from = move_from(m); + Square to = move_to(m); + Piece pc = piece_on(from); + + // Use a slower but simpler function for uncommon cases + if (move_is_special(m)) + return move_is_legal(m); + + // If the from square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (color_of_piece(pc) != us) + return false; + + // 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; + 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; + + // The move is pseudo-legal, check if it is also legal + return is_check() ? pl_move_is_evasion(m, pinned) : pl_move_is_legal(m, pinned); +} + /// Position::move_is_check() tests whether a pseudo-legal move is a check @@ -683,7 +766,7 @@ bool Position::move_is_check(Move m, const CheckInfo& ci) const { 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); @@ -769,6 +852,29 @@ 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 inside pos 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. @@ -783,6 +889,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI assert(is_ok()); assert(move_is_ok(m)); + assert(&newSt != st); nodes++; Key key = st->key; @@ -798,8 +905,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; @@ -836,7 +942,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) @@ -888,7 +994,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) @@ -909,7 +1014,7 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI // 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]++; @@ -941,6 +1046,9 @@ void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveI } } + // Prefetch pawn and material hash tables + prefetchTables(st->pawnKey, st->materialKey, threadID); + // Update incremental scores st->value += pst_delta(piece, from, to); @@ -1003,7 +1111,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; } @@ -1067,8 +1175,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 @@ -1097,8 +1205,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; @@ -1174,7 +1282,7 @@ 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 ? { @@ -1182,7 +1290,7 @@ void Position::undo_move(Move 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); @@ -1207,7 +1315,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 @@ -1229,7 +1337,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]++; @@ -1276,8 +1384,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); @@ -1297,8 +1405,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; @@ -1536,22 +1644,6 @@ 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, pieces list, bitboards, and piece counts. @@ -1686,7 +1778,7 @@ 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? @@ -1703,51 +1795,8 @@ bool Position::is_draw() const { 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 could 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 is_check() && generate(*this, moves) == moves; } @@ -1994,25 +2043,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))