X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fposition.cpp;h=c9db6224abfa2a160d5cf0777c15993a34e86f4b;hp=6336a5ed193bb4c3aef1731b7c1bcfda858d3e0a;hb=995ee4b31105ad8c7976cc68c11fabfdc5108e63;hpb=e5cfa14f40a38d99d11a9c048c34858e3145fbcd diff --git a/src/position.cpp b/src/position.cpp index 6336a5ed..c9db6224 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-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2020 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 @@ -50,41 +50,6 @@ const string PieceToChar(" PNBRQK pnbrqk"); 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* byTypeBB, Square to, Bitboard stmAttackers, - Bitboard& occupied, Bitboard& attackers) { - - Bitboard b = stmAttackers & byTypeBB[Pt]; - if (!b) - return min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); - - 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) & (byTypeBB[BISHOP] | byTypeBB[QUEEN]); - - if (Pt == ROOK || Pt == QUEEN) - attackers |= attacks_bb(to, occupied) & (byTypeBB[ROOK] | byTypeBB[QUEEN]); - - // 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 Pt; -} - -template<> -PieceType min_attacker(const Bitboard*, Square, Bitboard, Bitboard&, Bitboard&) { - return KING; // No need to update bitboards: it is the last cycle -} - } // namespace @@ -99,10 +64,11 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { for (File f = FILE_A; f <= FILE_H; ++f) os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; - os << " |\n +---+---+---+---+---+---+---+---+\n"; + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; } - os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; @@ -174,7 +140,7 @@ void Position::init() { for (Piece pc : Pieces) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) - if (PseudoAttacks[type_of(pc)][s1] & s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) { Move move = make_move(s1, s2); Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; @@ -341,7 +307,7 @@ void Position::set_castling_right(Color c, Square rfrom) { Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto) - & ~(square_bb(kfrom) | rfrom); + & ~(kfrom | rfrom); } @@ -354,10 +320,10 @@ void Position::set_check_info(StateInfo* si) const { Square ksq = square(~sideToMove); - si->checkSquares[PAWN] = attacks_from(ksq, ~sideToMove); - si->checkSquares[KNIGHT] = attacks_from(ksq); - si->checkSquares[BISHOP] = attacks_from(ksq); - si->checkSquares[ROOK] = attacks_from(ksq); + si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + si->checkSquares[KNIGHT] = attacks_bb(ksq); + si->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + si->checkSquares[ROOK] = attacks_bb(ksq, pieces()); si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; si->checkSquares[KING] = 0; } @@ -410,11 +376,13 @@ void Position::set_state(StateInfo* si) const { Position& Position::set(const string& code, Color c, StateInfo* si) { - assert(code.length() > 0 && code.length() < 8); assert(code[0] == 'K'); string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, code.find('K', 1)) }; // Strong + code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); @@ -488,8 +456,8 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners pinners = 0; // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK)) - | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders; + Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) + | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; Bitboard occupancy = pieces() ^ snipers; while (snipers) @@ -513,12 +481,12 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_from(s) & pieces(KNIGHT)) + return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_from(s) & pieces(KING)); + | (attacks_bb(s) & pieces(KING)); } @@ -621,15 +589,15 @@ bool Position::pseudo_legal(const Move m) const { if ((Rank8BB | Rank1BB) & to) return false; - if ( !(attacks_from(from, us) & pieces(~us) & to) // Not a capture + if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture && !((from + pawn_push(us) == to) && empty(to)) // Not a single push && !( (from + 2 * pawn_push(us) == to) // Not a double push - && (rank_of(from) == relative_rank(us, RANK_2)) + && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) return false; } - else if (!(attacks_from(type_of(pc), from) & to)) + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) return false; // Evasions generator already takes care to avoid some kind of illegal moves @@ -668,11 +636,11 @@ bool Position::gives_check(Move m) const { Square to = to_sq(m); // Is there a direct check? - if (st->checkSquares[type_of(piece_on(from))] & to) + if (check_squares(type_of(piece_on(from))) & to) return true; // Is there a discovered check? - if ( (st->blockersForKing[~sideToMove] & from) + if ( (blockers_for_king(~sideToMove) & from) && !aligned(from, to, square(~sideToMove))) return true; @@ -699,11 +667,11 @@ bool Position::gives_check(Move m) const { case CASTLING: { Square kfrom = from; - Square rfrom = to; // Castling is encoded as 'King captures the rook' + Square rfrom = to; // Castling is encoded as 'king captures the rook' Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); - return (PseudoAttacks[ROOK][rto] & square(~sideToMove)) + return (attacks_bb(rto) & square(~sideToMove)) && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); } default: @@ -778,8 +746,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(relative_rank(us, to) == RANK_6); assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); - - board[capsq] = NO_PIECE; // Not done by remove_piece() } st->pawnKey ^= Zobrist::psq[captured][capsq]; @@ -788,7 +754,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[them] -= PieceValue[MG][captured]; // Update board and piece lists - remove_piece(captured, capsq); + remove_piece(capsq); + + if (type_of(m) == ENPASSANT) + board[capsq] = NO_PIECE; // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@ -819,14 +788,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) != CASTLING) - move_piece(pc, from, to); + move_piece(from, to); // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { // Set en-passant square if the moved pawn can be captured if ( (int(to) ^ int(from)) == 16 - && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; @@ -839,7 +808,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); - remove_piece(pc, to); + remove_piece(to); put_piece(promotion, to); // Update hash keys @@ -852,7 +821,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][promotion]; } - // Update pawn hash key and prefetch access to pawnsTable + // Update pawn hash key st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; // Reset rule 50 draw counter @@ -919,7 +888,7 @@ void Position::undo_move(Move m) { assert(type_of(pc) == promotion_type(m)); assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); - remove_piece(pc, to); + remove_piece(to); pc = make_piece(us, PAWN); put_piece(pc, to); } @@ -931,7 +900,7 @@ void Position::undo_move(Move m) { } else { - move_piece(pc, to, from); // Put the piece back at the source square + move_piece(to, from); // Put the piece back at the source square if (st->capturedPiece) { @@ -971,15 +940,15 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); // Remove both pieces first since squares could overlap in Chess960 - remove_piece(make_piece(us, KING), Do ? from : to); - remove_piece(make_piece(us, ROOK), Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us put_piece(make_piece(us, KING), Do ? to : from); put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } -/// Position::do(undo)_null_move() is used to do(undo) a "null move": It flips +/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips /// the side to move without executing any move on the board. void Position::do_null_move(StateInfo& newSt) { @@ -1052,77 +1021,96 @@ bool Position::see_ge(Move m, Value threshold) const { 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 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 - - // 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 < VALUE_ZERO) + int swap = PieceValue[MG][piece_on(to)] - threshold; + if (swap < 0) return false; - // Now assume the worst possible result: that the opponent can - // capture our piece for free. - balance -= PieceValue[MG][nextVictim]; - - // 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) + swap = PieceValue[MG][piece_on(from)] - swap; + if (swap <= 0) return true; - // 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; + Color stm = color_of(piece_on(from)); + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; while (true) { - stmAttackers = attackers & pieces(stm); + stm = ~stm; + attackers &= occupied; + + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; // Don't allow pinned pieces to attack (except the king) as long as - // any pinners are on their original square. + // there are pinners 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) break; + res ^= 1; + // 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); + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValueMg - swap) < res) + break; - stm = ~stm; // Switch side to move + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } - // Negamax the balance with alpha = balance, beta = balance+1 and - // add nextVictim's value. - // - // (balance, balance+1) -> (-balance-1, -balance) - // - assert(balance < VALUE_ZERO); + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValueMg - swap) < res) + break; - balance = -balance - 1 - PieceValue[MG][nextVictim]; + occupied ^= lsb(bb); + } - // 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) + else if ((bb = stmAttackers & pieces(BISHOP))) { - if (nextVictim == KING && (attackers & pieces(stm))) - stm = ~stm; - break; + if ((swap = BishopValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } - assert(nextVictim != KING); + + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValueMg - swap) < res) + break; + + occupied ^= lsb(bb); + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); + } + + else // KING + // If we "capture" with the king but opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; } - return us != stm; // We break the above loop when stm loses -} + return bool(res); +} /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. @@ -1134,10 +1122,7 @@ bool Position::is_draw(int ply) const { // Return a draw score if a position repeats once earlier but strictly // after the root, or repeats twice before or at the root. - if (st->repetition && st->repetition < ply) - return true; - - return false; + return st->repetition && st->repetition < ply; }