/* 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 #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 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 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; }