droidfish/DroidFish/jni/rtb/rtb-probe.cpp
Peter Osterlund ac72cf115e DroidFish: Support 7-men syzygy tablebases.
Update syzygy probing code from texel to support probing of 7-men
tablebases.
2018-10-07 00:25:16 +02:00

609 lines
19 KiB
C++

/*
Copyright (c) 2013 Ronald de Man
This file may be redistributed and/or modified without restrictions.
tbprobe.cpp contains the Stockfish-specific routines of the
tablebase probing code. It should be relatively easy to adapt
this code to other chess engines.
*/
#include "piece.hpp"
#include "position.hpp"
#include "moveGen.hpp"
#include <type_traits>
#include "rtb-probe.hpp"
#include "rtb-core.hpp"
#include "rtb-core-impl.hpp"
int Syzygy::TBLargest = 0;
// Given a position with 6 or fewer pieces, produce a text string
// of the form KQPvKRP, where "KQP" represents the white pieces if
// mirror == false and the black pieces if mirror == true.
static void prt_str(Position& pos, char *str, bool mirror)
{
static_assert(Piece::WQUEEN == Piece::WKING + 1, "");
static_assert(Piece::WROOK == Piece::WQUEEN + 1, "");
static_assert(Piece::WBISHOP == Piece::WROOK + 1, "");
static_assert(Piece::WKNIGHT == Piece::WBISHOP + 1, "");
static_assert(Piece::WPAWN == Piece::WKNIGHT + 1, "");
static_assert(Piece::BQUEEN == Piece::BKING + 1, "");
static_assert(Piece::BROOK == Piece::BQUEEN + 1, "");
static_assert(Piece::BBISHOP == Piece::BROOK + 1, "");
static_assert(Piece::BKNIGHT == Piece::BBISHOP + 1, "");
static_assert(Piece::BPAWN == Piece::BKNIGHT + 1, "");
static char pchr[Piece::nPieceTypes+1] = " KQRBNPKQRBNP";
int p1Beg = mirror ? Piece::BKING : Piece::WKING;
int p1End = mirror ? Piece::BPAWN : Piece::WPAWN;
int p2Beg = mirror ? Piece::WKING : Piece::BKING;
int p2End = mirror ? Piece::WPAWN : Piece::BPAWN;
for (int p = p1Beg; p <= p1End; p++) {
int cnt = BitBoard::bitCount(pos.pieceTypeBB((Piece::Type)p));
for (int i = 0; i < cnt; i++)
*str++ = pchr[p];
}
*str++ = 'v';
for (int p = p2Beg; p <= p2End; p++) {
int cnt = BitBoard::bitCount(pos.pieceTypeBB((Piece::Type)p));
for (int i = 0; i < cnt; i++)
*str++ = pchr[p];
}
*str++ = 0;
}
// Given a position, produce a 64-bit material signature key.
// If the engine supports such a key, it should equal the engine's key.
static uint64_t calc_key(const Position& pos, bool mirror)
{
uint64_t h = mirror ? MatId::mirror(pos.materialId()) : pos.materialId();
h *= 0x842c2f50a7ac0ae1ULL;
h = ((h >> 32) ^ h) * 0xace7b66dbad28265ULL;
return h;
}
// Produce a 64-bit material key corresponding to the material combination
// defined by pcs[16], where pcs[1], ..., pcs[6] is the number of white
// pawns, ..., kings and pcs[9], ..., pcs[14] is the number of black
// pawns, ..., kings.
static uint64_t calc_key_from_pcs(const int *pcs, bool mirror)
{
MatId key;
key.addPieceCnt(Piece::WPAWN, pcs[1]);
key.addPieceCnt(Piece::WKNIGHT, pcs[2]);
key.addPieceCnt(Piece::WBISHOP, pcs[3]);
key.addPieceCnt(Piece::WROOK, pcs[4]);
key.addPieceCnt(Piece::WQUEEN, pcs[5]);
key.addPieceCnt(Piece::BPAWN, pcs[8+1]);
key.addPieceCnt(Piece::BKNIGHT, pcs[8+2]);
key.addPieceCnt(Piece::BBISHOP, pcs[8+3]);
key.addPieceCnt(Piece::BROOK, pcs[8+4]);
key.addPieceCnt(Piece::BQUEEN, pcs[8+5]);
uint64_t h = mirror ? MatId::mirror(key()) : key();
h *= 0x842c2f50a7ac0ae1ULL;
h = ((h >> 32) ^ h) * 0xace7b66dbad28265ULL;
return h;
}
static uint64_t get_pieces(const Position& pos, int color, int piece) {
int p = 7 - piece;
if (color)
p += Piece::BKING - Piece::WKING;
return pos.pieceTypeBB((Piece::Type)p);
}
static inline int pop_lsb(uint64_t& bb) {
int ret = BitBoard::numberOfTrailingZeros(bb);
bb &= bb - 1;
return ret;
}
// probe_wdl_table and probe_dtz_table require similar adaptations.
static int probe_wdl_table(Position& pos, int *success)
{
struct TBEntry *ptr;
struct TBHashEntry *ptr2;
uint64_t idx;
uint64_t key;
int i;
uint8_t res;
int p[TBPIECES];
// Obtain the position's material signature key.
key = calc_key(pos, false);
// Test for KvK.
if (!key) return 0;
ptr2 = WDL_hash[key >> (64 - TBHASHBITS)];
for (i = 0; i < HSHMAX; i++)
if (ptr2[i].key == key) break;
if (i == HSHMAX) {
*success = 0;
return 0;
}
ptr = ptr2[i].ptr;
uint8_t ready = ptr->ready.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (!ready) {
std::lock_guard<std::mutex> L(TB_mutex);
ready = ptr->ready.load(std::memory_order_relaxed);
if (!ready) {
char str[16];
prt_str(pos, str, ptr->key != key);
if (!init_table_wdl(ptr, str)) {
ptr2[i].key = 0ULL;
*success = 0;
return 0;
}
std::atomic_thread_fence(std::memory_order_release);
ptr->ready.store(1, std::memory_order_relaxed);
}
}
int bside, mirror, cmirror;
if (!ptr->symmetric) {
if (key != ptr->key) {
cmirror = 8;
mirror = 0x38;
bside = pos.isWhiteMove();
} else {
cmirror = mirror = 0;
bside = !pos.isWhiteMove();
}
} else {
cmirror = pos.isWhiteMove() ? 0 : 8;
mirror = pos.isWhiteMove() ? 0 : 0x38;
bside = 0;
}
// p[i] is to contain the square 0-63 (A1-H8) for a piece of type
// pc[i] ^ cmirror, where 1 = white pawn, ..., 14 = black king.
// Pieces of the same type are guaranteed to be consecutive.
if (!ptr->has_pawns) {
struct TBEntry_piece *entry = (struct TBEntry_piece *)ptr;
uint8_t *pc = entry->pieces[bside];
for (i = 0; i < entry->num;) {
uint64_t bb = get_pieces(pos, (pc[i] ^ cmirror) >> 3, pc[i] & 0x07);
do {
p[i++] = pop_lsb(bb);
} while (bb);
}
idx = encode_piece(entry, entry->norm[bside], p, entry->factor[bside]);
res = decompress_pairs(entry->precomp[bside], idx);
} else {
struct TBEntry_pawn *entry = (struct TBEntry_pawn *)ptr;
int k = entry->file[0].pieces[0][0] ^ cmirror;
uint64_t bb = get_pieces(pos, k >> 3, k & 0x07);
i = 0;
do {
p[i++] = pop_lsb(bb) ^ mirror;
} while (bb);
int f = pawn_file(entry, p);
uint8_t *pc = entry->file[f].pieces[bside];
for (; i < entry->num;) {
bb = get_pieces(pos, (pc[i] ^ cmirror) >> 3, pc[i] & 0x07);
do {
p[i++] = pop_lsb(bb) ^ mirror;
} while (bb);
}
idx = encode_pawn(entry, entry->file[f].norm[bside], p, entry->file[f].factor[bside]);
res = decompress_pairs(entry->file[f].precomp[bside], idx);
}
return ((int)res) - 2;
}
static int probe_dtz_table(Position& pos, int wdl, int *success)
{
uint64_t idx;
int i, res;
int p[TBPIECES];
// Obtain the position's material signature key.
uint64_t key = calc_key(pos, false);
DTZTableEntry* dtzTabEnt;
{
dtzTabEnt = DTZ_hash[key >> (64 - TBHASHBITS)];
for (i = 0; i < HSHMAX; i++)
if (dtzTabEnt[i].key1 == key) break;
if (i == HSHMAX) {
uint64_t key2 = calc_key(pos, true);
dtzTabEnt = DTZ_hash[key2 >> (64 - TBHASHBITS)];
for (i = 0; i < HSHMAX; i++)
if (dtzTabEnt[i].key2 == key) break;
}
if (i == HSHMAX) {
*success = 0;
return 0;
}
dtzTabEnt += i;
}
TBEntry* ptr = dtzTabEnt->entry.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
if (!ptr) {
std::lock_guard<std::mutex> L(TB_mutex);
ptr = dtzTabEnt->entry.load(std::memory_order_relaxed);
if (!ptr) {
struct TBHashEntry *ptr2 = WDL_hash[key >> (64 - TBHASHBITS)];
for (i = 0; i < HSHMAX; i++)
if (ptr2[i].key == key) break;
if (i == HSHMAX) {
*success = 0;
return 0;
}
char str[16];
bool mirror = (ptr2[i].ptr->key != key);
prt_str(pos, str, mirror);
ptr = load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror));
std::atomic_thread_fence(std::memory_order_release);
dtzTabEnt->entry.store(ptr, std::memory_order_relaxed);
}
}
if (!ptr) {
*success = 0;
return 0;
}
int bside, mirror, cmirror;
if (!ptr->symmetric) {
if (key != ptr->key) {
cmirror = 8;
mirror = 0x38;
bside = pos.isWhiteMove();
} else {
cmirror = mirror = 0;
bside = !pos.isWhiteMove();
}
} else {
cmirror = pos.isWhiteMove() ? 0 : 8;
mirror = pos.isWhiteMove() ? 0 : 0x38;
bside = 0;
}
if (!ptr->has_pawns) {
struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr;
if ((entry->flags & 1) != bside && !entry->symmetric) {
*success = -1;
return 0;
}
uint8_t *pc = entry->pieces;
for (i = 0; i < entry->num;) {
uint64_t bb = get_pieces(pos, (pc[i] ^ cmirror) >> 3, pc[i] & 0x07);
do {
p[i++] = pop_lsb(bb);
} while (bb);
}
idx = encode_piece((struct TBEntry_piece *)entry, entry->norm, p, entry->factor);
res = decompress_pairs(entry->precomp, idx);
if (entry->flags & 2) {
if (entry->flags & 16)
res = ((uint16_t*)entry->map)[entry->map_idx[wdl_to_map[wdl + 2]] + res];
else
res = entry->map[entry->map_idx[wdl_to_map[wdl + 2]] + res];
}
if (!(entry->flags & pa_flags[wdl + 2]) || (wdl & 1))
res *= 2;
} else {
struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr;
int k = entry->file[0].pieces[0] ^ cmirror;
uint64_t bb = get_pieces(pos, k >> 3, k & 0x07);
i = 0;
do {
p[i++] = pop_lsb(bb) ^ mirror;
} while (bb);
int f = pawn_file((struct TBEntry_pawn *)entry, p);
if ((entry->flags[f] & 1) != bside) {
*success = -1;
return 0;
}
uint8_t *pc = entry->file[f].pieces;
for (; i < entry->num;) {
bb = get_pieces(pos, (pc[i] ^ cmirror) >> 3, pc[i] & 0x07);
do {
p[i++] = pop_lsb(bb) ^ mirror;
} while (bb);
}
idx = encode_pawn((struct TBEntry_pawn *)entry, entry->file[f].norm, p, entry->file[f].factor);
res = decompress_pairs(entry->file[f].precomp, idx);
if (entry->flags[f] & 2)
res = entry->map[entry->map_idx[f][wdl_to_map[wdl + 2]] + res];
if (!(entry->flags[f] & pa_flags[wdl + 2]) || (wdl & 1))
res *= 2;
}
return res;
}
// Add bishop and rook underpromotion captures to move list.
static void add_underprom_caps(Position& pos, MoveGen::MoveList& moveList)
{
const int nMoves = moveList.size;
const bool wtm = pos.isWhiteMove();
const int queen = wtm ? Piece::WQUEEN : Piece::BQUEEN;
for (int i = 0; i < nMoves; i++) {
const Move& m = moveList[i];
if ((m.promoteTo() == queen) && (pos.getPiece(m.to()) != Piece::EMPTY)) {
moveList.addMove(m.from(), m.to(), wtm ? Piece::WROOK : Piece::BROOK);
moveList.addMove(m.from(), m.to(), wtm ? Piece::WBISHOP : Piece::BBISHOP);
}
}
}
static int probe_ab(Position& pos, int alpha, int beta, int *success)
{
// Generate (at least) all legal non-ep captures including (under)promotions.
// It is OK to generate more, as long as they are filtered out below.
MoveGen::MoveList moveList;
const bool inCheck = MoveGen::inCheck(pos);
if (inCheck) {
MoveGen::checkEvasions(pos, moveList);
} else {
MoveGen::pseudoLegalCaptures(pos, moveList);
// Since bishop and rook promotions are not included, we need to add them.
add_underprom_caps(pos, moveList);
}
UndoInfo ui;
for (int m = 0; m < moveList.size; m++) {
const Move& capture = moveList[m];
if ((pos.getPiece(capture.to()) == Piece::EMPTY) ||
!MoveGen::isLegal(pos, capture, inCheck))
continue;
pos.makeMove(capture, ui);
int v = -probe_ab(pos, -beta, -alpha, success);
pos.unMakeMove(capture, ui);
if (*success == 0) return 0;
if (v > alpha) {
if (v >= beta) {
*success = 2;
return v;
}
alpha = v;
}
}
int v = probe_wdl_table(pos, success);
if (*success == 0) return 0;
if (alpha >= v) {
*success = 1 + (alpha > 0);
return alpha;
} else {
*success = 1;
return v;
}
}
int Syzygy::probe_wdl(Position& pos, int *success)
{
*success = 1;
int v = probe_ab(pos, -2, 2, success);
// If en passant is not possible, we are done.
if (pos.getEpSquare() == -1)
return v;
if (!(*success)) return 0;
// Now handle en passant.
int v1 = -3;
// Generate (at least) all legal en passant captures.
MoveGen::MoveList moveList;
const bool inCheck = MoveGen::inCheck(pos);
if (inCheck) {
MoveGen::checkEvasions(pos, moveList);
} else {
MoveGen::pseudoLegalMoves(pos, moveList);
}
const int pawn = pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN;
UndoInfo ui;
for (int m = 0; m < moveList.size; m++) {
const Move& capture = moveList[m];
if ((capture.to() != pos.getEpSquare()) || (pos.getPiece(capture.from()) != pawn) ||
!MoveGen::isLegal(pos, capture, inCheck))
continue;
pos.makeMove(capture, ui);
int v0 = -probe_ab(pos, -2, 2, success);
pos.unMakeMove(capture, ui);
if (*success == 0) return 0;
if (v0 > v1) v1 = v0;
}
if (v1 > -3) {
if (v1 >= v) v = v1;
else if (v == 0) {
// Check whether there is at least one legal non-ep move.
for (int m = 0; m < moveList.size; m++) {
const Move& capture = moveList[m];
if ((capture.to() == pos.getEpSquare()) &&
(pos.getPiece(capture.from()) == pawn))
continue;
if (MoveGen::isLegal(pos, capture, inCheck))
return v;
}
// If not, then we are forced to play the losing ep capture.
v = v1;
}
}
return v;
}
// This routine treats a position with en passant captures as one without.
static int probe_dtz_no_ep(Position& pos, int *success)
{
const int wdl = probe_ab(pos, -2, 2, success);
if (*success == 0) return 0;
if (wdl == 0) return 0;
if (*success == 2)
return wdl == 2 ? 1 : 101;
MoveGen::MoveList moveList;
const bool inCheck = MoveGen::inCheck(pos);
const int pawn = pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN;
UndoInfo ui;
if (wdl > 0) {
// Generate at least all legal non-capturing pawn moves
// including non-capturing promotions.
if (inCheck) {
MoveGen::checkEvasions(pos, moveList);
} else {
MoveGen::pseudoLegalMoves(pos, moveList);
}
for (int m = 0; m < moveList.size; m++) {
const Move& move = moveList[m];
if ((pos.getPiece(move.from()) != pawn) ||
(Position::getX(move.from()) != Position::getX(move.to())) ||
!MoveGen::isLegal(pos, move, inCheck))
continue;
pos.makeMove(move, ui);
int v = -Syzygy::probe_wdl(pos, success);
pos.unMakeMove(move, ui);
if (*success == 0) return 0;
if (v == wdl)
return v == 2 ? 1 : 101;
}
}
int dtz = 1 + probe_dtz_table(pos, wdl, success);
if (*success >= 0) {
if (wdl & 1) dtz += 100;
return wdl >= 0 ? dtz : -dtz;
}
if (wdl > 0) {
int best = 0xffff;
for (int m = 0; m < moveList.size; m++) {
const Move& move = moveList[m];
if ((pos.getPiece(move.to()) != Piece::EMPTY) ||
(pos.getPiece(move.from()) == pawn) ||
!MoveGen::isLegal(pos, move, inCheck))
continue;
pos.makeMove(move, ui);
int v = -Syzygy::probe_dtz(pos, success);
pos.unMakeMove(move, ui);
if (*success == 0) return 0;
if (v > 0 && v + 1 < best)
best = v + 1;
}
return best;
} else {
int best = -1;
if (inCheck) {
MoveGen::checkEvasions(pos, moveList);
} else {
MoveGen::pseudoLegalMoves(pos, moveList);
}
for (int m = 0; m < moveList.size; m++) {
const Move& move = moveList[m];
if (!MoveGen::isLegal(pos, move, inCheck))
continue;
pos.makeMove(move, ui);
int v;
if (pos.getHalfMoveClock() == 0) {
if (wdl == -2) v = -1;
else {
v = probe_ab(pos, 1, 2, success);
v = (v == 2) ? 0 : -101;
}
} else {
v = -Syzygy::probe_dtz(pos, success) - 1;
}
pos.unMakeMove(move, ui);
if (*success == 0) return 0;
if (v < best)
best = v;
}
return best;
}
}
static int wdl_to_dtz[] = {
-1, -101, 0, 101, 1
};
int Syzygy::probe_dtz(Position& pos, int *success)
{
*success = 1;
int v = probe_dtz_no_ep(pos, success);
if (pos.getEpSquare() == -1)
return v;
if (*success == 0) return 0;
// Now handle en passant.
int v1 = -3;
MoveGen::MoveList moveList;
const bool inCheck = MoveGen::inCheck(pos);
const int pawn = pos.isWhiteMove() ? Piece::WPAWN : Piece::BPAWN;
UndoInfo ui;
if (!inCheck) {
MoveGen::pseudoLegalMoves(pos, moveList);
} else {
MoveGen::checkEvasions(pos, moveList);
}
for (int m = 0; m < moveList.size; m++) {
const Move& capture = moveList[m];
if ((capture.to() != pos.getEpSquare()) ||
(pos.getPiece(capture.from()) != pawn) ||
!MoveGen::isLegal(pos, capture, inCheck))
continue;
pos.makeMove(capture, ui);
int v0 = -probe_ab(pos, -2, 2, success);
pos.unMakeMove(capture, ui);
if (*success == 0) return 0;
if (v0 > v1) v1 = v0;
}
if (v1 > -3) {
v1 = wdl_to_dtz[v1 + 2];
if (v < -100) {
if (v1 >= 0)
v = v1;
} else if (v < 0) {
if (v1 >= 0 || v1 < -100)
v = v1;
} else if (v > 100) {
if (v1 > 0)
v = v1;
} else if (v > 0) {
if (v1 == 1)
v = v1;
} else if (v1 >= 0) {
v = v1;
} else {
for (int m = 0; m < moveList.size; m++) {
const Move& move = moveList[m];
if ((move.to() == pos.getEpSquare()) && (pos.getPiece(move.from()) == pawn))
continue;
if (MoveGen::isLegal(pos, move, inCheck))
return v;
}
v = v1;
}
}
return v;
}