mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Only a modest amount of work happens between the transposition table prefetch and the probe, so the probe still often stalls waiting for DRAM. The vast majority of the time (in particular, if !checkEP), the key is known much earlier in the do_move function and the latency can be better hidden. passed STC SMP https://tests.stockfishchess.org/tests/view/68f337c528e6d77fcffa066a LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 65256 W: 16806 L: 16462 D: 31988 Ptnml(0-2): 76, 7386, 17362, 7726, 78 but failed to gain STC https://tests.stockfishchess.org/tests/view/68f3378328e6d77fcffa0665 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 109824 W: 28523 L: 28618 D: 52683 Ptnml(0-2): 311, 11799, 30788, 11702, 312 In local tests, the speedup grows with thread count closes https://github.com/official-stockfish/Stockfish/pull/6372 No functional change
1371 lines
45 KiB
C++
1371 lines
45 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "position.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <initializer_list>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include "bitboard.h"
|
|
#include "misc.h"
|
|
#include "movegen.h"
|
|
#include "syzygy/tbprobe.h"
|
|
#include "tt.h"
|
|
#include "uci.h"
|
|
|
|
using std::string;
|
|
|
|
namespace Stockfish {
|
|
|
|
namespace Zobrist {
|
|
|
|
Key psq[PIECE_NB][SQUARE_NB];
|
|
Key enpassant[FILE_NB];
|
|
Key castling[CASTLING_RIGHT_NB];
|
|
Key side, noPawns;
|
|
}
|
|
|
|
namespace {
|
|
|
|
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
|
|
|
static 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};
|
|
} // namespace
|
|
|
|
|
|
// Returns an ASCII representation of the position
|
|
std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|
|
|
os << "\n +---+---+---+---+---+---+---+---+\n";
|
|
|
|
for (Rank r = RANK_8; r >= RANK_1; --r)
|
|
{
|
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
|
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
|
|
|
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
|
|
}
|
|
|
|
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: ";
|
|
|
|
for (Bitboard b = pos.checkers(); b;)
|
|
os << UCIEngine::square(pop_lsb(b)) << " ";
|
|
|
|
if (Tablebases::MaxCardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
|
{
|
|
StateInfo st;
|
|
|
|
Position p;
|
|
p.set(pos.fen(), pos.is_chess960(), &st);
|
|
Tablebases::ProbeState s1, s2;
|
|
Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
|
|
int dtz = Tablebases::probe_dtz(p, &s2);
|
|
os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")"
|
|
<< "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")";
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
|
|
// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions
|
|
// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes
|
|
// to allow fast detection of recurring positions. For details see:
|
|
// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
|
|
|
|
// First and second hash functions for indexing the cuckoo tables
|
|
inline int H1(Key h) { return h & 0x1fff; }
|
|
inline int H2(Key h) { return (h >> 16) & 0x1fff; }
|
|
|
|
// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves
|
|
std::array<Key, 8192> cuckoo;
|
|
std::array<Move, 8192> cuckooMove;
|
|
|
|
// Initializes at startup the various arrays used to compute hash keys
|
|
void Position::init() {
|
|
|
|
PRNG rng(1070372);
|
|
|
|
for (Piece pc : Pieces)
|
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
|
Zobrist::psq[pc][s] = rng.rand<Key>();
|
|
// pawns on these squares will promote
|
|
std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0);
|
|
std::fill_n(Zobrist::psq[B_PAWN], 8, 0);
|
|
|
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
|
Zobrist::enpassant[f] = rng.rand<Key>();
|
|
|
|
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
|
|
Zobrist::castling[cr] = rng.rand<Key>();
|
|
|
|
Zobrist::side = rng.rand<Key>();
|
|
Zobrist::noPawns = rng.rand<Key>();
|
|
|
|
// Prepare the cuckoo tables
|
|
cuckoo.fill(0);
|
|
cuckooMove.fill(Move::none());
|
|
[[maybe_unused]] int count = 0;
|
|
for (Piece pc : Pieces)
|
|
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
|
for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
|
|
if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))
|
|
{
|
|
Move move = Move(s1, s2);
|
|
Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
|
|
int i = H1(key);
|
|
while (true)
|
|
{
|
|
std::swap(cuckoo[i], key);
|
|
std::swap(cuckooMove[i], move);
|
|
if (move == Move::none()) // Arrived at empty slot?
|
|
break;
|
|
i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot
|
|
}
|
|
count++;
|
|
}
|
|
assert(count == 3668);
|
|
}
|
|
|
|
|
|
// Initializes the position object with the given FEN string.
|
|
// This function is not very robust - make sure that input FENs are correct,
|
|
// this is assumed to be the responsibility of the GUI.
|
|
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
|
|
/*
|
|
A FEN string defines a particular position using only the ASCII character set.
|
|
|
|
A FEN string contains six fields separated by 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. 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") whilst Black uses lowercase ("pnbrqk"). Blank squares are
|
|
noted using digits 1 through 8 (the number of blank squares), and "/"
|
|
separates ranks.
|
|
|
|
2) Active color. "w" means white moves next, "b" means black.
|
|
|
|
3) Castling availability. If neither side can castle, this is "-". Otherwise,
|
|
this has one or more letters: "K" (White can castle kingside), "Q" (White
|
|
can castle queenside), "k" (Black can castle kingside), and/or "q" (Black
|
|
can castle queenside).
|
|
|
|
4) En passant target square (in algebraic notation). If there's no en passant
|
|
target square, this is "-". If a pawn has just made a 2-square move, this
|
|
is the position "behind" the pawn. Following X-FEN standard, this is recorded
|
|
only if there is a pawn in position to make an en passant capture, and if
|
|
there really is a pawn that might have advanced two squares.
|
|
|
|
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
|
|
or capture. This is used to determine if a draw can be claimed under the
|
|
fifty-move rule.
|
|
|
|
6) Fullmove number. The number of the full move. It starts at 1, and is
|
|
incremented after Black's move.
|
|
*/
|
|
|
|
unsigned char col, row, token;
|
|
size_t idx;
|
|
Square sq = SQ_A8;
|
|
std::istringstream ss(fenStr);
|
|
|
|
std::memset(this, 0, sizeof(Position));
|
|
std::memset(si, 0, sizeof(StateInfo));
|
|
st = si;
|
|
|
|
ss >> std::noskipws;
|
|
|
|
// 1. Piece placement
|
|
while ((ss >> token) && !isspace(token))
|
|
{
|
|
if (isdigit(token))
|
|
sq += (token - '0') * EAST; // Advance the given number of files
|
|
|
|
else if (token == '/')
|
|
sq += 2 * SOUTH;
|
|
|
|
else if ((idx = PieceToChar.find(token)) != string::npos)
|
|
{
|
|
put_piece(Piece(idx), sq);
|
|
++sq;
|
|
}
|
|
}
|
|
|
|
// 2. Active color
|
|
ss >> token;
|
|
sideToMove = (token == 'w' ? WHITE : BLACK);
|
|
ss >> token;
|
|
|
|
// 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
|
|
// Shredder-FEN that uses the letters of the columns on which the rooks began
|
|
// the game instead of KQkq and also X-FEN standard that, in case of Chess960,
|
|
// if an inner rook is associated with the castling right, the castling tag is
|
|
// replaced by the file letter of the involved rook, as for the Shredder-FEN.
|
|
while ((ss >> token) && !isspace(token))
|
|
{
|
|
Square rsq;
|
|
Color c = islower(token) ? BLACK : WHITE;
|
|
Piece rook = make_piece(c, ROOK);
|
|
|
|
token = char(toupper(token));
|
|
|
|
if (token == 'K')
|
|
for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq)
|
|
{}
|
|
|
|
else if (token == 'Q')
|
|
for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq)
|
|
{}
|
|
|
|
else if (token >= 'A' && token <= 'H')
|
|
rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
|
|
|
|
else
|
|
continue;
|
|
|
|
set_castling_right(c, rsq);
|
|
}
|
|
|
|
// 4. En passant square.
|
|
// Ignore if square is invalid or not on side to move relative rank 6.
|
|
bool enpassant = false;
|
|
|
|
if (((ss >> col) && (col >= 'a' && col <= 'h'))
|
|
&& ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
|
|
{
|
|
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
|
|
|
|
// En passant square will be considered only if
|
|
// a) side to move have a pawn threatening epSquare
|
|
// b) there is an enemy pawn in front of epSquare
|
|
// c) there is no piece on epSquare or behind epSquare
|
|
enpassant = attacks_bb<PAWN>(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN)
|
|
&& (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
|
|
&& !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
|
|
}
|
|
|
|
if (!enpassant)
|
|
st->epSquare = SQ_NONE;
|
|
|
|
// 5-6. Halfmove clock and fullmove number
|
|
ss >> std::skipws >> st->rule50 >> gamePly;
|
|
|
|
// 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);
|
|
|
|
chess960 = isChess960;
|
|
set_state();
|
|
|
|
assert(pos_is_ok());
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
// Helper function used to set castling
|
|
// rights given the corresponding color and the rook starting square.
|
|
void Position::set_castling_right(Color c, Square rfrom) {
|
|
|
|
Square kfrom = square<KING>(c);
|
|
CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE);
|
|
|
|
st->castlingRights |= cr;
|
|
castlingRightsMask[kfrom] |= cr;
|
|
castlingRightsMask[rfrom] |= cr;
|
|
castlingRookSquare[cr] = rfrom;
|
|
|
|
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
|
|
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
|
|
|
|
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom);
|
|
}
|
|
|
|
|
|
// Sets king attacks to detect if a move gives check
|
|
void Position::set_check_info() const {
|
|
|
|
update_slider_blockers(WHITE);
|
|
update_slider_blockers(BLACK);
|
|
|
|
Square ksq = square<KING>(~sideToMove);
|
|
|
|
st->checkSquares[PAWN] = attacks_bb<PAWN>(ksq, ~sideToMove);
|
|
st->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
|
|
st->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
|
|
st->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
|
|
st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK];
|
|
st->checkSquares[KING] = 0;
|
|
}
|
|
|
|
|
|
// Computes the hash keys of the position, and other
|
|
// data that once computed is updated incrementally as moves are made.
|
|
// The function is only used when a new position is set up
|
|
void Position::set_state() const {
|
|
|
|
st->key = st->materialKey = 0;
|
|
st->minorPieceKey = 0;
|
|
st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0;
|
|
st->pawnKey = Zobrist::noPawns;
|
|
st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
|
|
st->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
|
|
|
|
set_check_info();
|
|
|
|
for (Bitboard b = pieces(); b;)
|
|
{
|
|
Square s = pop_lsb(b);
|
|
Piece pc = piece_on(s);
|
|
st->key ^= Zobrist::psq[pc][s];
|
|
|
|
if (type_of(pc) == PAWN)
|
|
st->pawnKey ^= Zobrist::psq[pc][s];
|
|
|
|
else
|
|
{
|
|
st->nonPawnKey[color_of(pc)] ^= Zobrist::psq[pc][s];
|
|
|
|
if (type_of(pc) != KING)
|
|
{
|
|
st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];
|
|
|
|
if (type_of(pc) <= BISHOP)
|
|
st->minorPieceKey ^= Zobrist::psq[pc][s];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (st->epSquare != SQ_NONE)
|
|
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
|
|
|
if (sideToMove == BLACK)
|
|
st->key ^= Zobrist::side;
|
|
|
|
st->key ^= Zobrist::castling[st->castlingRights];
|
|
|
|
for (Piece pc : Pieces)
|
|
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
|
|
st->materialKey ^= Zobrist::psq[pc][8 + cnt];
|
|
}
|
|
|
|
|
|
// Overload to initialize the position object with the given endgame code string
|
|
// like "KBPKN". It's mainly a helper to get the material key out of an endgame code.
|
|
Position& Position::set(const string& code, Color c, StateInfo* si) {
|
|
|
|
assert(code[0] == 'K');
|
|
|
|
string sides[] = {code.substr(code.find('K', 1)), // Weak
|
|
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);
|
|
|
|
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
|
|
+ char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
|
|
|
|
return set(fenStr, false, si);
|
|
}
|
|
|
|
|
|
// Returns a FEN representation of the position. In case of
|
|
// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
|
|
string Position::fen() const {
|
|
|
|
int emptyCnt;
|
|
std::ostringstream ss;
|
|
|
|
for (Rank r = RANK_8; r >= RANK_1; --r)
|
|
{
|
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
|
{
|
|
for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)
|
|
++emptyCnt;
|
|
|
|
if (emptyCnt)
|
|
ss << emptyCnt;
|
|
|
|
if (f <= FILE_H)
|
|
ss << PieceToChar[piece_on(make_square(f, r))];
|
|
}
|
|
|
|
if (r > RANK_1)
|
|
ss << '/';
|
|
}
|
|
|
|
ss << (sideToMove == WHITE ? " w " : " b ");
|
|
|
|
if (can_castle(WHITE_OO))
|
|
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K');
|
|
|
|
if (can_castle(WHITE_OOO))
|
|
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
|
|
|
|
if (can_castle(BLACK_OO))
|
|
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k');
|
|
|
|
if (can_castle(BLACK_OOO))
|
|
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
|
|
|
|
if (!can_castle(ANY_CASTLING))
|
|
ss << '-';
|
|
|
|
ss << (ep_square() == SQ_NONE ? " - " : " " + UCIEngine::square(ep_square()) + " ")
|
|
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
// Calculates st->blockersForKing[c] and st->pinners[~c],
|
|
// which store respectively the pieces preventing king of color c from being in check
|
|
// and the slider pieces of color ~c pinning pieces of color c to the king.
|
|
void Position::update_slider_blockers(Color c) const {
|
|
|
|
Square ksq = square<KING>(c);
|
|
|
|
st->blockersForKing[c] = 0;
|
|
st->pinners[~c] = 0;
|
|
|
|
// Snipers are sliders that attack 's' when a piece and other snipers are removed
|
|
Bitboard snipers = ((attacks_bb<ROOK>(ksq) & pieces(QUEEN, ROOK))
|
|
| (attacks_bb<BISHOP>(ksq) & pieces(QUEEN, BISHOP)))
|
|
& pieces(~c);
|
|
Bitboard occupancy = pieces() ^ snipers;
|
|
|
|
while (snipers)
|
|
{
|
|
Square sniperSq = pop_lsb(snipers);
|
|
Bitboard b = between_bb(ksq, sniperSq) & occupancy;
|
|
|
|
if (b && !more_than_one(b))
|
|
{
|
|
st->blockersForKing[c] |= b;
|
|
if (b & pieces(c))
|
|
st->pinners[~c] |= sniperSq;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Computes a bitboard of all pieces which attack a given square.
|
|
// Slider attacks use the occupied bitboard to indicate occupancy.
|
|
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
|
|
|
|
return (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))
|
|
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
|
|
| (attacks_bb<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
|
|
| (attacks_bb<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
|
|
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT)) | (attacks_bb<KING>(s) & pieces(KING));
|
|
}
|
|
|
|
bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const {
|
|
|
|
return ((attacks_bb<ROOK>(s) & pieces(c, ROOK, QUEEN))
|
|
&& (attacks_bb<ROOK>(s, occupied) & pieces(c, ROOK, QUEEN)))
|
|
|| ((attacks_bb<BISHOP>(s) & pieces(c, BISHOP, QUEEN))
|
|
&& (attacks_bb<BISHOP>(s, occupied) & pieces(c, BISHOP, QUEEN)))
|
|
|| (((attacks_bb<PAWN>(s, ~c) & pieces(PAWN)) | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
|
|
| (attacks_bb<KING>(s) & pieces(KING)))
|
|
& pieces(c));
|
|
}
|
|
|
|
// Tests whether a pseudo-legal move is legal
|
|
bool Position::legal(Move m) const {
|
|
|
|
assert(m.is_ok());
|
|
|
|
Color us = sideToMove;
|
|
Square from = m.from_sq();
|
|
Square to = m.to_sq();
|
|
|
|
assert(color_of(moved_piece(m)) == us);
|
|
assert(piece_on(square<KING>(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 after
|
|
// the move is made.
|
|
if (m.type_of() == EN_PASSANT)
|
|
{
|
|
Square ksq = square<KING>(us);
|
|
Square capsq = to - pawn_push(us);
|
|
Bitboard occupied = (pieces() ^ from ^ capsq) | to;
|
|
|
|
assert(to == ep_square());
|
|
assert(moved_piece(m) == make_piece(us, PAWN));
|
|
assert(piece_on(capsq) == make_piece(~us, PAWN));
|
|
assert(piece_on(to) == NO_PIECE);
|
|
|
|
return !(attacks_bb<ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
|
|
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
|
|
}
|
|
|
|
// Castling moves generation does not check if the castling path is clear of
|
|
// enemy attacks, it is delayed at a later time: now!
|
|
if (m.type_of() == CASTLING)
|
|
{
|
|
// After castling, the rook and king final positions are the same in
|
|
// Chess960 as they would be in standard chess.
|
|
to = relative_square(us, to > from ? SQ_G1 : SQ_C1);
|
|
Direction step = to > from ? WEST : EAST;
|
|
|
|
for (Square s = to; s != from; s += step)
|
|
if (attackers_to_exist(s, pieces(), ~us))
|
|
return false;
|
|
|
|
// In case of Chess960, verify if the Rook blocks some checks.
|
|
// For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
|
|
return !chess960 || !(blockers_for_king(us) & m.to_sq());
|
|
}
|
|
|
|
// If the moving piece is a king, check whether the destination square is
|
|
// attacked by the opponent.
|
|
if (type_of(piece_on(from)) == KING)
|
|
return !(attackers_to_exist(to, pieces() ^ from, ~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 !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING);
|
|
}
|
|
|
|
|
|
// Takes a random move and tests whether the move is
|
|
// pseudo-legal. It is used to validate moves from TT that can be corrupted
|
|
// due to SMP concurrent access or hash position key aliasing.
|
|
bool Position::pseudo_legal(const Move m) const {
|
|
|
|
Color us = sideToMove;
|
|
Square from = m.from_sq();
|
|
Square to = m.to_sq();
|
|
Piece pc = moved_piece(m);
|
|
|
|
// Use a slower but simpler function for uncommon cases
|
|
// yet we skip the legality check of MoveList<LEGAL>().
|
|
if (m.type_of() != NORMAL)
|
|
return checkers() ? MoveList<EVASIONS>(*this).contains(m)
|
|
: MoveList<NON_EVASIONS>(*this).contains(m);
|
|
|
|
// Is not a promotion, so the promotion piece must be empty
|
|
assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE);
|
|
|
|
// If the 'from' square is not occupied by a piece belonging to the side to
|
|
// move, the move is obviously not legal.
|
|
if (pc == NO_PIECE || color_of(pc) != us)
|
|
return false;
|
|
|
|
// The destination square cannot be occupied by a friendly piece
|
|
if (pieces(us) & to)
|
|
return false;
|
|
|
|
// Handle the special case of a pawn move
|
|
if (type_of(pc) == PAWN)
|
|
{
|
|
// We have already handled promotion moves, so destination cannot be on the 8th/1st rank
|
|
if ((Rank8BB | Rank1BB) & to)
|
|
return false;
|
|
|
|
// Check if it's a valid capture, single push, or double push
|
|
const bool isCapture = bool(attacks_bb<PAWN>(from, us) & pieces(~us) & to);
|
|
const bool isSinglePush = (from + pawn_push(us) == to) && empty(to);
|
|
const bool isDoublePush = (from + 2 * pawn_push(us) == to)
|
|
&& (relative_rank(us, from) == RANK_2) && empty(to)
|
|
&& empty(to - pawn_push(us));
|
|
|
|
if (!(isCapture || isSinglePush || isDoublePush))
|
|
return false;
|
|
}
|
|
else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
|
|
return false;
|
|
|
|
// Evasions generator already takes care to avoid some kind of illegal moves
|
|
// and legal() relies on this. We therefore have to take care that the same
|
|
// kind of moves are filtered out here.
|
|
if (checkers())
|
|
{
|
|
if (type_of(pc) != KING)
|
|
{
|
|
// Double check? In this case, a king move is required
|
|
if (more_than_one(checkers()))
|
|
return false;
|
|
|
|
// Our move must be a blocking interposition or a capture of the checking piece
|
|
if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
|
|
return false;
|
|
}
|
|
// In case of king moves under check we have to remove the king so as to catch
|
|
// invalid moves like b1a1 when opposite queen is on c1.
|
|
else if (attackers_to_exist(to, pieces() ^ from, ~us))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Tests whether a pseudo-legal move gives a check
|
|
bool Position::gives_check(Move m) const {
|
|
|
|
assert(m.is_ok());
|
|
assert(color_of(moved_piece(m)) == sideToMove);
|
|
|
|
Square from = m.from_sq();
|
|
Square to = m.to_sq();
|
|
|
|
// Is there a direct check?
|
|
if (check_squares(type_of(piece_on(from))) & to)
|
|
return true;
|
|
|
|
// Is there a discovered check?
|
|
if (blockers_for_king(~sideToMove) & from)
|
|
return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING;
|
|
|
|
switch (m.type_of())
|
|
{
|
|
case NORMAL :
|
|
return false;
|
|
|
|
case PROMOTION :
|
|
return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING);
|
|
|
|
// En passant capture with check? We have already handled the case of direct
|
|
// checks and ordinary discovered check, so the only case we need to handle
|
|
// is the unusual case of a discovered check through the captured pawn.
|
|
case EN_PASSANT : {
|
|
Square capsq = make_square(file_of(to), rank_of(from));
|
|
Bitboard b = (pieces() ^ from ^ capsq) | to;
|
|
|
|
return (attacks_bb<ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
|
|
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b)
|
|
& pieces(sideToMove, QUEEN, BISHOP));
|
|
}
|
|
default : //CASTLING
|
|
{
|
|
// Castling is encoded as 'king captures the rook'
|
|
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
|
|
|
|
return check_squares(ROOK) & rto;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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.
|
|
// If a pointer to the TT table is passed, the entry for the new position
|
|
// will be prefetched
|
|
DirtyPiece Position::do_move(Move m,
|
|
StateInfo& newSt,
|
|
bool givesCheck,
|
|
const TranspositionTable* tt = nullptr) {
|
|
|
|
assert(m.is_ok());
|
|
assert(&newSt != st);
|
|
|
|
Key k = st->key ^ Zobrist::side;
|
|
|
|
// Copy some fields of the old state to our new StateInfo object except the
|
|
// ones which are going to be recalculated from scratch anyway and then switch
|
|
// our state pointer to point to the new (ready to be updated) state.
|
|
std::memcpy(&newSt, st, offsetof(StateInfo, key));
|
|
newSt.previous = st;
|
|
st = &newSt;
|
|
|
|
// Increment ply counters. In particular, rule50 will be reset to zero later on
|
|
// in case of a capture or a pawn move.
|
|
++gamePly;
|
|
++st->rule50;
|
|
++st->pliesFromNull;
|
|
|
|
Color us = sideToMove;
|
|
Color them = ~us;
|
|
Square from = m.from_sq();
|
|
Square to = m.to_sq();
|
|
Piece pc = piece_on(from);
|
|
Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);
|
|
|
|
bool checkEP = false;
|
|
|
|
DirtyPiece dp;
|
|
dp.pc = pc;
|
|
dp.from = from;
|
|
dp.to = to;
|
|
dp.add_sq = SQ_NONE;
|
|
|
|
assert(color_of(pc) == us);
|
|
assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us));
|
|
assert(type_of(captured) != KING);
|
|
|
|
if (m.type_of() == CASTLING)
|
|
{
|
|
assert(pc == make_piece(us, KING));
|
|
assert(captured == make_piece(us, ROOK));
|
|
|
|
Square rfrom, rto;
|
|
do_castling<true>(us, from, to, rfrom, rto, &dp);
|
|
|
|
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
|
st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
|
captured = NO_PIECE;
|
|
}
|
|
else if (captured)
|
|
{
|
|
Square capsq = to;
|
|
|
|
// If the captured piece is a pawn, update pawn hash key, otherwise
|
|
// update non-pawn material.
|
|
if (type_of(captured) == PAWN)
|
|
{
|
|
if (m.type_of() == EN_PASSANT)
|
|
{
|
|
capsq -= pawn_push(us);
|
|
|
|
assert(pc == make_piece(us, PAWN));
|
|
assert(to == st->epSquare);
|
|
assert(relative_rank(us, to) == RANK_6);
|
|
assert(piece_on(to) == NO_PIECE);
|
|
assert(piece_on(capsq) == make_piece(them, PAWN));
|
|
}
|
|
|
|
st->pawnKey ^= Zobrist::psq[captured][capsq];
|
|
}
|
|
else
|
|
{
|
|
st->nonPawnMaterial[them] -= PieceValue[captured];
|
|
st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq];
|
|
|
|
if (type_of(captured) <= BISHOP)
|
|
st->minorPieceKey ^= Zobrist::psq[captured][capsq];
|
|
}
|
|
|
|
dp.remove_pc = captured;
|
|
dp.remove_sq = capsq;
|
|
|
|
// Update board and piece lists
|
|
remove_piece(capsq);
|
|
|
|
k ^= Zobrist::psq[captured][capsq];
|
|
st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]];
|
|
|
|
// Reset rule 50 counter
|
|
st->rule50 = 0;
|
|
}
|
|
else
|
|
dp.remove_sq = SQ_NONE;
|
|
|
|
// Update hash key
|
|
k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
|
|
|
// Reset en passant square
|
|
if (st->epSquare != SQ_NONE)
|
|
{
|
|
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
|
st->epSquare = SQ_NONE;
|
|
}
|
|
|
|
// Update castling rights if needed
|
|
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
|
|
{
|
|
k ^= Zobrist::castling[st->castlingRights];
|
|
st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
|
|
k ^= Zobrist::castling[st->castlingRights];
|
|
}
|
|
|
|
// Move the piece. The tricky Chess960 castling is handled earlier
|
|
if (m.type_of() != CASTLING)
|
|
move_piece(from, to);
|
|
|
|
// If the moving piece is a pawn do some special extra work
|
|
if (type_of(pc) == PAWN)
|
|
{
|
|
// Check later if the en passant square needs to be set
|
|
if ((int(to) ^ int(from)) == 16)
|
|
checkEP = true;
|
|
|
|
else if (m.type_of() == PROMOTION)
|
|
{
|
|
Piece promotion = make_piece(us, m.promotion_type());
|
|
PieceType promotionType = type_of(promotion);
|
|
|
|
assert(relative_rank(us, to) == RANK_8);
|
|
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
|
|
|
|
remove_piece(to);
|
|
put_piece(promotion, to);
|
|
|
|
dp.add_pc = promotion;
|
|
dp.add_sq = to;
|
|
dp.to = SQ_NONE;
|
|
|
|
// Update hash keys
|
|
// Zobrist::psq[pc][to] is zero, so we don't need to clear it
|
|
k ^= Zobrist::psq[promotion][to];
|
|
st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1]
|
|
^ Zobrist::psq[pc][8 + pieceCount[pc]];
|
|
|
|
if (promotionType <= BISHOP)
|
|
st->minorPieceKey ^= Zobrist::psq[promotion][to];
|
|
|
|
// Update material
|
|
st->nonPawnMaterial[us] += PieceValue[promotion];
|
|
}
|
|
|
|
// Update pawn hash key
|
|
st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
|
|
|
// Reset rule 50 draw counter
|
|
st->rule50 = 0;
|
|
}
|
|
|
|
else
|
|
{
|
|
st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
|
|
|
if (type_of(pc) <= BISHOP)
|
|
st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
|
}
|
|
|
|
// If en passant is impossible, then k will not change and we can prefetch earlier
|
|
if (tt && !checkEP)
|
|
prefetch(tt->first_entry(adjust_key50(k)));
|
|
|
|
// Set capture piece
|
|
st->capturedPiece = captured;
|
|
|
|
// Calculate checkers bitboard (if move gives check)
|
|
st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
|
|
|
|
sideToMove = ~sideToMove;
|
|
|
|
// Update king attacks used for fast check detection
|
|
set_check_info();
|
|
|
|
// Accurate e.p. info is needed for correct zobrist key generation and 3-fold checking
|
|
while (checkEP)
|
|
{
|
|
auto updateEpSquare = [&] {
|
|
st->epSquare = to - pawn_push(us);
|
|
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
|
};
|
|
|
|
Bitboard pawns = attacks_bb<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN);
|
|
|
|
// If there are no pawns attacking the ep square, ep is not possible
|
|
if (!pawns)
|
|
break;
|
|
|
|
// If there are checkers other than the to be captured pawn, ep is never legal
|
|
if (checkers() & ~square_bb(to))
|
|
break;
|
|
|
|
if (more_than_one(pawns))
|
|
{
|
|
// If there are two pawns potentially being abled to capture and at least one
|
|
// is not pinned, ep is legal as there are no horizontal exposed checks
|
|
if (!more_than_one(blockers_for_king(them) & pawns))
|
|
{
|
|
updateEpSquare();
|
|
break;
|
|
}
|
|
|
|
// If there is no pawn on our king's file, and thus both pawns are pinned
|
|
// by bishops, ep is not legal as the king square must be in front of the to square.
|
|
// And because the ep square and the king are not on a common diagonal, either ep capture
|
|
// would expose the king to a check from one of the bishops
|
|
if (!(file_bb(square<KING>(them)) & pawns))
|
|
break;
|
|
|
|
// Otherwise remove the pawn on the king file, as an ep capture by it can never be legal and the
|
|
// check below relies on there only being one pawn
|
|
pawns &= ~file_bb(square<KING>(them));
|
|
}
|
|
|
|
Square ksq = square<KING>(them);
|
|
Square capsq = to;
|
|
Bitboard occupied = (pieces() ^ lsb(pawns) ^ capsq) | (to - pawn_push(us));
|
|
|
|
// If our king is not attacked after making the move, ep is legal.
|
|
if (!(attacks_bb<ROOK>(ksq, occupied) & pieces(us, QUEEN, ROOK))
|
|
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(us, QUEEN, BISHOP)))
|
|
updateEpSquare();
|
|
|
|
break;
|
|
}
|
|
|
|
// Update the key with the final value
|
|
st->key = k;
|
|
if (tt)
|
|
prefetch(tt->first_entry(key()));
|
|
|
|
// Calculate the repetition info. It is the ply distance from the previous
|
|
// occurrence of the same position, negative in the 3-fold case, or zero
|
|
// if the position was not repeated.
|
|
st->repetition = 0;
|
|
int end = std::min(st->rule50, st->pliesFromNull);
|
|
if (end >= 4)
|
|
{
|
|
StateInfo* stp = st->previous->previous;
|
|
for (int i = 4; i <= end; i += 2)
|
|
{
|
|
stp = stp->previous->previous;
|
|
if (stp->key == st->key)
|
|
{
|
|
st->repetition = stp->repetition ? -i : i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
assert(pos_is_ok());
|
|
|
|
assert(dp.pc != NO_PIECE);
|
|
assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE));
|
|
assert(dp.from != SQ_NONE);
|
|
assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING));
|
|
return dp;
|
|
}
|
|
|
|
|
|
// Unmakes a move. When it returns, the position should
|
|
// be restored to exactly the same state as before the move was made.
|
|
void Position::undo_move(Move m) {
|
|
|
|
assert(m.is_ok());
|
|
|
|
sideToMove = ~sideToMove;
|
|
|
|
Color us = sideToMove;
|
|
Square from = m.from_sq();
|
|
Square to = m.to_sq();
|
|
Piece pc = piece_on(to);
|
|
|
|
assert(empty(from) || m.type_of() == CASTLING);
|
|
assert(type_of(st->capturedPiece) != KING);
|
|
|
|
if (m.type_of() == PROMOTION)
|
|
{
|
|
assert(relative_rank(us, to) == RANK_8);
|
|
assert(type_of(pc) == m.promotion_type());
|
|
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
|
|
|
|
remove_piece(to);
|
|
pc = make_piece(us, PAWN);
|
|
put_piece(pc, to);
|
|
}
|
|
|
|
if (m.type_of() == CASTLING)
|
|
{
|
|
Square rfrom, rto;
|
|
do_castling<false>(us, from, to, rfrom, rto);
|
|
}
|
|
else
|
|
{
|
|
move_piece(to, from); // Put the piece back at the source square
|
|
|
|
if (st->capturedPiece)
|
|
{
|
|
Square capsq = to;
|
|
|
|
if (m.type_of() == EN_PASSANT)
|
|
{
|
|
capsq -= pawn_push(us);
|
|
|
|
assert(type_of(pc) == PAWN);
|
|
assert(to == st->previous->epSquare);
|
|
assert(relative_rank(us, to) == RANK_6);
|
|
assert(piece_on(capsq) == NO_PIECE);
|
|
assert(st->capturedPiece == make_piece(~us, PAWN));
|
|
}
|
|
|
|
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
|
}
|
|
}
|
|
|
|
// Finally point our state pointer back to the previous state
|
|
st = st->previous;
|
|
--gamePly;
|
|
|
|
assert(pos_is_ok());
|
|
}
|
|
|
|
|
|
// Helper used to do/undo a castling move. This is a bit
|
|
// tricky in Chess960 where from/to squares can overlap.
|
|
template<bool Do>
|
|
void Position::do_castling(
|
|
Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) {
|
|
|
|
bool kingSide = to > from;
|
|
rfrom = to; // Castling is encoded as "king captures friendly rook"
|
|
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
|
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
|
|
|
assert(!Do || dp);
|
|
|
|
if (Do)
|
|
{
|
|
dp->to = to;
|
|
dp->remove_pc = dp->add_pc = make_piece(us, ROOK);
|
|
dp->remove_sq = rfrom;
|
|
dp->add_sq = rto;
|
|
}
|
|
|
|
// Remove both pieces first since squares could overlap in Chess960
|
|
remove_piece(Do ? from : to);
|
|
remove_piece(Do ? rfrom : rto);
|
|
board[Do ? from : to] = board[Do ? rfrom : rto] =
|
|
NO_PIECE; // remove_piece does not do this for us
|
|
put_piece(make_piece(us, KING), Do ? to : from);
|
|
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
|
|
}
|
|
|
|
|
|
// Used to do a "null move": it flips
|
|
// the side to move without executing any move on the board.
|
|
void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) {
|
|
|
|
assert(!checkers());
|
|
assert(&newSt != st);
|
|
|
|
std::memcpy(&newSt, st, sizeof(StateInfo));
|
|
|
|
newSt.previous = st;
|
|
st = &newSt;
|
|
|
|
if (st->epSquare != SQ_NONE)
|
|
{
|
|
st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
|
|
st->epSquare = SQ_NONE;
|
|
}
|
|
|
|
st->key ^= Zobrist::side;
|
|
prefetch(tt.first_entry(key()));
|
|
|
|
st->pliesFromNull = 0;
|
|
|
|
sideToMove = ~sideToMove;
|
|
|
|
set_check_info();
|
|
|
|
st->repetition = 0;
|
|
|
|
assert(pos_is_ok());
|
|
}
|
|
|
|
|
|
// Must be used to undo a "null move"
|
|
void Position::undo_null_move() {
|
|
|
|
assert(!checkers());
|
|
|
|
st = st->previous;
|
|
sideToMove = ~sideToMove;
|
|
}
|
|
|
|
|
|
// Tests if the SEE (Static Exchange Evaluation)
|
|
// value of move is greater or equal to the given threshold. We'll use an
|
|
// algorithm similar to alpha-beta pruning with a null window.
|
|
bool Position::see_ge(Move m, int threshold) const {
|
|
|
|
assert(m.is_ok());
|
|
|
|
// Only deal with normal moves, assume others pass a simple SEE
|
|
if (m.type_of() != NORMAL)
|
|
return VALUE_ZERO >= threshold;
|
|
|
|
Square from = m.from_sq(), to = m.to_sq();
|
|
|
|
assert(piece_on(from) != NO_PIECE);
|
|
|
|
int swap = PieceValue[piece_on(to)] - threshold;
|
|
if (swap < 0)
|
|
return false;
|
|
|
|
swap = PieceValue[piece_on(from)] - swap;
|
|
if (swap <= 0)
|
|
return true;
|
|
|
|
assert(color_of(piece_on(from)) == sideToMove);
|
|
Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic
|
|
Color stm = sideToMove;
|
|
Bitboard attackers = attackers_to(to, occupied);
|
|
Bitboard stmAttackers, bb;
|
|
int res = 1;
|
|
|
|
while (true)
|
|
{
|
|
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 as long as there are
|
|
// pinners on their original square.
|
|
if (pinners(~stm) & occupied)
|
|
{
|
|
stmAttackers &= ~blockers_for_king(stm);
|
|
|
|
if (!stmAttackers)
|
|
break;
|
|
}
|
|
|
|
res ^= 1;
|
|
|
|
// Locate and remove the next least valuable attacker, and add to
|
|
// the bitboard 'attackers' any X-ray attackers behind it.
|
|
if ((bb = stmAttackers & pieces(PAWN)))
|
|
{
|
|
if ((swap = PawnValue - swap) < res)
|
|
break;
|
|
occupied ^= least_significant_square_bb(bb);
|
|
|
|
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
|
}
|
|
|
|
else if ((bb = stmAttackers & pieces(KNIGHT)))
|
|
{
|
|
if ((swap = KnightValue - swap) < res)
|
|
break;
|
|
occupied ^= least_significant_square_bb(bb);
|
|
}
|
|
|
|
else if ((bb = stmAttackers & pieces(BISHOP)))
|
|
{
|
|
if ((swap = BishopValue - swap) < res)
|
|
break;
|
|
occupied ^= least_significant_square_bb(bb);
|
|
|
|
attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
|
|
}
|
|
|
|
else if ((bb = stmAttackers & pieces(ROOK)))
|
|
{
|
|
if ((swap = RookValue - swap) < res)
|
|
break;
|
|
occupied ^= least_significant_square_bb(bb);
|
|
|
|
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
|
|
}
|
|
|
|
else if ((bb = stmAttackers & pieces(QUEEN)))
|
|
{
|
|
swap = QueenValue - swap;
|
|
// implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded)
|
|
assert(swap >= res);
|
|
occupied ^= least_significant_square_bb(bb);
|
|
|
|
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
|
|
| (attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN));
|
|
}
|
|
|
|
else // KING
|
|
// If we "capture" with the king but the opponent still has attackers,
|
|
// reverse the result.
|
|
return (attackers & ~pieces(stm)) ? res ^ 1 : res;
|
|
}
|
|
|
|
return bool(res);
|
|
}
|
|
|
|
// Tests whether the position is drawn by 50-move rule
|
|
// or by repetition. It does not detect stalemates.
|
|
bool Position::is_draw(int ply) const {
|
|
|
|
if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
|
|
return true;
|
|
|
|
return is_repetition(ply);
|
|
}
|
|
|
|
// Return a draw score if a position repeats once earlier but strictly
|
|
// after the root, or repeats twice before or at the root.
|
|
bool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; }
|
|
|
|
// 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;
|
|
int end = std::min(st->rule50, st->pliesFromNull);
|
|
while (end-- >= 4)
|
|
{
|
|
if (stc->repetition)
|
|
return true;
|
|
|
|
stc = stc->previous;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Tests if the position has a move which draws by repetition.
|
|
// This function accurately matches the outcome of is_draw() over all legal moves.
|
|
bool Position::upcoming_repetition(int ply) const {
|
|
|
|
int j;
|
|
|
|
int end = std::min(st->rule50, st->pliesFromNull);
|
|
|
|
if (end < 3)
|
|
return false;
|
|
|
|
Key originalKey = st->key;
|
|
StateInfo* stp = st->previous;
|
|
Key other = originalKey ^ stp->key ^ Zobrist::side;
|
|
|
|
for (int i = 3; i <= end; i += 2)
|
|
{
|
|
stp = stp->previous;
|
|
other ^= stp->key ^ stp->previous->key ^ Zobrist::side;
|
|
stp = stp->previous;
|
|
|
|
if (other != 0)
|
|
continue;
|
|
|
|
Key moveKey = originalKey ^ stp->key;
|
|
if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey))
|
|
{
|
|
Move move = cuckooMove[j];
|
|
Square s1 = move.from_sq();
|
|
Square s2 = move.to_sq();
|
|
|
|
if (!((between_bb(s1, s2) ^ s2) & pieces()))
|
|
{
|
|
if (ply > i)
|
|
return true;
|
|
|
|
// For nodes before or at the root, check that the move is a
|
|
// repetition rather than a move to the current position.
|
|
if (stp->repetition)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Flips position with the white and black sides reversed. This
|
|
// is only useful for debugging e.g. for finding evaluation symmetry bugs.
|
|
void Position::flip() {
|
|
|
|
string f, token;
|
|
std::stringstream ss(fen());
|
|
|
|
for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
|
|
{
|
|
std::getline(ss, token, r > RANK_1 ? '/' : ' ');
|
|
f.insert(0, token + (f.empty() ? " " : "/"));
|
|
}
|
|
|
|
ss >> token; // Active color
|
|
f += (token == "w" ? "B " : "W "); // Will be lowercased later
|
|
|
|
ss >> token; // Castling availability
|
|
f += token + " ";
|
|
|
|
std::transform(f.begin(), f.end(), f.begin(),
|
|
[](char c) { return char(islower(c) ? toupper(c) : tolower(c)); });
|
|
|
|
ss >> token; // En passant square
|
|
f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3"));
|
|
|
|
std::getline(ss, token); // Half and full moves
|
|
f += token;
|
|
|
|
set(f, is_chess960(), st);
|
|
|
|
assert(pos_is_ok());
|
|
}
|
|
|
|
|
|
// Performs some consistency checks for the position object
|
|
// and raise an assert if something wrong is detected.
|
|
// This is meant to be helpful when debugging.
|
|
bool Position::pos_is_ok() const {
|
|
|
|
constexpr bool Fast = true; // Quick (default) or full check?
|
|
|
|
if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square<KING>(WHITE)) != W_KING
|
|
|| piece_on(square<KING>(BLACK)) != B_KING
|
|
|| (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6))
|
|
assert(0 && "pos_is_ok: Default");
|
|
|
|
if (Fast)
|
|
return true;
|
|
|
|
if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1
|
|
|| attackers_to_exist(square<KING>(~sideToMove), pieces(), sideToMove))
|
|
assert(0 && "pos_is_ok: Kings");
|
|
|
|
if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8)
|
|
assert(0 && "pos_is_ok: Pawns");
|
|
|
|
if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces()
|
|
|| popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16)
|
|
assert(0 && "pos_is_ok: Bitboards");
|
|
|
|
for (PieceType p1 = PAWN; p1 <= KING; ++p1)
|
|
for (PieceType p2 = PAWN; p2 <= KING; ++p2)
|
|
if (p1 != p2 && (pieces(p1) & pieces(p2)))
|
|
assert(0 && "pos_is_ok: Bitboards");
|
|
|
|
|
|
for (Piece pc : Pieces)
|
|
if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|
|
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|
|
assert(0 && "pos_is_ok: Pieces");
|
|
|
|
for (Color c : {WHITE, BLACK})
|
|
for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
|
|
{
|
|
if (!can_castle(cr))
|
|
continue;
|
|
|
|
if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
|
|
|| castlingRightsMask[castlingRookSquare[cr]] != cr
|
|
|| (castlingRightsMask[square<KING>(c)] & cr) != cr)
|
|
assert(0 && "pos_is_ok: Castling");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace Stockfish
|