1
0
Fork 0
stockfish/src/position.cpp

1212 lines
38 KiB
C++
Raw Normal View History

2008-08-31 23:59:13 -06:00
/*
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-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
2008-08-31 23:59:13 -06:00
Stockfish is free software: you can redistribute it and/or modify
2008-08-31 23:59:13 -06:00
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,
2008-08-31 23:59:13 -06:00
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.
2008-08-31 23:59:13 -06:00
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 <algorithm>
2008-08-31 23:59:13 -06:00
#include <cassert>
#include <cstddef> // For offsetof()
#include <cstring> // For std::memset, std::memcmp
#include <iomanip>
#include <sstream>
2008-08-31 23:59:13 -06:00
#include "bitboard.h"
#include "misc.h"
2008-08-31 23:59:13 -06:00
#include "movegen.h"
#include "position.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
2008-08-31 23:59:13 -06:00
using std::string;
namespace PSQT {
extern Score psq[PIECE_NB][SQUARE_NB];
}
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");
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 };
// 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<int Pt>
PieceType min_attacker(const Bitboard* byTypeBB, Square to, Bitboard stmAttackers,
Bitboard& occupied, Bitboard& attackers) {
Bitboard b = stmAttackers & byTypeBB[Pt];
if (!b)
return min_attacker<Pt + 1>(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<BISHOP>(to, occupied) & (byTypeBB[BISHOP] | byTypeBB[QUEEN]);
if (Pt == ROOK || Pt == QUEEN)
attackers |= attacks_bb<ROOK>(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 (PieceType)Pt;
}
template<>
PieceType min_attacker<KING>(const Bitboard*, Square, Bitboard, Bitboard&, Bitboard&) {
return KING; // No need to update bitboards: it is the last cycle
}
} // namespace
/// operator<<(Position) 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 << " |\n +---+---+---+---+---+---+---+---+\n";
}
os << "\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;
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;
}
/// Position::init() initializes at startup the various arrays used to compute
/// hash keys.
void Position::init() {
Simpler PRNG and faster magics search This patch replaces RKISS by a simpler and faster PRNG, xorshift64* proposed by S. Vigna (2014). It is extremely simple, has a large enough period for Stockfish's needs (2^64), requires no warming-up (allowing such code to be removed), and offers slightly better randomness than MT19937. Paper: http://xorshift.di.unimi.it/ Reference source code (public domain): http://xorshift.di.unimi.it/xorshift64star.c The patch also simplifies how init_magics() searches for magics: - Old logic: seed the PRNG always with the same seed, then use optimized bit rotations to tailor the RNG sequence per rank. - New logic: seed the PRNG with an optimized seed per rank. This has two advantages: 1. Less code and less computation to perform during magics search (not ROTL). 2. More choices for random sequence tuning. The old logic only let us choose from 4096 bit rotation pairs. With the new one, we can look for the best seeds among 2^64 values. Indeed, the set of seeds[][] provided in the patch reduces the effort needed to find the magics: 64-bit SF: Old logic -> 5,783,789 rand64() calls needed to find the magics New logic -> 4,420,086 calls 32-bit SF: Old logic -> 2,175,518 calls New logic -> 1,895,955 calls In the 64-bit case, init_magics() take 25 ms less to complete (Intel Core i5). Finally, when playing with strength handicap, non-determinism is achieved by setting the seed of the static RNG only once. Afterwards, there is no need to skip output values. The bench only changes because the Zobrist keys are now different (since they are random numbers straight out of the PRNG). The RNG seed has been carefully chosen so that the resulting Zobrist keys are particularly well-behaved: 1. All triplets of XORed keys are unique, implying that it would take at least 7 keys to find a 64-bit collision (test suggested by ceebo) 2. All pairs of XORed keys are unique modulo 2^32 3. The cardinality of { (key1 ^ key2) >> 48 } is as close as possible to the maximum (65536) Point 2 aims at ensuring a good distribution among the bits that determine an TT entry's cluster, likewise point 3 among the bits that form the TT entry's key16 inside a cluster. Details: Bitset card(key1^key2) ------ --------------- RKISS key16 64894 = 99.020% of theoretical maximum low18 180117 = 99.293% low32 305362 = 99.997% Xorshift64*, old seed key16 64918 = 99.057% low18 179994 = 99.225% low32 305350 = 99.993% Xorshift64*, new seed key16 65027 = 99.223% low18 181118 = 99.845% low32 305371 = 100.000% Bench: 9324905 Resolves #148
2014-12-07 17:10:57 -07:00
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)
Simpler PRNG and faster magics search This patch replaces RKISS by a simpler and faster PRNG, xorshift64* proposed by S. Vigna (2014). It is extremely simple, has a large enough period for Stockfish's needs (2^64), requires no warming-up (allowing such code to be removed), and offers slightly better randomness than MT19937. Paper: http://xorshift.di.unimi.it/ Reference source code (public domain): http://xorshift.di.unimi.it/xorshift64star.c The patch also simplifies how init_magics() searches for magics: - Old logic: seed the PRNG always with the same seed, then use optimized bit rotations to tailor the RNG sequence per rank. - New logic: seed the PRNG with an optimized seed per rank. This has two advantages: 1. Less code and less computation to perform during magics search (not ROTL). 2. More choices for random sequence tuning. The old logic only let us choose from 4096 bit rotation pairs. With the new one, we can look for the best seeds among 2^64 values. Indeed, the set of seeds[][] provided in the patch reduces the effort needed to find the magics: 64-bit SF: Old logic -> 5,783,789 rand64() calls needed to find the magics New logic -> 4,420,086 calls 32-bit SF: Old logic -> 2,175,518 calls New logic -> 1,895,955 calls In the 64-bit case, init_magics() take 25 ms less to complete (Intel Core i5). Finally, when playing with strength handicap, non-determinism is achieved by setting the seed of the static RNG only once. Afterwards, there is no need to skip output values. The bench only changes because the Zobrist keys are now different (since they are random numbers straight out of the PRNG). The RNG seed has been carefully chosen so that the resulting Zobrist keys are particularly well-behaved: 1. All triplets of XORed keys are unique, implying that it would take at least 7 keys to find a 64-bit collision (test suggested by ceebo) 2. All pairs of XORed keys are unique modulo 2^32 3. The cardinality of { (key1 ^ key2) >> 48 } is as close as possible to the maximum (65536) Point 2 aims at ensuring a good distribution among the bits that determine an TT entry's cluster, likewise point 3 among the bits that form the TT entry's key16 inside a cluster. Details: Bitset card(key1^key2) ------ --------------- RKISS key16 64894 = 99.020% of theoretical maximum low18 180117 = 99.293% low32 305362 = 99.997% Xorshift64*, old seed key16 64918 = 99.057% low18 179994 = 99.225% low32 305350 = 99.993% Xorshift64*, new seed key16 65027 = 99.223% low18 181118 = 99.845% low32 305371 = 100.000% Bench: 9324905 Resolves #148
2014-12-07 17:10:57 -07:00
Zobrist::enpassant[f] = rng.rand<Key>();
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
{
Zobrist::castling[cr] = 0;
Bitboard b = cr;
while (b)
{
Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
Simpler PRNG and faster magics search This patch replaces RKISS by a simpler and faster PRNG, xorshift64* proposed by S. Vigna (2014). It is extremely simple, has a large enough period for Stockfish's needs (2^64), requires no warming-up (allowing such code to be removed), and offers slightly better randomness than MT19937. Paper: http://xorshift.di.unimi.it/ Reference source code (public domain): http://xorshift.di.unimi.it/xorshift64star.c The patch also simplifies how init_magics() searches for magics: - Old logic: seed the PRNG always with the same seed, then use optimized bit rotations to tailor the RNG sequence per rank. - New logic: seed the PRNG with an optimized seed per rank. This has two advantages: 1. Less code and less computation to perform during magics search (not ROTL). 2. More choices for random sequence tuning. The old logic only let us choose from 4096 bit rotation pairs. With the new one, we can look for the best seeds among 2^64 values. Indeed, the set of seeds[][] provided in the patch reduces the effort needed to find the magics: 64-bit SF: Old logic -> 5,783,789 rand64() calls needed to find the magics New logic -> 4,420,086 calls 32-bit SF: Old logic -> 2,175,518 calls New logic -> 1,895,955 calls In the 64-bit case, init_magics() take 25 ms less to complete (Intel Core i5). Finally, when playing with strength handicap, non-determinism is achieved by setting the seed of the static RNG only once. Afterwards, there is no need to skip output values. The bench only changes because the Zobrist keys are now different (since they are random numbers straight out of the PRNG). The RNG seed has been carefully chosen so that the resulting Zobrist keys are particularly well-behaved: 1. All triplets of XORed keys are unique, implying that it would take at least 7 keys to find a 64-bit collision (test suggested by ceebo) 2. All pairs of XORed keys are unique modulo 2^32 3. The cardinality of { (key1 ^ key2) >> 48 } is as close as possible to the maximum (65536) Point 2 aims at ensuring a good distribution among the bits that determine an TT entry's cluster, likewise point 3 among the bits that form the TT entry's key16 inside a cluster. Details: Bitset card(key1^key2) ------ --------------- RKISS key16 64894 = 99.020% of theoretical maximum low18 180117 = 99.293% low32 305362 = 99.997% Xorshift64*, old seed key16 64918 = 99.057% low18 179994 = 99.225% low32 305350 = 99.993% Xorshift64*, new seed key16 65027 = 99.223% low18 181118 = 99.845% low32 305371 = 100.000% Bench: 9324905 Resolves #148
2014-12-07 17:10:57 -07:00
Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
}
}
Simpler PRNG and faster magics search This patch replaces RKISS by a simpler and faster PRNG, xorshift64* proposed by S. Vigna (2014). It is extremely simple, has a large enough period for Stockfish's needs (2^64), requires no warming-up (allowing such code to be removed), and offers slightly better randomness than MT19937. Paper: http://xorshift.di.unimi.it/ Reference source code (public domain): http://xorshift.di.unimi.it/xorshift64star.c The patch also simplifies how init_magics() searches for magics: - Old logic: seed the PRNG always with the same seed, then use optimized bit rotations to tailor the RNG sequence per rank. - New logic: seed the PRNG with an optimized seed per rank. This has two advantages: 1. Less code and less computation to perform during magics search (not ROTL). 2. More choices for random sequence tuning. The old logic only let us choose from 4096 bit rotation pairs. With the new one, we can look for the best seeds among 2^64 values. Indeed, the set of seeds[][] provided in the patch reduces the effort needed to find the magics: 64-bit SF: Old logic -> 5,783,789 rand64() calls needed to find the magics New logic -> 4,420,086 calls 32-bit SF: Old logic -> 2,175,518 calls New logic -> 1,895,955 calls In the 64-bit case, init_magics() take 25 ms less to complete (Intel Core i5). Finally, when playing with strength handicap, non-determinism is achieved by setting the seed of the static RNG only once. Afterwards, there is no need to skip output values. The bench only changes because the Zobrist keys are now different (since they are random numbers straight out of the PRNG). The RNG seed has been carefully chosen so that the resulting Zobrist keys are particularly well-behaved: 1. All triplets of XORed keys are unique, implying that it would take at least 7 keys to find a 64-bit collision (test suggested by ceebo) 2. All pairs of XORed keys are unique modulo 2^32 3. The cardinality of { (key1 ^ key2) >> 48 } is as close as possible to the maximum (65536) Point 2 aims at ensuring a good distribution among the bits that determine an TT entry's cluster, likewise point 3 among the bits that form the TT entry's key16 inside a cluster. Details: Bitset card(key1^key2) ------ --------------- RKISS key16 64894 = 99.020% of theoretical maximum low18 180117 = 99.293% low32 305362 = 99.997% Xorshift64*, old seed key16 64918 = 99.057% low18 179994 = 99.225% low32 305350 = 99.993% Xorshift64*, new seed key16 65027 = 99.223% low18 181118 = 99.845% low32 305371 = 100.000% Bench: 9324905 Resolves #148
2014-12-07 17:10:57 -07:00
Zobrist::side = rng.rand<Key>();
Zobrist::noPawns = rng.rand<Key>();
}
/// Position::set() 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.
2008-08-31 23:59:13 -06:00
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
/*
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. 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.
*/
2008-08-31 23:59:13 -06:00
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));
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
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;
2008-08-31 23:59:13 -06:00
}
}
// 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 no pawn capture is possible
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
&& ((ss >> row) && (row == '3' || row == '6')))
{
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
|| !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
st->epSquare = SQ_NONE;
}
else
st->epSquare = SQ_NONE;
2008-08-31 23:59:13 -06:00
// 5-6. Halfmove clock and fullmove number
ss >> std::skipws >> st->rule50 >> gamePly;
Let ss->ply denote the number of plies from the root to the current node This patch lets ss->ply be equal to 0 at the root of the search. Currently, the root has ss->ply == 1, which is less intuitive: - Setting the rootNode bool has to check (ss-1)->ply == 0. - All mate values are off by one: the code seems to assume that mated-in-0 is -VALUE_MATE, mate-1-in-ply is VALUE_MATE-1, mated-in-2-ply is VALUE_MATE+2, etc. But the mate_in() and mated_in() functions are called with ss->ply, which is 1 in at the root. - The is_draw() function currently needs to explain why it has "ply - 1 > i" instead of simply "ply > i". - The ss->ply >= MAX_PLY tests in search() and qsearch() already assume that ss->ply == 0 at the root. If we start at ss->ply == 1, it would make more sense to go up to and including ss->ply == MAX_PLY, so stop at ss->ply > MAX_PLY. See also the asserts testing for 0 <= ss->ply && ss->ply < MAX_PLY. The reason for ss->ply == 1 at the root is the line "ss->ply = (ss-1)->ply + 1" at the start for search() and qsearch(). By replacing this with "(ss+1)->ply = ss->ply + 1" we keep ss->ply == 0 at the root. Note that search() already clears killers in (ss+2), so there is no danger in accessing ss+1. I have NOT changed pv[MAX_PLY + 1] to pv[MAX_PLY + 2] in search() and qsearch(). It seems to me that MAX_PLY + 1 is exactly right: - MAX_PLY entries for ss->ply running from 0 to MAX_PLY-1, and 1 entry for the final MOVE_NONE. I have verified that mate scores are reported correctly. (They were already reported correctly due to the extra ply being rounded down when converting to moves.) The value of seldepth output to the user should probably not change, so I add 1 to it. (Humans count from 1, computers from 0.) A small optimisation I did not include: instead of setting ss->ply in every invocation of search() and qsearch(), it could be set once for all plies at the start of Thread::search(). This saves a couple of instructions per node. No functional change (unless the search searches a branch MAX_PLY deep), so bench does not change.
2017-09-16 13:49:29 -06:00
// 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);
2008-08-31 23:59:13 -06:00
chess960 = isChess960;
thisThread = th;
set_state(st);
assert(pos_is_ok());
return *this;
}
/// Position::set_castling_right() is a 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);
CastlingSide cs = kfrom < rfrom ? KING_SIDE : QUEEN_SIDE;
CastlingRight cr = (c | cs);
st->castlingRights |= cr;
castlingRightsMask[kfrom] |= cr;
castlingRightsMask[rfrom] |= cr;
castlingRookSquare[cr] = rfrom;
Square kto = relative_square(c, cs == KING_SIDE ? SQ_G1 : SQ_C1);
Square rto = relative_square(c, cs == KING_SIDE ? SQ_F1 : SQ_D1);
for (Square s = std::min(rfrom, rto); s <= std::max(rfrom, rto); ++s)
if (s != kfrom && s != rfrom)
castlingPath[cr] |= s;
for (Square s = std::min(kfrom, kto); s <= std::max(kfrom, kto); ++s)
if (s != kfrom && s != rfrom)
castlingPath[cr] |= s;
}
/// Position::set_check_info() sets king attacks to detect if a move gives check
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] = attacks_from<PAWN>(ksq, ~sideToMove);
si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
si->checkSquares[ROOK] = attacks_from<ROOK>(ksq);
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
si->checkSquares[KING] = 0;
}
/// Position::set_state() 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, and to verify
/// the correctness of the StateInfo data when running in debug mode.
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->psq = SCORE_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];
si->psq += PSQT::psq[pc][s];
}
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 (Bitboard b = pieces(PAWN); b; )
{
Square s = pop_lsb(&b);
si->pawnKey ^= Zobrist::psq[piece_on(s)][s];
}
for (Piece pc : Pieces)
{
if (type_of(pc) != PAWN && type_of(pc) != KING)
si->nonPawnMaterial[color_of(pc)] += pieceCount[pc] * PieceValue[MG][pc];
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
si->materialKey ^= Zobrist::psq[pc][cnt];
}
}
/// Position::set() is an overload to initialize the position object with
/// the given endgame code string like "KBPKN". It is 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.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
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);
}
/// Position::fen() returns a FEN representation of the position. In case of
/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
2008-08-31 23:59:13 -06:00
const string Position::fen() const {
int emptyCnt;
std::ostringstream ss;
2008-08-31 23:59:13 -06:00
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))];
2008-08-31 23:59:13 -06:00
}
if (r > RANK_1)
ss << '/';
2008-08-31 23:59:13 -06:00
}
ss << (sideToMove == WHITE ? " w " : " b ");
if (can_castle(WHITE_OO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE | KING_SIDE))) : 'K');
if (can_castle(WHITE_OOO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE | QUEEN_SIDE))) : 'Q');
if (can_castle(BLACK_OO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK | KING_SIDE))) : 'k');
if (can_castle(BLACK_OOO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK | QUEEN_SIDE))) : 'q');
if (!can_castle(WHITE) && !can_castle(BLACK))
ss << '-';
ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
return ss.str();
2008-08-31 23:59:13 -06:00
}
/// 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
/// square 's' is attacked. For example, a king-attack blocking piece can be either
/// a pinned or a discovered check piece, according if its color is the opposite
/// or the same of the color of the slider.
Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const {
Bitboard blockers = 0;
pinners = 0;
// Snipers are sliders that attack 's' when a piece is removed
Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK))
| (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
while (snipers)
{
Square sniperSq = pop_lsb(&snipers);
Bitboard b = between_bb(s, sniperSq) & pieces();
if (b && !more_than_one(b))
{
blockers |= b;
if (b & pieces(color_of(piece_on(s))))
pinners |= sniperSq;
}
2008-08-31 23:59:13 -06:00
}
return blockers;
2008-08-31 23:59:13 -06:00
}
/// Position::attackers_to() computes a bitboard of all pieces which attack a
/// given square. Slider attacks use the occupied bitboard to indicate occupancy.
2008-08-31 23:59:13 -06:00
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
return (attacks_from<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
| (attacks_from<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
| (attacks_from<KNIGHT>(s) & pieces(KNIGHT))
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
| (attacks_from<KING>(s) & pieces(KING));
}
/// Position::legal() tests whether a pseudo-legal move is legal
bool Position::legal(Move m) const {
assert(is_ok(m));
2008-08-31 23:59:13 -06:00
Color us = sideToMove;
Square from = from_sq(m);
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 (type_of(m) == ENPASSANT)
{
Square ksq = square<KING>(us);
Square to = to_sq(m);
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));
2008-08-31 23:59:13 -06:00
}
// If the moving piece is a king, check whether the destination
// square is attacked by the opponent. Castling moves are checked
// for legality during move generation.
if (type_of(piece_on(from)) == KING)
return type_of(m) == CASTLING || !(attackers_to(to_sq(m)) & pieces(~us));
2008-08-31 23:59:13 -06:00
// 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)
|| aligned(from, to_sq(m), square<KING>(us));
2008-08-31 23:59:13 -06:00
}
/// Position::pseudo_legal() 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 = from_sq(m);
Square to = to_sq(m);
Piece pc = moved_piece(m);
// Use a slower but simpler function for uncommon cases
if (type_of(m) != NORMAL)
return MoveList<LEGAL>(*this).contains(m);
// Is not a promotion, so promotion piece must be empty
if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
return false;
// 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 (rank_of(to) == relative_rank(us, RANK_8))
return false;
if ( !(attacks_from<PAWN>(from, us) & 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))
&& empty(to)
&& empty(to - pawn_push(us))))
return false;
}
else if (!(attacks_from(type_of(pc), from) & 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 evasion or a capture of the checking piece
if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
return false;
}
// In case of king moves under check we have to remove king so as to catch
// invalid moves like b1a1 when opposite queen is on c1.
else if (attackers_to(to, pieces() ^ from) & pieces(~us))
return false;
}
return true;
}
/// Position::gives_check() tests whether a pseudo-legal move gives a check
2008-08-31 23:59:13 -06:00
bool Position::gives_check(Move m) const {
assert(is_ok(m));
assert(color_of(moved_piece(m)) == sideToMove);
2008-08-31 23:59:13 -06:00
Square from = from_sq(m);
Square to = to_sq(m);
// Is there a direct check?
if (st->checkSquares[type_of(piece_on(from))] & to)
return true;
2008-08-31 23:59:13 -06:00
// Is there a discovered check?
if ( (st->blockersForKing[~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);
2008-08-31 23:59:13 -06:00
// 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 ENPASSANT:
{
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));
}
case CASTLING:
{
Square kfrom = from;
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<KING>(~sideToMove))
&& (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
2008-08-31 23:59:13 -06:00
}
default:
assert(false);
return false;
}
2008-08-31 23:59:13 -06:00
}
/// Position::do_move() makes a move, and saves all information necessary
/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal
/// moves should be filtered out before this function is called.
2008-08-31 23:59:13 -06:00
void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
assert(is_ok(m));
assert(&newSt != st);
2008-08-31 23:59:13 -06:00
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
// 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;
2008-08-31 23:59:13 -06:00
// 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;
2008-08-31 23:59:13 -06:00
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) == ENPASSANT ? 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);
st->psq += PSQT::psq[captured][rto] - PSQT::psq[captured][rfrom];
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
captured = NO_PIECE;
}
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 (type_of(m) == ENPASSANT)
{
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));
board[capsq] = NO_PIECE; // Not done by remove_piece()
}
st->pawnKey ^= Zobrist::psq[captured][capsq];
}
else
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
// Update board and piece lists
remove_piece(captured, capsq);
// Update material hash key and prefetch access to materialTable
k ^= Zobrist::psq[captured][capsq];
st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
prefetch(thisThread->materialTable[st->materialKey]);
// Update incremental scores
st->psq -= PSQT::psq[captured][capsq];
// Reset rule 50 counter
st->rule50 = 0;
}
2008-08-31 23:59:13 -06:00
// 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]))
{
int cr = castlingRightsMask[from] | castlingRightsMask[to];
k ^= Zobrist::castling[st->castlingRights & cr];
st->castlingRights &= ~cr;
}
// Move the piece. The tricky Chess960 castling is handled earlier
if (type_of(m) != CASTLING)
move_piece(pc, 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<PAWN>(to - pawn_push(us), 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(pc, to);
put_piece(promotion, to);
// Update hash keys
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]];
// Update incremental score
st->psq += PSQT::psq[promotion][to] - PSQT::psq[pc][to];
// Update material
st->nonPawnMaterial[us] += PieceValue[MG][promotion];
}
// Update pawn hash key and prefetch access to pawnsTable
st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
prefetch2(thisThread->pawnsTable[st->pawnKey]);
// Reset rule 50 draw counter
st->rule50 = 0;
}
// Update incremental scores
st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
// Set capture piece
st->capturedPiece = captured;
// Update the key with the final value
st->key = k;
// Calculate checkers bitboard (if move gives check)
st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
2008-08-31 23:59:13 -06:00
sideToMove = ~sideToMove;
// Update king attacks used for fast check detection
set_check_info(st);
assert(pos_is_ok());
2008-08-31 23:59:13 -06:00
}
/// Position::undo_move() 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(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(pc, 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(pc, to, from); // Put the piece back at the source square
if (st->capturedPiece)
{
Square capsq = to;
if (type_of(m) == ENPASSANT)
{
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());
}
/// Position::do_castling() is a 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) {
2008-08-31 23:59:13 -06:00
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);
2008-08-31 23:59:13 -06:00
// 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
put_piece(make_piece(us, KING), Do ? to : from);
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
2008-08-31 23:59:13 -06:00
}
/// 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) {
assert(!checkers());
assert(&newSt != st);
std::memcpy(&newSt, st, sizeof(StateInfo));
newSt.previous = st;
st = &newSt;
2008-08-31 23:59:13 -06:00
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(st->key));
++st->rule50;
st->pliesFromNull = 0;
sideToMove = ~sideToMove;
set_check_info(st);
assert(pos_is_ok());
2008-08-31 23:59:13 -06:00
}
void Position::undo_null_move() {
assert(!checkers());
st = st->previous;
sideToMove = ~sideToMove;
}
/// Position::key_after() computes the new hash key after the given move. Needed
/// for speculative prefetch. It doesn't recognize special moves like castling,
/// en-passant and promotions.
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];
return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
}
2008-08-31 23:59:13 -06:00
/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the
/// SEE 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, Value threshold) const {
assert(is_ok(m));
// 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 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)
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)
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;
while (true)
{
stmAttackers = attackers & pieces(stm);
2008-08-31 23:59:13 -06:00
// 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)
break;
// Locate and remove the next least valuable attacker, and add to
// the bitboard 'attackers' the possibly X-ray attackers behind it.
nextVictim = min_attacker<PAWN>(byTypeBB, to, stmAttackers, occupied, attackers);
stm = ~stm; // Switch side to move
// Negamax the balance with alpha = balance, beta = balance+1 and
// add nextVictim's value.
//
// (balance, balance+1) -> (-balance-1, -balance)
//
assert(balance < VALUE_ZERO);
2008-08-31 23:59:13 -06:00
balance = -balance - 1 - PieceValue[MG][nextVictim];
2008-08-31 23:59:13 -06:00
// 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
2008-08-31 23:59:13 -06:00
}
/// Position::is_draw() tests whether the position is drawn by 50-move rule
/// or by repetition. It does not detect stalemates.
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
bool Position::is_draw(int ply) const {
if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
return true;
2008-08-31 23:59:13 -06:00
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
int end = std::min(st->rule50, st->pliesFromNull);
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
if (end < 4)
return false;
StateInfo* stp = st->previous->previous;
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
int cnt = 0;
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
for (int i = 4; i <= end; i += 2)
{
stp = stp->previous->previous;
Let ss->ply denote the number of plies from the root to the current node This patch lets ss->ply be equal to 0 at the root of the search. Currently, the root has ss->ply == 1, which is less intuitive: - Setting the rootNode bool has to check (ss-1)->ply == 0. - All mate values are off by one: the code seems to assume that mated-in-0 is -VALUE_MATE, mate-1-in-ply is VALUE_MATE-1, mated-in-2-ply is VALUE_MATE+2, etc. But the mate_in() and mated_in() functions are called with ss->ply, which is 1 in at the root. - The is_draw() function currently needs to explain why it has "ply - 1 > i" instead of simply "ply > i". - The ss->ply >= MAX_PLY tests in search() and qsearch() already assume that ss->ply == 0 at the root. If we start at ss->ply == 1, it would make more sense to go up to and including ss->ply == MAX_PLY, so stop at ss->ply > MAX_PLY. See also the asserts testing for 0 <= ss->ply && ss->ply < MAX_PLY. The reason for ss->ply == 1 at the root is the line "ss->ply = (ss-1)->ply + 1" at the start for search() and qsearch(). By replacing this with "(ss+1)->ply = ss->ply + 1" we keep ss->ply == 0 at the root. Note that search() already clears killers in (ss+2), so there is no danger in accessing ss+1. I have NOT changed pv[MAX_PLY + 1] to pv[MAX_PLY + 2] in search() and qsearch(). It seems to me that MAX_PLY + 1 is exactly right: - MAX_PLY entries for ss->ply running from 0 to MAX_PLY-1, and 1 entry for the final MOVE_NONE. I have verified that mate scores are reported correctly. (They were already reported correctly due to the extra ply being rounded down when converting to moves.) The value of seldepth output to the user should probably not change, so I add 1 to it. (Humans count from 1, computers from 0.) A small optimisation I did not include: instead of setting ss->ply in every invocation of search() and qsearch(), it could be set once for all plies at the start of Thread::search(). This saves a couple of instructions per node. No functional change (unless the search searches a branch MAX_PLY deep), so bench does not change.
2017-09-16 13:49:29 -06:00
// Return a draw score if a position repeats once earlier but strictly
// after the root, or repeats twice before or at the root.
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
if ( stp->key == st->key
Let ss->ply denote the number of plies from the root to the current node This patch lets ss->ply be equal to 0 at the root of the search. Currently, the root has ss->ply == 1, which is less intuitive: - Setting the rootNode bool has to check (ss-1)->ply == 0. - All mate values are off by one: the code seems to assume that mated-in-0 is -VALUE_MATE, mate-1-in-ply is VALUE_MATE-1, mated-in-2-ply is VALUE_MATE+2, etc. But the mate_in() and mated_in() functions are called with ss->ply, which is 1 in at the root. - The is_draw() function currently needs to explain why it has "ply - 1 > i" instead of simply "ply > i". - The ss->ply >= MAX_PLY tests in search() and qsearch() already assume that ss->ply == 0 at the root. If we start at ss->ply == 1, it would make more sense to go up to and including ss->ply == MAX_PLY, so stop at ss->ply > MAX_PLY. See also the asserts testing for 0 <= ss->ply && ss->ply < MAX_PLY. The reason for ss->ply == 1 at the root is the line "ss->ply = (ss-1)->ply + 1" at the start for search() and qsearch(). By replacing this with "(ss+1)->ply = ss->ply + 1" we keep ss->ply == 0 at the root. Note that search() already clears killers in (ss+2), so there is no danger in accessing ss+1. I have NOT changed pv[MAX_PLY + 1] to pv[MAX_PLY + 2] in search() and qsearch(). It seems to me that MAX_PLY + 1 is exactly right: - MAX_PLY entries for ss->ply running from 0 to MAX_PLY-1, and 1 entry for the final MOVE_NONE. I have verified that mate scores are reported correctly. (They were already reported correctly due to the extra ply being rounded down when converting to moves.) The value of seldepth output to the user should probably not change, so I add 1 to it. (Humans count from 1, computers from 0.) A small optimisation I did not include: instead of setting ss->ply in every invocation of search() and qsearch(), it could be set once for all plies at the start of Thread::search(). This saves a couple of instructions per node. No functional change (unless the search searches a branch MAX_PLY deep), so bench does not change.
2017-09-16 13:49:29 -06:00
&& ++cnt + (ply > i) == 2)
Threefold repetition detection Implement a threefold repetition detection. Below are the examples of problems fixed by this change. Loosing move in a drawn position. position fen 8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1 moves a1a2 a7a8 a2a1 The old code suggested a loosing move "bestmove a8a7", the new code suggests "bestmove a8b7" leading to a draw. Incorrect evaluation (happened in a real game in TCEC Season 9). position fen 4rbkr/1q3pp1/b3pn2/7p/1pN5/1P1BBP1P/P1R2QP1/3R2K1 w - - 5 31 moves e3d4 h8h6 d4e3 The old code evaluated it as "cp 0", the new code evaluation is around "cp -50" which is adequate. Brings 0.5-1 ELO gain. Passes [-3.00,1.00]. STC: http://tests.stockfishchess.org/tests/view/584ece040ebc5903140c5aea LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 47744 W: 8537 L: 8461 D: 30746 LTC: http://tests.stockfishchess.org/tests/view/584f134d0ebc5903140c5b37 LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 36775 W: 4739 L: 4639 D: 27397 Patch has been rewritten into current form for simplification and logic slightly changed so that return a draw score if the position repeats once earlier but after or at the root, or repeats twice strictly before the root. In its original form, repetition at root was not returned as an immediate draw. After retestimng testing both version with SPRT[-3, 1], both passed succesfully, but this version was chosen becuase more natural. There is an argument about MultiPV in which an extended draw at root may be sensible. See discussion here: https://github.com/official-stockfish/Stockfish/pull/925 For documentation, current version passed both at STC and LTC: STC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 51562 W: 9314 L: 9245 D: 33003 LTC LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 115663 W: 14904 L: 14906 D: 85853 bench: 5468995
2016-12-12 08:04:16 -07:00
return true;
}
2008-08-31 23:59:13 -06:00
return false;
}
/// Position::flip() 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() {
2008-08-31 23:59:13 -06:00
string f, token;
std::stringstream ss(fen());
2008-08-31 23:59:13 -06:00
for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
{
std::getline(ss, token, r > RANK_1 ? '/' : ' ');
f.insert(0, token + (f.empty() ? " " : "/"));
}
2008-08-31 23:59:13 -06:00
ss >> token; // Active color
f += (token == "w" ? "B " : "W "); // Will be lowercased later
2008-08-31 23:59:13 -06:00
ss >> token; // Castling availability
f += token + " ";
2008-08-31 23:59:13 -06:00
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"));
2008-08-31 23:59:13 -06:00
std::getline(ss, token); // Half and full moves
f += token;
set(f, is_chess960(), st, this_thread());
2008-08-31 23:59:13 -06:00
assert(pos_is_ok());
2008-08-31 23:59:13 -06:00
}
2008-08-31 23:59:13 -06:00
/// Position::pos_is_ok() performs some consistency checks for the
/// position object and raises an asserts if something wrong is detected.
2008-08-31 23:59:13 -06:00
/// This is meant to be helpful when debugging.
bool Position::pos_is_ok() const {
const bool Fast = true; // Quick (default) or full check?
2008-08-31 23:59:13 -06:00
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");
2008-08-31 23:59:13 -06:00
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");
2008-08-31 23:59:13 -06:00
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;
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 (int i = 0; i < pieceCount[pc]; ++i)
if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
assert(0 && "pos_is_ok: Index");
}
for (Color c = WHITE; c <= BLACK; ++c)
for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1))
{
if (!can_castle(c | s))
continue;
if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK)
|| castlingRightsMask[castlingRookSquare[c | s]] != (c | s)
|| (castlingRightsMask[square<KING>(c)] & (c | s)) != (c | s))
assert(0 && "pos_is_ok: Castling");
}
2008-08-31 23:59:13 -06:00
return true;
}