X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fposition.cpp;h=0be309ee60cac3d0ea78bcec0ad6c38237e7768b;hp=67cf2a02522c7f062d0cc41c647e01bb6463e91d;hb=108f0da4d7f993732aa2e854b8f3fa8ca6d3b46c;hpb=fa1e3427bdf7f6347326015a381014564e8fb337 diff --git a/src/position.cpp b/src/position.cpp index 67cf2a02..0be309ee 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,30 +52,35 @@ namespace { const string PieceToChar(" PNBRQK pnbrqk"); -const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; +constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; // min_attacker() is a helper function used by see_ge() to locate the least // valuable attacker for the side to move, remove the attacker we just found // from the bitboards and scan for new X-ray attacks behind it. template -PieceType min_attacker(const Bitboard* bb, Square to, Bitboard stmAttackers, +PieceType min_attacker(const Bitboard* byTypeBB, Square to, Bitboard stmAttackers, Bitboard& occupied, Bitboard& attackers) { - Bitboard b = stmAttackers & bb[Pt]; + Bitboard b = stmAttackers & byTypeBB[Pt]; if (!b) - return min_attacker(bb, to, stmAttackers, occupied, attackers); + return min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); - occupied ^= b & ~(b - 1); + occupied ^= lsb(b); // Remove the attacker from occupied + // Add any X-ray attack behind the just removed piece. For instance with + // rooks in a8 and a7 attacking a1, after removing a7 we add rook in a8. + // Note that new added attackers can be of any color. if (Pt == PAWN || Pt == BISHOP || Pt == QUEEN) - attackers |= attacks_bb(to, occupied) & (bb[BISHOP] | bb[QUEEN]); + attackers |= attacks_bb(to, occupied) & (byTypeBB[BISHOP] | byTypeBB[QUEEN]); if (Pt == ROOK || Pt == QUEEN) - attackers |= attacks_bb(to, occupied) & (bb[ROOK] | bb[QUEEN]); + attackers |= attacks_bb(to, occupied) & (byTypeBB[ROOK] | byTypeBB[QUEEN]); - attackers &= occupied; // After X-ray that may add already processed pieces + // X-ray may add already processed pieces because byTypeBB[] is constant: in + // the rook example, now attackers contains _again_ rook in a7, so remove it. + attackers &= occupied; return (PieceType)Pt; } @@ -211,10 +216,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th while ((ss >> token) && !isspace(token)) { if (isdigit(token)) - sq += Square(token - '0'); // Advance the given number of files + sq += (token - '0') * EAST; // Advance the given number of files else if (token == '/') - sq -= Square(16); + sq += 2 * SOUTH; else if ((idx = PieceToChar.find(token)) != string::npos) { @@ -272,7 +277,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to ply starting from 0, + // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); @@ -317,8 +322,8 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_check_info(StateInfo* si) const { - si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinnersForKing[WHITE]); - si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinnersForKing[BLACK]); + si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinners[BLACK]); + si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinners[WHITE]); Square ksq = square(~sideToMove); @@ -450,19 +455,6 @@ const string Position::fen() const { } -/// Position::game_phase() calculates the game phase interpolating total non-pawn -/// material between endgame and midgame limits. - -Phase Position::game_phase() const { - - Value npm = st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; - - npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); - - return Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); -} - - /// Position::slider_blockers() returns a bitboard of all the pieces (both colors) /// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a /// slider if removing that piece from the board would result in a position where @@ -472,7 +464,7 @@ Phase Position::game_phase() const { Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { - Bitboard result = 0; + Bitboard blockers = 0; pinners = 0; // Snipers are sliders that attack 's' when a piece is removed @@ -484,14 +476,14 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners Square sniperSq = pop_lsb(&snipers); Bitboard b = between_bb(s, sniperSq) & pieces(); - if (!more_than_one(b)) + if (b && !more_than_one(b)) { - result |= b; + blockers |= b; if (b & pieces(color_of(piece_on(s)))) pinners |= sniperSq; } } - return result; + return blockers; } @@ -548,7 +540,7 @@ bool Position::legal(Move m) const { // 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_pieces(us) & from) + return !(blockers_for_king(us) & from) || aligned(from, to_sq(m), square(us)); } @@ -640,7 +632,7 @@ bool Position::gives_check(Move m) const { return true; // Is there a discovered check? - if ( (discovered_check_candidates() & from) + if ( (st->blockersForKing[~sideToMove] & from) && !aligned(from, to, square(~sideToMove))) return true; @@ -690,7 +682,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(is_ok(m)); assert(&newSt != st); - ++nodes; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); Key k = st->key ^ Zobrist::side; // Copy some fields of the old state to our new StateInfo object except the @@ -800,7 +792,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( (int(to) ^ int(from)) == 16 && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) { - st->epSquare = (from + to) / 2; + st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; } @@ -1006,75 +998,79 @@ bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); - // Castling moves are implemented as king capturing the rook so cannot be - // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always - // correct unless in the rare case the rook ends up under attack. - if (type_of(m) == CASTLING) + // Only deal with normal moves, assume others pass a simple see + if (type_of(m) != NORMAL) return VALUE_ZERO >= threshold; + Bitboard stmAttackers; Square from = from_sq(m), to = to_sq(m); PieceType nextVictim = type_of(piece_on(from)); - Color stm = ~color_of(piece_on(from)); // First consider opponent's move - Value balance; // Values of the pieces taken by us minus opponent's ones - Bitboard occupied, stmAttackers; + Color us = color_of(piece_on(from)); + Color stm = ~us; // First consider opponent's move + Value balance; // Values of the pieces taken by us minus opponent's ones - if (type_of(m) == ENPASSANT) - { - occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn - balance = PieceValue[MG][PAWN]; - } - else - { - balance = PieceValue[MG][piece_on(to)]; - occupied = 0; - } + // The opponent may be able to recapture so this is the best result + // we can hope for. + balance = PieceValue[MG][piece_on(to)] - threshold; - if (balance < threshold) + if (balance < VALUE_ZERO) return false; - if (nextVictim == KING) - return true; - + // Now assume the worst possible result: that the opponent can + // capture our piece for free. balance -= PieceValue[MG][nextVictim]; - if (balance >= threshold) + // If it is enough (like in PxQ) then return immediately. Note that + // in case nextVictim == KING we always return here, this is ok + // if the given move is legal. + if (balance >= VALUE_ZERO) return true; - bool relativeStm = true; // True if the opponent is to move - occupied ^= pieces() ^ from ^ to; - - // Find all attackers to the destination square, with the moving piece removed, - // but possibly an X-ray attacker added behind it. + // Find all attackers to the destination square, with the moving piece + // removed, but possibly an X-ray attacker added behind it. + Bitboard occupied = pieces() ^ from ^ to; Bitboard attackers = attackers_to(to, occupied) & occupied; while (true) { stmAttackers = attackers & pieces(stm); - // Don't allow pinned pieces to attack pieces except the king as long all - // pinners are on their original square. - if (!(st->pinnersForKing[stm] & ~occupied)) + // Don't allow pinned pieces to attack (except the king) as long as + // all pinners are on their original square. + if (!(st->pinners[~stm] & ~occupied)) stmAttackers &= ~st->blockersForKing[stm]; + // If stm has no more attackers then give up: stm loses if (!stmAttackers) - return relativeStm; + break; - // Locate and remove the next least valuable attacker + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' the possibly X-ray attackers behind it. nextVictim = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); - if (nextVictim == KING) - return relativeStm == bool(attackers & pieces(~stm)); - - balance += relativeStm ? PieceValue[MG][nextVictim] - : -PieceValue[MG][nextVictim]; + stm = ~stm; // Switch side to move - relativeStm = !relativeStm; + // Negamax the balance with alpha = balance, beta = balance+1 and + // add nextVictim's value. + // + // (balance, balance+1) -> (-balance-1, -balance) + // + assert(balance < VALUE_ZERO); - if (relativeStm == (balance >= threshold)) - return relativeStm; + balance = -balance - 1 - PieceValue[MG][nextVictim]; - stm = ~stm; + // If balance is still non-negative after giving away nextVictim then we + // win. The only thing to be careful about it is that we should revert + // stm if we captured with the king when the opponent still has attackers. + if (balance >= VALUE_ZERO) + { + if (nextVictim == KING && (attackers & pieces(stm))) + stm = ~stm; + break; + } + assert(nextVictim != KING); } + return us != stm; // We break the above loop when stm loses } @@ -1098,11 +1094,10 @@ bool Position::is_draw(int ply) const { { stp = stp->previous->previous; - // At root position ply is 1, so return a draw score if a position - // repeats once earlier but strictly after the root, or repeats twice - // before or at the root. + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. if ( stp->key == st->key - && ++cnt + (ply - 1 > i) == 2) + && ++cnt + (ply > i) == 2) return true; } @@ -1110,6 +1105,35 @@ bool Position::is_draw(int ply) const { } +// Position::has_repeated() tests whether there has been at least one repetition +// of positions since the last capture or pawn move. + +bool Position::has_repeated() const { + + StateInfo* stc = st; + while (true) + { + int i = 4, e = std::min(stc->rule50, stc->pliesFromNull); + + if (e < i) + return false; + + StateInfo* stp = st->previous->previous; + + do { + stp = stp->previous->previous; + + if (stp->key == stc->key) + return true; + + i += 2; + } while (i <= e); + + stc = stc->previous; + } +} + + /// Position::flip() flips position with the white and black sides reversed. This /// is only useful for debugging e.g. for finding evaluation symmetry bugs. @@ -1151,7 +1175,7 @@ void Position::flip() { bool Position::pos_is_ok() const { - const bool Fast = true; // Quick (default) or full check? + constexpr bool Fast = true; // Quick (default) or full check? if ( (sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING