|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <algorithm> |
|
#include <cassert> |
|
#include <cstddef> |
|
#include <cstring> |
|
#include <iomanip> |
|
#include <sstream> |
|
|
|
#include "bitboard.h" |
|
#include "misc.h" |
|
#include "movegen.h" |
|
#include "position.h" |
|
#include "thread.h" |
|
#include "tt.h" |
|
#include "uci.h" |
|
#include "syzygy/tbprobe.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 { |
|
|
|
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 }; |
|
} |
|
|
|
|
|
|
|
|
|
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 << UCI::square(pop_lsb(b)) << " "; |
|
|
|
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) |
|
&& !pos.can_castle(ANY_CASTLING)) |
|
{ |
|
StateInfo st; |
|
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); |
|
|
|
Position p; |
|
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline int H1(Key h) { return h & 0x1fff; } |
|
inline int H2(Key h) { return (h >> 16) & 0x1fff; } |
|
|
|
|
|
Key cuckoo[8192]; |
|
Move cuckooMove[8192]; |
|
|
|
|
|
|
|
|
|
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>(); |
|
|
|
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>(); |
|
|
|
|
|
std::memset(cuckoo, 0, sizeof(cuckoo)); |
|
std::memset(cuckooMove, 0, sizeof(cuckooMove)); |
|
[[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 = make_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) |
|
break; |
|
i = (i == H1(key)) ? H2(key) : H1(key); |
|
} |
|
count++; |
|
} |
|
assert(count == 3668); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
while ((ss >> token) && !isspace(token)) |
|
{ |
|
if (isdigit(token)) |
|
sq += (token - '0') * EAST; |
|
|
|
else if (token == '/') |
|
sq += 2 * SOUTH; |
|
|
|
else if ((idx = PieceToChar.find(token)) != string::npos) { |
|
put_piece(Piece(idx), sq); |
|
++sq; |
|
} |
|
} |
|
|
|
|
|
ss >> token; |
|
sideToMove = (token == 'w' ? WHITE : BLACK); |
|
ss >> token; |
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
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')); |
|
|
|
|
|
|
|
|
|
|
|
enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & 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; |
|
|
|
|
|
ss >> std::skipws >> st->rule50 >> gamePly; |
|
|
|
|
|
|
|
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); |
|
|
|
chess960 = isChess960; |
|
thisThread = th; |
|
set_state(st); |
|
|
|
assert(pos_is_ok()); |
|
|
|
return *this; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
} |
|
|
|
|
|
|
|
|
|
void Position::set_check_info(StateInfo* si) const { |
|
|
|
si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]); |
|
si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]); |
|
|
|
Square ksq = square<KING>(~sideToMove); |
|
|
|
si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); |
|
si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq); |
|
si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces()); |
|
si->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces()); |
|
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; |
|
si->checkSquares[KING] = 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Position::set_state(StateInfo* si) const { |
|
|
|
si->key = si->materialKey = 0; |
|
si->pawnKey = Zobrist::noPawns; |
|
si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; |
|
si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove); |
|
|
|
set_check_info(si); |
|
|
|
for (Bitboard b = pieces(); b; ) |
|
{ |
|
Square s = pop_lsb(b); |
|
Piece pc = piece_on(s); |
|
si->key ^= Zobrist::psq[pc][s]; |
|
|
|
if (type_of(pc) == PAWN) |
|
si->pawnKey ^= Zobrist::psq[pc][s]; |
|
|
|
else if (type_of(pc) != KING) |
|
si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; |
|
} |
|
|
|
if (si->epSquare != SQ_NONE) |
|
si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; |
|
|
|
if (sideToMove == BLACK) |
|
si->key ^= Zobrist::side; |
|
|
|
si->key ^= Zobrist::castling[si->castlingRights]; |
|
|
|
for (Piece pc : Pieces) |
|
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) |
|
si->materialKey ^= Zobrist::psq[pc][cnt]; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
Position& Position::set(const string& code, Color c, StateInfo* si) { |
|
|
|
assert(code[0] == 'K'); |
|
|
|
string sides[] = { code.substr(code.find('K', 1)), |
|
code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; |
|
|
|
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, nullptr); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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 ? " - " : " " + UCI::square(ep_square()) + " ") |
|
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; |
|
|
|
return ss.str(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { |
|
|
|
Bitboard blockers = 0; |
|
pinners = 0; |
|
|
|
|
|
Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) |
|
| (attacks_bb<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders; |
|
Bitboard occupancy = pieces() ^ snipers; |
|
|
|
while (snipers) |
|
{ |
|
Square sniperSq = pop_lsb(snipers); |
|
Bitboard b = between_bb(s, sniperSq) & occupancy; |
|
|
|
if (b && !more_than_one(b)) |
|
{ |
|
blockers |= b; |
|
if (b & pieces(color_of(piece_on(s)))) |
|
pinners |= sniperSq; |
|
} |
|
} |
|
return blockers; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
Bitboard Position::attackers_to(Square s, Bitboard occupied) const { |
|
|
|
return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) |
|
| (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) |
|
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT)) |
|
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) |
|
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN)) |
|
| (attacks_bb<KING>(s) & pieces(KING)); |
|
} |
|
|
|
|
|
|
|
|
|
bool Position::legal(Move m) const { |
|
|
|
assert(is_ok(m)); |
|
|
|
Color us = sideToMove; |
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
|
|
assert(color_of(moved_piece(m)) == us); |
|
assert(piece_on(square<KING>(us)) == make_piece(us, KING)); |
|
|
|
|
|
|
|
|
|
if (type_of(m) == 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)); |
|
} |
|
|
|
|
|
|
|
if (type_of(m) == CASTLING) |
|
{ |
|
|
|
|
|
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(s) & pieces(~us)) |
|
return false; |
|
|
|
|
|
|
|
return !chess960 || !(blockers_for_king(us) & to_sq(m)); |
|
} |
|
|
|
|
|
|
|
if (type_of(piece_on(from)) == KING) |
|
return !(attackers_to(to, pieces() ^ from) & pieces(~us)); |
|
|
|
|
|
|
|
return !(blockers_for_king(us) & from) |
|
|| aligned(from, to, square<KING>(us)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Position::pseudo_legal(const Move m) const { |
|
|
|
Color us = sideToMove; |
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
Piece pc = moved_piece(m); |
|
|
|
|
|
|
|
if (type_of(m) != NORMAL) |
|
return checkers() ? MoveList< EVASIONS>(*this).contains(m) |
|
: MoveList<NON_EVASIONS>(*this).contains(m); |
|
|
|
|
|
if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) |
|
return false; |
|
|
|
|
|
|
|
if (pc == NO_PIECE || color_of(pc) != us) |
|
return false; |
|
|
|
|
|
if (pieces(us) & to) |
|
return false; |
|
|
|
|
|
if (type_of(pc) == PAWN) |
|
{ |
|
|
|
|
|
if ((Rank8BB | Rank1BB) & to) |
|
return false; |
|
|
|
if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) |
|
&& !((from + pawn_push(us) == to) && empty(to)) |
|
&& !( (from + 2 * pawn_push(us) == to) |
|
&& (relative_rank(us, from) == RANK_2) |
|
&& empty(to) |
|
&& empty(to - pawn_push(us)))) |
|
return false; |
|
} |
|
else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) |
|
return false; |
|
|
|
|
|
|
|
|
|
if (checkers()) |
|
{ |
|
if (type_of(pc) != KING) |
|
{ |
|
|
|
if (more_than_one(checkers())) |
|
return false; |
|
|
|
|
|
if (!(between_bb(square<KING>(us), lsb(checkers())) & to)) |
|
return false; |
|
} |
|
|
|
|
|
else if (attackers_to(to, pieces() ^ from) & pieces(~us)) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
bool Position::gives_check(Move m) const { |
|
|
|
assert(is_ok(m)); |
|
assert(color_of(moved_piece(m)) == sideToMove); |
|
|
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
|
|
|
|
if (check_squares(type_of(piece_on(from))) & to) |
|
return true; |
|
|
|
|
|
if ( (blockers_for_king(~sideToMove) & from) |
|
&& !aligned(from, to, square<KING>(~sideToMove))) |
|
return true; |
|
|
|
switch (type_of(m)) |
|
{ |
|
case NORMAL: |
|
return false; |
|
|
|
case PROMOTION: |
|
return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove); |
|
|
|
|
|
|
|
|
|
|
|
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: |
|
{ |
|
|
|
Square ksq = square<KING>(~sideToMove); |
|
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); |
|
|
|
return (attacks_bb<ROOK>(rto) & ksq) |
|
&& (attacks_bb<ROOK>(rto, pieces() ^ from ^ to) & ksq); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { |
|
|
|
assert(is_ok(m)); |
|
assert(&newSt != st); |
|
|
|
thisThread->nodes.fetch_add(1, std::memory_order_relaxed); |
|
Key k = st->key ^ Zobrist::side; |
|
|
|
|
|
|
|
|
|
std::memcpy(&newSt, st, offsetof(StateInfo, key)); |
|
newSt.previous = st; |
|
st = &newSt; |
|
|
|
|
|
|
|
++gamePly; |
|
++st->rule50; |
|
++st->pliesFromNull; |
|
|
|
|
|
st->accumulator.computed[WHITE] = false; |
|
st->accumulator.computed[BLACK] = false; |
|
auto& dp = st->dirtyPiece; |
|
dp.dirty_num = 1; |
|
|
|
Color us = sideToMove; |
|
Color them = ~us; |
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
Piece pc = piece_on(from); |
|
Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); |
|
|
|
assert(color_of(pc) == us); |
|
assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); |
|
assert(type_of(captured) != KING); |
|
|
|
if (type_of(m) == CASTLING) |
|
{ |
|
assert(pc == make_piece(us, KING)); |
|
assert(captured == make_piece(us, ROOK)); |
|
|
|
Square rfrom, rto; |
|
do_castling<true>(us, from, to, rfrom, rto); |
|
|
|
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; |
|
captured = NO_PIECE; |
|
} |
|
|
|
if (captured) |
|
{ |
|
Square capsq = to; |
|
|
|
|
|
|
|
if (type_of(captured) == PAWN) |
|
{ |
|
if (type_of(m) == 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[MG][captured]; |
|
|
|
if (Eval::useNNUE) |
|
{ |
|
dp.dirty_num = 2; |
|
dp.piece[1] = captured; |
|
dp.from[1] = capsq; |
|
dp.to[1] = SQ_NONE; |
|
} |
|
|
|
|
|
remove_piece(capsq); |
|
|
|
if (type_of(m) == EN_PASSANT) |
|
board[capsq] = NO_PIECE; |
|
|
|
|
|
k ^= Zobrist::psq[captured][capsq]; |
|
st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; |
|
prefetch(thisThread->materialTable[st->materialKey]); |
|
|
|
|
|
st->rule50 = 0; |
|
} |
|
|
|
|
|
k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; |
|
|
|
|
|
if (st->epSquare != SQ_NONE) |
|
{ |
|
k ^= Zobrist::enpassant[file_of(st->epSquare)]; |
|
st->epSquare = SQ_NONE; |
|
} |
|
|
|
|
|
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) |
|
{ |
|
k ^= Zobrist::castling[st->castlingRights]; |
|
st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); |
|
k ^= Zobrist::castling[st->castlingRights]; |
|
} |
|
|
|
|
|
if (type_of(m) != CASTLING) |
|
{ |
|
if (Eval::useNNUE) |
|
{ |
|
dp.piece[0] = pc; |
|
dp.from[0] = from; |
|
dp.to[0] = to; |
|
} |
|
|
|
move_piece(from, to); |
|
} |
|
|
|
|
|
if (type_of(pc) == PAWN) |
|
{ |
|
|
|
if ( (int(to) ^ int(from)) == 16 |
|
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) |
|
{ |
|
st->epSquare = to - pawn_push(us); |
|
k ^= Zobrist::enpassant[file_of(st->epSquare)]; |
|
} |
|
|
|
else if (type_of(m) == PROMOTION) |
|
{ |
|
Piece promotion = make_piece(us, promotion_type(m)); |
|
|
|
assert(relative_rank(us, to) == RANK_8); |
|
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); |
|
|
|
remove_piece(to); |
|
put_piece(promotion, to); |
|
|
|
if (Eval::useNNUE) |
|
{ |
|
|
|
dp.to[0] = SQ_NONE; |
|
dp.piece[dp.dirty_num] = promotion; |
|
dp.from[dp.dirty_num] = SQ_NONE; |
|
dp.to[dp.dirty_num] = to; |
|
dp.dirty_num++; |
|
} |
|
|
|
|
|
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; |
|
st->pawnKey ^= Zobrist::psq[pc][to]; |
|
st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] |
|
^ Zobrist::psq[pc][pieceCount[pc]]; |
|
|
|
|
|
st->nonPawnMaterial[us] += PieceValue[MG][promotion]; |
|
} |
|
|
|
|
|
st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; |
|
|
|
|
|
st->rule50 = 0; |
|
} |
|
|
|
|
|
st->capturedPiece = captured; |
|
|
|
|
|
st->key = k; |
|
|
|
|
|
st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0; |
|
|
|
sideToMove = ~sideToMove; |
|
|
|
|
|
set_check_info(st); |
|
|
|
|
|
|
|
|
|
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()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Position::undo_move(Move m) { |
|
|
|
assert(is_ok(m)); |
|
|
|
sideToMove = ~sideToMove; |
|
|
|
Color us = sideToMove; |
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
Piece pc = piece_on(to); |
|
|
|
assert(empty(from) || type_of(m) == CASTLING); |
|
assert(type_of(st->capturedPiece) != KING); |
|
|
|
if (type_of(m) == PROMOTION) |
|
{ |
|
assert(relative_rank(us, to) == RANK_8); |
|
assert(type_of(pc) == promotion_type(m)); |
|
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); |
|
|
|
remove_piece(to); |
|
pc = make_piece(us, PAWN); |
|
put_piece(pc, to); |
|
} |
|
|
|
if (type_of(m) == CASTLING) |
|
{ |
|
Square rfrom, rto; |
|
do_castling<false>(us, from, to, rfrom, rto); |
|
} |
|
else |
|
{ |
|
move_piece(to, from); |
|
|
|
if (st->capturedPiece) |
|
{ |
|
Square capsq = to; |
|
|
|
if (type_of(m) == 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); |
|
} |
|
} |
|
|
|
|
|
st = st->previous; |
|
--gamePly; |
|
|
|
assert(pos_is_ok()); |
|
} |
|
|
|
|
|
|
|
|
|
template<bool Do> |
|
void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { |
|
|
|
bool kingSide = to > from; |
|
rfrom = to; |
|
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); |
|
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); |
|
|
|
if (Do && Eval::useNNUE) |
|
{ |
|
auto& dp = st->dirtyPiece; |
|
dp.piece[0] = make_piece(us, KING); |
|
dp.from[0] = from; |
|
dp.to[0] = to; |
|
dp.piece[1] = make_piece(us, ROOK); |
|
dp.from[1] = rfrom; |
|
dp.to[1] = rto; |
|
dp.dirty_num = 2; |
|
} |
|
|
|
|
|
remove_piece(Do ? from : to); |
|
remove_piece(Do ? rfrom : rto); |
|
board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; |
|
put_piece(make_piece(us, KING), Do ? to : from); |
|
put_piece(make_piece(us, ROOK), Do ? rto : rfrom); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Position::do_null_move(StateInfo& newSt) { |
|
|
|
assert(!checkers()); |
|
assert(&newSt != st); |
|
|
|
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); |
|
|
|
newSt.previous = st; |
|
st = &newSt; |
|
|
|
st->dirtyPiece.dirty_num = 0; |
|
st->dirtyPiece.piece[0] = NO_PIECE; |
|
st->accumulator.computed[WHITE] = false; |
|
st->accumulator.computed[BLACK] = false; |
|
|
|
if (st->epSquare != SQ_NONE) |
|
{ |
|
st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; |
|
st->epSquare = SQ_NONE; |
|
} |
|
|
|
st->key ^= Zobrist::side; |
|
++st->rule50; |
|
prefetch(TT.first_entry(key())); |
|
|
|
st->pliesFromNull = 0; |
|
|
|
sideToMove = ~sideToMove; |
|
|
|
set_check_info(st); |
|
|
|
st->repetition = 0; |
|
|
|
assert(pos_is_ok()); |
|
} |
|
|
|
|
|
|
|
|
|
void Position::undo_null_move() { |
|
|
|
assert(!checkers()); |
|
|
|
st = st->previous; |
|
sideToMove = ~sideToMove; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
Key Position::key_after(Move m) const { |
|
|
|
Square from = from_sq(m); |
|
Square to = to_sq(m); |
|
Piece pc = piece_on(from); |
|
Piece captured = piece_on(to); |
|
Key k = st->key ^ Zobrist::side; |
|
|
|
if (captured) |
|
k ^= Zobrist::psq[captured][to]; |
|
|
|
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; |
|
|
|
return (captured || type_of(pc) == PAWN) |
|
? k : adjust_key50<true>(k); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Position::see_ge(Move m, Value threshold) const { |
|
|
|
assert(is_ok(m)); |
|
|
|
|
|
if (type_of(m) != NORMAL) |
|
return VALUE_ZERO >= threshold; |
|
|
|
Square from = from_sq(m), to = to_sq(m); |
|
|
|
int swap = PieceValue[MG][piece_on(to)] - threshold; |
|
if (swap < 0) |
|
return false; |
|
|
|
swap = PieceValue[MG][piece_on(from)] - swap; |
|
if (swap <= 0) |
|
return true; |
|
|
|
assert(color_of(piece_on(from)) == sideToMove); |
|
Bitboard occupied = pieces() ^ from ^ to; |
|
Color stm = sideToMove; |
|
Bitboard attackers = attackers_to(to, occupied); |
|
Bitboard stmAttackers, bb; |
|
int res = 1; |
|
|
|
while (true) |
|
{ |
|
stm = ~stm; |
|
attackers &= occupied; |
|
|
|
|
|
if (!(stmAttackers = attackers & pieces(stm))) |
|
break; |
|
|
|
|
|
|
|
if (pinners(~stm) & occupied) |
|
{ |
|
stmAttackers &= ~blockers_for_king(stm); |
|
|
|
if (!stmAttackers) |
|
break; |
|
} |
|
|
|
res ^= 1; |
|
|
|
|
|
|
|
if ((bb = stmAttackers & pieces(PAWN))) |
|
{ |
|
if ((swap = PawnValueMg - 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 = KnightValueMg - swap) < res) |
|
break; |
|
|
|
occupied ^= least_significant_square_bb(bb); |
|
} |
|
|
|
else if ((bb = stmAttackers & pieces(BISHOP))) |
|
{ |
|
if ((swap = BishopValueMg - 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 = RookValueMg - swap) < res) |
|
break; |
|
|
|
occupied ^= least_significant_square_bb(bb); |
|
attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN); |
|
} |
|
|
|
else if ((bb = stmAttackers & pieces(QUEEN))) |
|
{ |
|
if ((swap = QueenValueMg - swap) < res) |
|
break; |
|
|
|
occupied ^= least_significant_square_bb(bb); |
|
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN)) |
|
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN)); |
|
} |
|
|
|
else |
|
|
|
|
|
return (attackers & ~pieces(stm)) ? res ^ 1 : res; |
|
} |
|
|
|
return bool(res); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool Position::is_draw(int ply) const { |
|
|
|
if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size())) |
|
return true; |
|
|
|
|
|
|
|
return st->repetition && st->repetition < ply; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool Position::has_game_cycle(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; |
|
|
|
for (int i = 3; i <= end; i += 2) |
|
{ |
|
stp = stp->previous->previous; |
|
|
|
Key moveKey = originalKey ^ stp->key; |
|
if ( (j = H1(moveKey), cuckoo[j] == moveKey) |
|
|| (j = H2(moveKey), cuckoo[j] == moveKey)) |
|
{ |
|
Move move = cuckooMove[j]; |
|
Square s1 = from_sq(move); |
|
Square s2 = to_sq(move); |
|
|
|
if (!((between_bb(s1, s2) ^ s2) & pieces())) |
|
{ |
|
if (ply > i) |
|
return true; |
|
|
|
|
|
|
|
|
|
|
|
if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) |
|
continue; |
|
|
|
|
|
if (stp->repetition) |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
void Position::flip() { |
|
|
|
string f, token; |
|
std::stringstream ss(fen()); |
|
|
|
for (Rank r = RANK_8; r >= RANK_1; --r) |
|
{ |
|
std::getline(ss, token, r > RANK_1 ? '/' : ' '); |
|
f.insert(0, token + (f.empty() ? " " : "/")); |
|
} |
|
|
|
ss >> token; |
|
f += (token == "w" ? "B " : "W "); |
|
|
|
ss >> token; |
|
f += token + " "; |
|
|
|
std::transform(f.begin(), f.end(), f.begin(), |
|
[](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); |
|
|
|
ss >> token; |
|
f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); |
|
|
|
std::getline(ss, token); |
|
f += token; |
|
|
|
set(f, is_chess960(), st, this_thread()); |
|
|
|
assert(pos_is_ok()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
bool Position::pos_is_ok() const { |
|
|
|
constexpr bool Fast = true; |
|
|
|
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(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"); |
|
|
|
StateInfo si = *st; |
|
ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); |
|
|
|
set_state(&si); |
|
if (std::memcmp(&si, st, sizeof(StateInfo))) |
|
assert(0 && "pos_is_ok: State"); |
|
|
|
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; |
|
} |
|
|
|
} |
|
|