mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-25 14:17:13 +00:00
As Stockfish developers, we aim to make our code as legible and as close to simple English as possible. However, one of the more notable exceptions to this rule concerns operations between Squares and Bitboards. Prior to this pull request, AND, OR, and XOR were only defined when the Bitboard was the first operand, and the Square the second. For example, for a Bitboard b and Square s, "b & s" would be valid but "s & b" would not. This conflicts with natural reasoning about logical operators, both mathematically and intuitively, which says that logical operators should commute. More dangerously, however, both Square and Bitboard are defined as integers "under the hood." As a result, code like "s & b" would still compile and give reasonable bench values. This trap occasionally ensnares even experienced Stockfish developers, but it is especially dangerous for new developers not aware of this peculiarity. Because there is no compilation or runtime error, and a reasonable bench, only a close review by approvers can spot this error when a test has been submitted--and many times, these bugs have slipped past review. This is by far the most common logical error on Fishtest, and has wasted uncountable STC games over the years. However, it can be fixed by adding three non-functional lines of code. In this patch, we define the operators when the operands are provided in the opposite order, i.e., we make AND, OR, and XOR commutative for Bitboards and Squares. Because these are inline methods and implemented identically, the executable does not change at all. This patch has the small side-effect of requiring Squares to be explicitly cast to integers before AND, OR, or XOR with integers. This is only performed twice in Stockfish's source code, and again does not change the executable at all (since Square is an enum defined as an integer anyway). For demonstration purposes, this pull request also inverts the order of one AND and one OR, to show that neither the bench nor the executable change. (This change can be removed before merging, if preferred.) I hope that this pull request significantly lowers the barrier-of-entry for new developer to join the Stockfish project. I also hope that this change will improve our efficiency in using our generous CPU donors' machines, since it will remove one of the most common causes of buggy tests. Following helpful review and comments by Michael Stembera (@mstembera), we add a further clean-up by implementing OR for two Squares, to anticipate additional traps developers may encounter and handle them cleanly. Closes https://github.com/official-stockfish/Stockfish/pull/2387 No functional change.
181 lines
6.2 KiB
C++
181 lines
6.2 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
|
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <cassert>
|
|
#include <numeric>
|
|
#include <vector>
|
|
|
|
#include "bitboard.h"
|
|
#include "types.h"
|
|
|
|
namespace {
|
|
|
|
// There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
|
|
// Positions with the pawn on files E to H will be mirrored before probing.
|
|
constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
|
|
|
|
// Each uint32_t stores results of 32 positions, one per bit
|
|
uint32_t KPKBitbase[MAX_INDEX / 32];
|
|
|
|
// A KPK bitbase index is an integer in [0, IndexMax] range
|
|
//
|
|
// Information is mapped in a way that minimizes the number of iterations:
|
|
//
|
|
// bit 0- 5: white king square (from SQ_A1 to SQ_H8)
|
|
// bit 6-11: black king square (from SQ_A1 to SQ_H8)
|
|
// bit 12: side to move (WHITE or BLACK)
|
|
// bit 13-14: white pawn file (from FILE_A to FILE_D)
|
|
// bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
|
|
unsigned index(Color us, Square bksq, Square wksq, Square psq) {
|
|
return int(wksq) | (bksq << 6) | (us << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
|
|
}
|
|
|
|
enum Result {
|
|
INVALID = 0,
|
|
UNKNOWN = 1,
|
|
DRAW = 2,
|
|
WIN = 4
|
|
};
|
|
|
|
Result& operator|=(Result& r, Result v) { return r = Result(r | v); }
|
|
|
|
struct KPKPosition {
|
|
KPKPosition() = default;
|
|
explicit KPKPosition(unsigned idx);
|
|
operator Result() const { return result; }
|
|
Result classify(const std::vector<KPKPosition>& db)
|
|
{ return us == WHITE ? classify<WHITE>(db) : classify<BLACK>(db); }
|
|
|
|
template<Color Us> Result classify(const std::vector<KPKPosition>& db);
|
|
|
|
Color us;
|
|
Square ksq[COLOR_NB], psq;
|
|
Result result;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) {
|
|
|
|
assert(file_of(wpsq) <= FILE_D);
|
|
|
|
unsigned idx = index(us, bksq, wksq, wpsq);
|
|
return KPKBitbase[idx / 32] & (1 << (idx & 0x1F));
|
|
}
|
|
|
|
|
|
void Bitbases::init() {
|
|
|
|
std::vector<KPKPosition> db(MAX_INDEX);
|
|
unsigned idx, repeat = 1;
|
|
|
|
// Initialize db with known win / draw positions
|
|
for (idx = 0; idx < MAX_INDEX; ++idx)
|
|
db[idx] = KPKPosition(idx);
|
|
|
|
// Iterate through the positions until none of the unknown positions can be
|
|
// changed to either wins or draws (15 cycles needed).
|
|
while (repeat)
|
|
for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
|
|
repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
|
|
|
|
// Map 32 results into one KPKBitbase[] entry
|
|
for (idx = 0; idx < MAX_INDEX; ++idx)
|
|
if (db[idx] == WIN)
|
|
KPKBitbase[idx / 32] |= 1 << (idx & 0x1F);
|
|
}
|
|
|
|
|
|
namespace {
|
|
|
|
KPKPosition::KPKPosition(unsigned idx) {
|
|
|
|
ksq[WHITE] = Square((idx >> 0) & 0x3F);
|
|
ksq[BLACK] = Square((idx >> 6) & 0x3F);
|
|
us = Color ((idx >> 12) & 0x01);
|
|
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
|
|
|
|
// Check if two pieces are on the same square or if a king can be captured
|
|
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|
|
|| ksq[WHITE] == psq
|
|
|| ksq[BLACK] == psq
|
|
|| (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK])))
|
|
result = INVALID;
|
|
|
|
// Immediate win if a pawn can be promoted without getting captured
|
|
else if ( us == WHITE
|
|
&& rank_of(psq) == RANK_7
|
|
&& ksq[us] != psq + NORTH
|
|
&& ( distance(ksq[~us], psq + NORTH) > 1
|
|
|| (PseudoAttacks[KING][ksq[us]] & (psq + NORTH))))
|
|
result = WIN;
|
|
|
|
// Immediate draw if it is a stalemate or a king captures undefended pawn
|
|
else if ( us == BLACK
|
|
&& ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq]))
|
|
|| (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]])))
|
|
result = DRAW;
|
|
|
|
// Position will be classified later
|
|
else
|
|
result = UNKNOWN;
|
|
}
|
|
|
|
template<Color Us>
|
|
Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
|
|
|
|
// White to move: If one move leads to a position classified as WIN, the result
|
|
// of the current position is WIN. If all moves lead to positions classified
|
|
// as DRAW, the current position is classified as DRAW, otherwise the current
|
|
// position is classified as UNKNOWN.
|
|
//
|
|
// Black to move: If one move leads to a position classified as DRAW, the result
|
|
// of the current position is DRAW. If all moves lead to positions classified
|
|
// as WIN, the position is classified as WIN, otherwise the current position is
|
|
// classified as UNKNOWN.
|
|
|
|
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
|
constexpr Result Good = (Us == WHITE ? WIN : DRAW);
|
|
constexpr Result Bad = (Us == WHITE ? DRAW : WIN);
|
|
|
|
Result r = INVALID;
|
|
Bitboard b = PseudoAttacks[KING][ksq[Us]];
|
|
|
|
while (b)
|
|
r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)]
|
|
: db[index(Them, pop_lsb(&b), ksq[Them] , psq)];
|
|
|
|
if (Us == WHITE)
|
|
{
|
|
if (rank_of(psq) < RANK_7) // Single push
|
|
r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH)];
|
|
|
|
if ( rank_of(psq) == RANK_2 // Double push
|
|
&& psq + NORTH != ksq[Us]
|
|
&& psq + NORTH != ksq[Them])
|
|
r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH + NORTH)];
|
|
}
|
|
|
|
return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
|
|
}
|
|
|
|
} // namespace
|