From 6089a7ba3364ec56f1cf3edbc5cd59f3a0ce580c Mon Sep 17 00:00:00 2001 From: connormcmonigle Date: Fri, 8 Oct 2021 12:49:22 -0700 Subject: [PATCH] syzygy (#76) bench: 1944878 ELO | 1.87 +- 3.83 (95%) SPRT | 8.0+0.08s Threads=1 Hash=32MB LLR | 2.96 (-2.94, 2.94) [-5.00, 0.00] GAMES | N: 12848 W: 2656 L: 2587 D: 7605 ELO | 9.49 +- 5.61 (95%) SPRT | 8.0+0.08s Threads=1 Hash=32MB LLR | 2.96 (-2.94, 2.94) [0.00, 5.00] GAMES | N: 5528 W: 1114 L: 963 D: 3451 --- build/makefile | 9 +- {include/incbin => incbin}/incbin.h | 0 include/embedded_weights.h | 2 +- include/search_constants.h | 8 +- include/search_stack.h | 4 +- include/syzygy.h | 104 ++ include/thread_worker.h | 30 +- include/uci.h | 24 +- syzygy/LICENSE | 23 + syzygy/README.md | 104 ++ syzygy/stdendian.h | 285 +++ syzygy/tbchess.c | 1054 +++++++++++ syzygy/tbconfig.h | 150 ++ syzygy/tbprobe.c | 2633 +++++++++++++++++++++++++++ syzygy/tbprobe.h | 401 ++++ 15 files changed, 4817 insertions(+), 14 deletions(-) rename {include/incbin => incbin}/incbin.h (100%) create mode 100644 include/syzygy.h create mode 100644 syzygy/LICENSE create mode 100644 syzygy/README.md create mode 100644 syzygy/stdendian.h create mode 100644 syzygy/tbchess.c create mode 100644 syzygy/tbconfig.h create mode 100644 syzygy/tbprobe.c create mode 100644 syzygy/tbprobe.h diff --git a/build/makefile b/build/makefile index b2e85eb..e28a096 100644 --- a/build/makefile +++ b/build/makefile @@ -1,18 +1,21 @@ EXE = seer -SRC = ../src/seer.cc +SRC = ../src/seer.cc ../syzygy/*.c CC = g++ EVALFILE = weights/0xf8175070.bin OPSLIMIT = 1000000000 CXXSTANDARD = 17 + INCLUDE = ../include +INCBIN = ../incbin +SYZYGY = ../syzygy CXXFLAGS += -std=c++$(CXXSTANDARD) -CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp +CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp -flto CXXFLAGS += -Wall -Wextra CXXFLAGS += -fconstexpr-ops-limit=$(OPSLIMIT) -CXXFLAGS += -I$(INCLUDE) +CXXFLAGS += -I$(INCLUDE) -I$(INCBIN) -I$(SYZYGY) CXXFLAGS += -DEVALFILE=\"$(EVALFILE)\" THREADSANFLAGS += $(CXXFLAGS) -fsanitize=thread diff --git a/include/incbin/incbin.h b/incbin/incbin.h similarity index 100% rename from include/incbin/incbin.h rename to incbin/incbin.h diff --git a/include/embedded_weights.h b/include/embedded_weights.h index 0c9d071..9b15153 100644 --- a/include/embedded_weights.h +++ b/include/embedded_weights.h @@ -19,7 +19,7 @@ #define INCBIN_PREFIX #define INCBIN_STYLE INCBIN_STYLE_SNAKE -#include +#include namespace embed { diff --git a/include/search_constants.h b/include/search_constants.h index 96c115a..0e777ef 100644 --- a/include/search_constants.h +++ b/include/search_constants.h @@ -59,6 +59,10 @@ inline constexpr score_type max_mate_score = -2 * big_number; inline constexpr score_type mate_score = max_mate_score - (max_depth + max_depth_margin); +inline constexpr score_type tb_win_score = big_number + 1; + +inline constexpr score_type tb_loss_score = -tb_win_score; + inline constexpr score_type draw_score = 0; inline constexpr score_type aspiration_delta = 20; @@ -97,7 +101,9 @@ struct fixed_constants { return lmr_tbl[std::min(last_idx, depth) * lmr_tbl_dim + std::min(last_idx, move_idx)]; } - constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const { return 4 + depth / 6 + std::min(3, (value - beta) / 256); } + constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const { + return 4 + depth / 6 + std::min(3, (value - beta) / 256); + } constexpr see_type nmp_see_threshold() const { return 200; } diff --git a/include/search_stack.h b/include/search_stack.h index 537935f..ba6b45d 100644 --- a/include/search_stack.h +++ b/include/search_stack.h @@ -90,7 +90,9 @@ struct stack_view { stack* view_; depth_type height_{}; - constexpr score_type effective_mate_score() const { return mate_score + height_; } + constexpr score_type loss_score() const { return mate_score + height_; } + + constexpr score_type win_score() const { return -mate_score - height_; } bool reached_max_height() const { return height_ >= (safe_depth_ - 1); } diff --git a/include/syzygy.h b/include/syzygy.h new file mode 100644 index 0000000..a7c59d1 --- /dev/null +++ b/include/syzygy.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace syzygy { + +enum class wdl_type { + loss, + draw, + win +}; + +struct tb_wdl_result { + bool success; + wdl_type wdl{wdl_type::draw}; + + static constexpr tb_wdl_result failure() { return tb_wdl_result{false}; } + static constexpr tb_wdl_result from_value(const unsigned int& value) { + if (value == TB_WIN) { return tb_wdl_result{true, wdl_type::win}; } + if (value == TB_DRAW) { return tb_wdl_result{true, wdl_type::draw}; } + if (value == TB_LOSS) { return tb_wdl_result{true, wdl_type::loss}; } + return failure(); + } +}; + +struct tb_dtz_result { + bool success; + search::score_type score{search::draw_score}; + chess::move move{chess::move::null()}; + + static constexpr tb_dtz_result failure() { return tb_dtz_result{false}; } + static tb_dtz_result from_value(const chess::board& bd, const unsigned int& value) { + auto is_same_promo = [](const chess::move& mv, const int& promo) { + constexpr int num_pieces = 6; + return ((!mv.is_promotion() && promo == 0) || (mv.is_promotion() && (num_pieces - promo - 1) == static_cast(mv.promotion()))); + }; + + if (value == TB_RESULT_FAILED || value == TB_RESULT_CHECKMATE || value == TB_RESULT_STALEMATE) { failure(); } + const int wdl = TB_GET_WDL(value); + + const chess::move dtz_move = [&] { + const chess::move_list list = bd.generate_moves(); + const int promo = TB_GET_PROMOTES(value); + const int from = TB_GET_FROM(value); + const int to = TB_GET_TO(value); + const auto it = std::find_if(list.begin(), list.end(), [&](const chess::move& mv) { + return mv.from().index() == from && mv.to().index() == to && is_same_promo(mv, promo); + }); + if (it != list.end()) { return *it; } + return chess::move::null(); + }(); + + if (dtz_move == chess::move::null()) { return failure(); } + + if (wdl == TB_WIN) { return tb_dtz_result{true, search::tb_win_score, dtz_move}; } + if (wdl == TB_LOSS) { return tb_dtz_result{true, search::tb_loss_score, dtz_move}; } + return tb_dtz_result{true, search::draw_score, dtz_move}; + } +}; + +tb_wdl_result probe_wdl(const chess::board& bd) { + if (bd.num_pieces() > TB_LARGEST || bd.lat_.half_clock != 0) { return tb_wdl_result::failure(); } + if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_wdl_result::failure(); } + + constexpr unsigned int rule_50 = 0; + constexpr unsigned int castling_rights = 0; + const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; + const bool turn = bd.turn(); + + const unsigned value = tb_probe_wdl( + bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, + (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, + (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, + (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn); + + return tb_wdl_result::from_value(value); +} + +tb_dtz_result probe_dtz(const chess::board& bd) { + if (bd.num_pieces() > TB_LARGEST) { return tb_dtz_result::failure(); } + if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_dtz_result::failure(); } + + const unsigned int rule_50 = bd.lat_.half_clock; + constexpr unsigned int castling_rights = 0; + const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; + const bool turn = bd.turn(); + + const unsigned value = tb_probe_root( + bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, + (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, + (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, + (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn, nullptr); + + return tb_dtz_result::from_value(bd, value); +} + +void init(const std::string& path) { tb_init(path.c_str()); } + +} // namespace syzygy \ No newline at end of file diff --git a/include/thread_worker.h b/include/thread_worker.h index 5ce0131..8ac126e 100644 --- a/include/thread_worker.h +++ b/include/thread_worker.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,7 @@ struct internal_state { std::atomic_bool is_stable{false}; std::atomic_size_t nodes{}; + std::atomic_size_t tb_hits{}; std::atomic depth{}; std::atomic score{}; @@ -81,6 +83,7 @@ struct internal_state { cache.clear(); is_stable.store(false); nodes.store(0); + tb_hits.store(0); depth.store(0); score.store(0); best_move.store(move::null().data); @@ -182,7 +185,7 @@ struct thread_worker { const move_list list = bd.generate_noisy_moves(); const bool is_check = bd.is_check(); - if (list.size() == 0 && is_check) { return ss.effective_mate_score(); } + if (list.size() == 0 && is_check) { return ss.loss_score(); } if (ss.is_two_fold(bd.hash())) { return search::draw_score; } if (bd.is_trivially_drawn()) { return search::draw_score; } @@ -199,7 +202,7 @@ struct thread_worker { const auto [static_value, value] = [&] { const auto maybe_eval = internal.cache.find(bd.hash()); - const search::score_type static_value = is_check ? ss.effective_mate_score() : + const search::score_type static_value = is_check ? ss.loss_score() : !is_pv && maybe_eval.has_value() ? maybe_eval.value() : eval.evaluate(bd.turn()); @@ -290,11 +293,15 @@ struct thread_worker { const move_list list = bd.generate_moves(); const bool is_check = bd.is_check(); - if (list.size() == 0 && is_check) { return make_result(ss.effective_mate_score(), move::null()); } + if (list.size() == 0 && is_check) { return make_result(ss.loss_score(), move::null()); } if (list.size() == 0) { return make_result(search::draw_score, move::null()); } if (!is_root && ss.is_two_fold(bd.hash())) { return make_result(search::draw_score, move::null()); } if (!is_root && bd.is_trivially_drawn()) { return make_result(search::draw_score, move::null()); } + if constexpr (is_root) { + if (const syzygy::tb_dtz_result result = syzygy::probe_dtz(bd); result.success) { return make_result(result.score, result.move); } + } + const search::score_type original_alpha = alpha; // step 3. initialize move orderer (setting tt move first if applicable) @@ -314,6 +321,16 @@ struct thread_worker { orderer.set_first(entry.best_move()); } + if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(bd); !is_root && result.success) { + ++internal.tb_hits; + + switch (result.wdl) { + case syzygy::wdl_type::loss: return make_result(ss.loss_score(), move::null()); + case syzygy::wdl_type::draw: return make_result(search::draw_score, move::null()); + case syzygy::wdl_type::win: return make_result(ss.win_score(), move::null()); + } + } + // step 4. internal iterative reductions const bool should_iir = !maybe.has_value() && !ss.has_excluded() && depth >= external.constants->iir_depth(); if (should_iir) { --depth; } @@ -321,7 +338,7 @@ struct thread_worker { // step 5. compute static eval and adjust appropriately if there's a tt hit const auto [static_value, value] = [&] { const auto maybe_eval = internal.cache.find(bd.hash()); - const search::score_type static_value = is_check ? ss.effective_mate_score() : + const search::score_type static_value = is_check ? ss.loss_score() : !is_pv && maybe_eval.has_value() ? maybe_eval.value() : eval.evaluate(bd.turn()); @@ -345,7 +362,7 @@ struct thread_worker { // step 8. static null move pruning const bool snm_prune = !is_pv && !ss.has_excluded() && !is_check && depth <= external.constants->snmp_depth() && - value > beta + external.constants->snmp_margin(improving, depth) && value > ss.effective_mate_score(); + value > beta + external.constants->snmp_margin(improving, depth) && value > ss.loss_score(); if (snm_prune) { return make_result(value, move::null()); } @@ -375,7 +392,7 @@ struct thread_worker { move_list quiets_tried{}; // move loop - search::score_type best_score = ss.effective_mate_score(); + search::score_type best_score = ss.loss_score(); move best_move = *list.begin(); bool did_double_extend = false; @@ -598,6 +615,7 @@ struct thread_worker { void go(const position_history& hist, const board& bd, const search::depth_type& start_depth) { loop.next([hist, bd, start_depth, this] { internal.nodes.store(0); + internal.tb_hits.store(0); internal.depth.store(start_depth); internal.is_stable.store(false); internal.best_move.store(bd.generate_moves().begin()->data); diff --git a/include/uci.h b/include/uci.h index a5c2a34..cb26635 100644 --- a/include/uci.h +++ b/include/uci.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -112,7 +113,9 @@ struct uci { book_info_string(); }); - return uci_options(weight_path, hash_size, thread_count, ponder, own_book, book_path); + auto syzygy_path = option_callback(string_option("SyzygyPath"), [this](const std::string& path) { syzygy::init(path); }); + + return uci_options(weight_path, hash_size, thread_count, own_book, book_path, syzygy_path); } void uci_new_game() { @@ -164,7 +167,7 @@ struct uci { const size_t nps = std::chrono::milliseconds(std::chrono::seconds(1)).count() * nodes / (1 + elapsed_ms); if (is_searching() && depth < search::max_depth) { os << "info depth " << depth << " seldepth " << worker.internal.stack.sel_depth() << " score cp " << score << " nodes " << nodes << " nps " - << nps << " time " << elapsed_ms << " pv " << worker.internal.stack.pv_string() << std::endl; + << nps << " time " << elapsed_ms << " tbhits " << worker.internal.tb_hits << " pv " << worker.internal.stack.pv_string() << std::endl; } } @@ -222,6 +225,21 @@ struct uci { os << "score: " << evaluator.evaluate(position.turn()) << std::endl; } + void probe() { + std::lock_guard os_lk(os_mutex_); + if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(position); result.success) { + std::cout << "success: " << [&]{ + switch (result.wdl) { + case syzygy::wdl_type::loss: return "loss"; + case syzygy::wdl_type::draw: return "draw"; + case syzygy::wdl_type::win: return "win"; + } + }() << std::endl; + } else { + std::cout << "fail" << std::endl; + } + } + void see() { std::lock_guard os_lk(os_mutex_); for (const chess::move& mv : position.generate_moves()) { @@ -263,6 +281,8 @@ struct uci { eval(); } else if (!is_searching() && line == "see") { see(); + } else if (!is_searching() && line == "probe") { + probe(); } else if (!is_searching() && std::regex_match(line, perft_rgx)) { perft(line); } else if (!is_searching() && std::regex_match(line, go_rgx)) { diff --git a/syzygy/LICENSE b/syzygy/LICENSE new file mode 100644 index 0000000..8eb910f --- /dev/null +++ b/syzygy/LICENSE @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2015 basil00 +Modifications Copyright (c) 2016-2017 by Jon Dart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/syzygy/README.md b/syzygy/README.md new file mode 100644 index 0000000..5b20015 --- /dev/null +++ b/syzygy/README.md @@ -0,0 +1,104 @@ +Fathom +====== + +Fathom is a stand-alone Syzygy tablebase probing tool. The aims of Fathom +are: + +* To make it easy to integrate the Syzygy tablebases into existing chess + engines; +* To make it easy to create stand-alone applications that use the Syzygy + tablebases; + +Tool +---- + +Fathom includes a stand-alone command-line syzygy probing tool `fathom`. To +probe a position, simply run the command: + + fathom --path= "FEN-string" + +The tool will print out a PGN representation of the probe result, including: + +* Result: "1-0" (white wins), "1/2-1/2" (draw), or "0-1" (black wins) +* The Win-Draw-Loss (WDL) value for the next move: "Win", "Draw", "Loss", + "CursedWin" (win but 50-move draw) or "BlessedLoss" (loss but 50-move draw) +* The Distance-To-Zero (DTZ) value (in plys) for the next move +* WinningMoves: The list of all winning moves +* DrawingMoves: The list of all drawing moves +* LosingMoves: The list of all losing moves +* A pseudo "principle variation" of Syzygy vs. Syzygy for the input position. + +For more information, run the following command: + + fathom --help + +Pre-compiled versions of `fathom` (for all platforms) are available from here: + +* https://github.com/basil00/Fathom/releases + +Programming API +--------------- + +Fathom provides a simple API. There are three main function calls: + +* `tb_init` initializes the tablebase +* `tb_probe_wdl` probes the Win-Draw-Loss (WDL) table for a given position +* `tb_probe_root` probes the Distance-To-Zero (DTZ) table for the given + position. + +All of the API functions use basic integer types, i.e. there is no need to +create and initialize data-structures. Fathom does not require the callee +to provide any additional functionality (e.g. move generation) unlike the +traditional `tbprobe` code. However, chess engines can opt to replace some +of the functionality of Fathom for better performance (see below). + +Chess Engines +------------- + +Chess engines can be `tb_probe_wdl` to get the WDL value during search. The +`tb_probe_root` functional can be used to help pick the best move at the root. +Note that `tb_probe_root` is slower and therefore should only be used at the +root. + +Chess engines can opt for a tighter integration of Fathom by configuring +`tbconfig.h`. Specifically, the chess engines can define `TB_*_ATTACKS` +macros that replace the default definitions with the engine's own definitions, +avoiding duplication of functionality. + +Credits +------- + +The Syzygy tablebases were created by Ronald de Man. Much of the probing code +`tbprobe.c` is a modified version of Ronald's `tbprobe.cpp` for Stockfish (all +Stockfish-specific code has been removed). The `tbcore.c` file is virtually +unchanged from Ronald's original version. + +License +------- + +(C) 2013-2015 Ronald de Man (original code) +(C) 2015 basil (new modifications) + +Ronald de Man's original code can be "redistributed and/or modified without +restrictions". + +The new modifications are released under the permissive MIT License: + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/syzygy/stdendian.h b/syzygy/stdendian.h new file mode 100644 index 0000000..cd0e317 --- /dev/null +++ b/syzygy/stdendian.h @@ -0,0 +1,285 @@ +#ifndef _STDENDIAN_H_ +#define _STDENDIAN_H_ +/* from https://gist.github.com/michaeljclark/3b4fd912f6fa8bb598b3 */ +/* modified to use functions not macros for bswap */ +/* and added a fix for Cygwin */ +/* + * stdendian.h + * + * This header defines the following endian macros as defined here: + * http://austingroupbugs.net/view.php?id=162 + * + * BYTE_ORDER this macro shall have a value equal to one + * of the *_ENDIAN macros in this header. + * LITTLE_ENDIAN if BYTE_ORDER == LITTLE_ENDIAN, the host + * byte order is from least significant to + * most significant. + * BIG_ENDIAN if BYTE_ORDER == BIG_ENDIAN, the host byte + * order is from most significant to least + * significant. + * + * The following are defined as macros: + * + * uint16_t bswap16(uint16_t x); + * uint32_t bswap32(uint32_t x); + * uint64_t bswap64(uint64_t x); + + * uint16_t htobe16(uint16_t x); + * uint16_t htole16(uint16_t x); + * uint16_t be16toh(uint16_t x); + * uint16_t le16toh(uint16_t x); + * + * uint32_t htobe32(uint32_t x); + * uint32_t htole32(uint32_t x); + * uint32_t be32toh(uint32_t x); + * uint32_t le32toh(uint32_t x); + * + * uint64_t htobe64(uint64_t x); + * uint64_t htole64(uint64_t x); + * uint64_t be64toh(uint64_t x); + * uint64_t le64toh(uint64_t x); + * + * The header defines the following macro for OpenCL compatibility + * https://www.khronos.org/registry/cl/sdk/2.0/docs/man/xhtml/preprocessorDirectives.html + * + * __ENDIAN_LITTLE__ if BYTE_ORDER == LITTLE_ENDIAN then this + * macro is present for OpenCL compatibility + * + * The implementation provides a uniform interface to endian macros using only + * system headers on recent Linux, Darwin, FreeBSD, Solaris and Windows systems. + * + * This approach is intended to avoid the need for preflight configure scripts. + * An alternative approach would be to test compiler CPU architecture marcros. + * + * This header has had *limited* testing on recent C11/C++11 compilers and is + * based on the austin bug tracker interface, manpages, and headers present in + * Linux, FreeBSD, Windows, Solaris and Darwin. + * + * The header uses __builtin_bswapXX intrinsic with GCC/Clang (__GNUC__) on + * platforms that do not provide bswap16, bswap32, bswap64 (Darwin) + * + * Public Domain. + */ + +/* requires C11 or C++11 */ +#if defined (__cplusplus) +#include +#elif !defined (__OPENCL_VERSION__) +#include +#endif + +/* Linux / GLIBC */ +#if defined(__linux__) || defined(__GLIBC__) || defined(__CYGWIN__) +#include +#include +#define __ENDIAN_DEFINED 1 +#define __BSWAP_DEFINED 1 +#define __HOSTSWAP_DEFINED 1 +// NDK defines _BYTE_ORDER etc +#ifndef _BYTE_ORDER +#define _BYTE_ORDER __BYTE_ORDER +#define _LITTLE_ENDIAN __LITTLE_ENDIAN +#define _BIG_ENDIAN __BIG_ENDIAN +#endif +#define bswap16(x) bswap_16(x) +#define bswap32(x) bswap_32(x) +#define bswap64(x) bswap_64(x) +#endif /* __linux__ || __GLIBC__ */ + +/* BSD */ +#if defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) || defined(__OpenBSD__) +#include +#define __ENDIAN_DEFINED 1 +#define __BSWAP_DEFINED 1 +#define __HOSTSWAP_DEFINED 1 +#endif /* BSD */ + +/* Solaris */ +#if defined (sun) +#include +/* sun headers don't set a value for _LITTLE_ENDIAN or _BIG_ENDIAN */ +#if defined(_LITTLE_ENDIAN) +#undef _LITTLE_ENDIAN +#define _LITTLE_ENDIAN 1234 +#define _BIG_ENDIAN 4321 +#define _BYTE_ORDER _LITTLE_ENDIAN +#elif defined(_BIG_ENDIAN) +#undef _BIG_ENDIAN +#define _LITTLE_ENDIAN 1234 +#define _BIG_ENDIAN 4321 +#define _BYTE_ORDER _BIG_ENDIAN +#endif +#define __ENDIAN_DEFINED 1 +#endif /* sun */ + +/* Windows */ +#if defined(_WIN32) || defined(_MSC_VER) +/* assumes all Microsoft targets are little endian */ +#define _LITTLE_ENDIAN 1234 +#define _BIG_ENDIAN 4321 +#define _BYTE_ORDER _LITTLE_ENDIAN +#define __ENDIAN_DEFINED 1 +#endif /* _MSC_VER */ + +/* OS X */ +#if defined(__APPLE__) +#include +#define _BYTE_ORDER BYTE_ORDER +#define _LITTLE_ENDIAN LITTLE_ENDIAN +#define _BIG_ENDIAN BIG_ENDIAN +#define __ENDIAN_DEFINED 1 +#endif /* __APPLE__ */ + +/* OpenCL */ +#if defined (__OPENCL_VERSION__) +#define _LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#if defined (__ENDIAN_LITTLE__) +#define _BYTE_ORDER _LITTLE_ENDIAN +#else +#define _BYTE_ORDER _BIG_ENDIAN +#endif +#define bswap16(x) as_ushort(as_uchar2(ushort(x)).s1s0) +#define bswap32(x) as_uint(as_uchar4(uint(x)).s3s2s1s0) +#define bswap64(x) as_ulong(as_uchar8(ulong(x)).s7s6s5s4s3s2s1s0) +#define __ENDIAN_DEFINED 1 +#define __BSWAP_DEFINED 1 +#endif + +/* Unknown */ +#if !__ENDIAN_DEFINED +#error Could not determine CPU byte order +#endif + +/* POSIX - http://austingroupbugs.net/view.php?id=162 */ +#ifndef BYTE_ORDER +#define BYTE_ORDER _BYTE_ORDER +#endif +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN _LITTLE_ENDIAN +#endif +#ifndef BIG_ENDIAN +#define BIG_ENDIAN _BIG_ENDIAN +#endif + +/* OpenCL compatibility - define __ENDIAN_LITTLE__ on little endian systems */ +#if _BYTE_ORDER == _LITTLE_ENDIAN +#if !defined (__ENDIAN_LITTLE__) +#define __ENDIAN_LITTLE__ 1 +#endif +#endif + +/* Byte swap macros */ +#if !__BSWAP_DEFINED + +#ifndef bswap16 +/* handle missing __builtin_bswap16 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624 */ +#if defined __GNUC__ +#define bswap16(x) __builtin_bswap16(x) +#else +inline uint16_t bswap16(uint16_t x) { + return (uint16_t)((((uint16_t) (x) & 0xff00) >> 8) | \ + (((uint16_t) (x) & 0x00ff) << 8)); +} +#endif +#endif + +#ifndef bswap32 +#if defined __GNUC__ +#define bswap32(x) __builtin_bswap32(x) +#else +inline uint32_t bswap32(uint32_t x) { + return (( x & 0xff000000) >> 24) | \ + (( x & 0x00ff0000) >> 8) | \ + (( x & 0x0000ff00) << 8) | \ + (( x & 0x000000ff) << 24); +} +#endif +#endif + +#ifndef bswap64 +#if defined __GNUC__ +#define bswap64(x) __builtin_bswap64(x) +#else +inline uint64_t bswap64(uint64_t x) { + return (( x & 0xff00000000000000ull) >> 56) | \ + (( x & 0x00ff000000000000ull) >> 40) | \ + (( x & 0x0000ff0000000000ull) >> 24) | \ + (( x & 0x000000ff00000000ull) >> 8) | \ + (( x & 0x00000000ff000000ull) << 8) | \ + (( x & 0x0000000000ff0000ull) << 24) | \ + (( x & 0x000000000000ff00ull) << 40) | \ + (( x & 0x00000000000000ffull) << 56); +} +#endif +#endif + +#endif + +/* Host swap macros */ +#ifndef __HOSTSWAP_DEFINED +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobe16(x) bswap16((x)) +#define htole16(x) ((uint16_t)(x)) +#define be16toh(x) bswap16((x)) +#define le16toh(x) ((uint16_t)(x)) + +#define htobe32(x) bswap32((x)) +#define htole32(x) ((uint32_t((x)) +#define be32toh(x) bswap32((x)) +#define le32toh(x) ((uint32_t)(x)) + +#define htobe64(x) bswap64((x)) +#define htole64(x) ((uint64_t)(x)) +#define be64toh(x) bswap64((x)) +#define le64toh(x) ((uint64_t)(x)) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define htobe16(x) ((uint16_t)(x)) +#define htole16(x) bswap16((x)) +#define be16toh(x) ((uint16_t)(x)) +#define le16toh(x) bswap16((x)) + +#define htobe32(x) ((uint32_t)(x)) +#define htole32(x) bswap32((x)) +#define be32toh(x) ((uint32_t)(x)) +#define le64toh(x) bswap64((x)) + +#define htobe64(x) ((uint64_t)(x)) +#define htole64(x) bswap64((x)) +#define be64toh(x) ((uint64_t)(x)) +#define le32toh(x) bswap32((x)) +#endif +#endif + +/* + +#include +#include + +int main() +{ + +#if BYTE_ORDER == LITTLE_ENDIAN +printf("little endian\n"); +#endif + +#if BYTE_ORDER == BIG_ENDIAN +printf("big endian\n"); +#endif + +printf("bswap16(%04x) %04x\n", 0xf0e0, bswap16(0xf0e0)); +printf("htobe16(%04x) %04x\n", 0xf0e0, htobe16(0xf0e0)); +printf("htole16(%04x) %04x\n", 0xf0e0, htole16(0xf0e0)); + +printf("bswap32(%08x) %08x\n", 0xf0e0d0c0, bswap32(0xf0e0d0c0)); +printf("htobe32(%08x) %08x\n", 0xf0e0d0c0, htobe32(0xf0e0d0c0)); +printf("htole32(%08x) %08x\n", 0xf0e0d0c0, htole32(0xf0e0d0c0)); + +printf("bswap64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, bswap64(0xf0e0d0c0b0a09080ULL)); +printf("htobe64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, htobe64(0xf0e0d0c0b0a09080ULL)); +printf("htole64(%016llx) %016llx\n", 0xf0e0d0c0b0a09080ULL, htole64(0xf0e0d0c0b0a09080ULL)); +} + +*/ +#endif diff --git a/syzygy/tbchess.c b/syzygy/tbchess.c new file mode 100644 index 0000000..3b84040 --- /dev/null +++ b/syzygy/tbchess.c @@ -0,0 +1,1054 @@ +/* +Copyright (c) 2015 basil00 +Modifications Copyright (c) 2016-2020 by Jon Dart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include "tbprobe.h" +#include + +#define TB_PAWN 1 +#define TB_KNIGHT 2 +#define TB_BISHOP 3 +#define TB_ROOK 4 +#define TB_QUEEN 5 +#define TB_KING 6 + +#define TB_WPAWN TB_PAWN +#define TB_BPAWN (TB_PAWN | 8) + +#define WHITE_KING (TB_WPAWN + 5) +#define WHITE_QUEEN (TB_WPAWN + 4) +#define WHITE_ROOK (TB_WPAWN + 3) +#define WHITE_BISHOP (TB_WPAWN + 2) +#define WHITE_KNIGHT (TB_WPAWN + 1) +#define WHITE_PAWN TB_WPAWN +#define BLACK_KING (TB_BPAWN + 5) +#define BLACK_QUEEN (TB_BPAWN + 4) +#define BLACK_ROOK (TB_BPAWN + 3) +#define BLACK_BISHOP (TB_BPAWN + 2) +#define BLACK_KNIGHT (TB_BPAWN + 1) +#define BLACK_PAWN TB_BPAWN + +#define PRIME_WHITE_QUEEN 11811845319353239651ull +#define PRIME_WHITE_ROOK 10979190538029446137ull +#define PRIME_WHITE_BISHOP 12311744257139811149ull +#define PRIME_WHITE_KNIGHT 15202887380319082783ull +#define PRIME_WHITE_PAWN 17008651141875982339ull +#define PRIME_BLACK_QUEEN 15484752644942473553ull +#define PRIME_BLACK_ROOK 18264461213049635989ull +#define PRIME_BLACK_BISHOP 15394650811035483107ull +#define PRIME_BLACK_KNIGHT 13469005675588064321ull +#define PRIME_BLACK_PAWN 11695583624105689831ull + +#define BOARD_RANK_EDGE 0x8181818181818181ull +#define BOARD_FILE_EDGE 0xFF000000000000FFull +#define BOARD_EDGE (BOARD_RANK_EDGE | BOARD_FILE_EDGE) +#define BOARD_RANK_1 0x00000000000000FFull +#define BOARD_FILE_A 0x8080808080808080ull + +#define KEY_KvK 0 + +#define BEST_NONE 0xFFFF +#define SCORE_ILLEGAL 0x7FFF + +// Note: WHITE, BLACK values are reverse of Stockfish +#ifdef __cplusplus +namespace { +enum Color { BLACK, WHITE }; +enum PieceType { PAWN=1, KNIGHT, BISHOP, ROOK, QUEEN, KING }; +enum Piece { + W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; +#else +typedef enum Color { BLACK, WHITE } Color; +typedef enum PieceType { PAWN=1, KNIGHT, BISHOP, ROOK, QUEEN, KING } PieceType; +typedef enum Piece { + W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING +} Piece; +#endif + +static inline Color ColorOfPiece(int piece) { + return (Color)(!(piece >> 3)); +} + +static inline PieceType TypeOfPiece(int piece) { + return (PieceType)(piece & 7); +} + +typedef int32_t Value; + +typedef struct Pos +{ + uint64_t white; + uint64_t black; + uint64_t kings; + uint64_t queens; + uint64_t rooks; + uint64_t bishops; + uint64_t knights; + uint64_t pawns; + uint8_t rule50; + uint8_t ep; + bool turn; +} Pos; + +static inline uint64_t pieces_by_type(const Pos *pos, Color c, PieceType p) { + uint64_t mask = (c == WHITE) ? pos->white : pos->black; + switch(p) { + case PAWN: + return pos->pawns & mask; + case KNIGHT: + return pos->knights & mask; + case BISHOP: + return pos->bishops & mask; + case ROOK: + return pos->rooks & mask; + case QUEEN: + return pos->queens & mask; + case KING: + return pos->kings & mask; + default: + assert(0); + return 0; + } +} + +static const char piece_to_char[] = " PNBRQK pnbrqk"; + +// map upper-case characters to piece types +static PieceType char_to_piece_type(char c) { + for (int i = PAWN; i <= KING; i++) + if (c == piece_to_char[i]) { + return (PieceType)i; + } + return (PieceType)0; +} + +#define rank(s) ((s) >> 3) +#define file(s) ((s) & 0x07) +#define board(s) ((uint64_t)1 << (s)) +#define square(r, f) (8 * (r) + (f)) + +#ifdef TB_KING_ATTACKS +#define king_attacks(s) TB_KING_ATTACKS(s) +#define king_attacks_init() /* NOP */ +#else /* TB_KING_ATTACKS */ + +static uint64_t king_attacks_table[64]; + +#define king_attacks(s) king_attacks_table[(s)] + +static void king_attacks_init(void) +{ + for (unsigned s = 0; s < 64; s++) + { + unsigned r = rank(s); + unsigned f = file(s); + uint64_t b = 0; + if (r != 0 && f != 0) + b |= board(square(r-1, f-1)); + if (r != 0) + b |= board(square(r-1, f)); + if (r != 0 && f != 7) + b |= board(square(r-1, f+1)); + if (f != 7) + b |= board(square(r, f+1)); + if (r != 7 && f != 7) + b |= board(square(r+1, f+1)); + if (r != 7) + b |= board(square(r+1, f)); + if (r != 7 && f != 0) + b |= board(square(r+1, f-1)); + if (f != 0) + b |= board(square(r, f-1)); + king_attacks_table[s] = b; + } +} + +#endif /* TB_KING_ATTACKS */ + +#ifdef TB_KNIGHT_ATTACKS +#define knight_attacks(s) TB_KNIGHT_ATTACKS(s) +#define knight_attacks_init() /* NOP */ +#else /* TB_KNIGHT_ATTACKS */ + +static uint64_t knight_attacks_table[64]; + +#define knight_attacks(s) knight_attacks_table[(s)] + +static void knight_attacks_init(void) +{ + for (unsigned s = 0; s < 64; s++) + { + int r1, r = rank(s); + int f1, f = file(s); + uint64_t b = 0; + r1 = r-1; f1 = f-2; + if (r1 >= 0 && f1 >= 0) + b |= board(square(r1, f1)); + r1 = r-1; f1 = f+2; + if (r1 >= 0 && f1 <= 7) + b |= board(square(r1, f1)); + r1 = r-2; f1 = f-1; + if (r1 >= 0 && f1 >= 0) + b |= board(square(r1, f1)); + r1 = r-2; f1 = f+1; + if (r1 >= 0 && f1 <= 7) + b |= board(square(r1, f1)); + r1 = r+1; f1 = f-2; + if (r1 <= 7 && f1 >= 0) + b |= board(square(r1, f1)); + r1 = r+1; f1 = f+2; + if (r1 <= 7 && f1 <= 7) + b |= board(square(r1, f1)); + r1 = r+2; f1 = f-1; + if (r1 <= 7 && f1 >= 0) + b |= board(square(r1, f1)); + r1 = r+2; f1 = f+1; + if (r1 <= 7 && f1 <= 7) + b |= board(square(r1, f1)); + knight_attacks_table[s] = b; + } +} + +#endif /* TB_KNIGHT_ATTACKS */ + +#ifdef TB_BISHOP_ATTACKS +#define bishop_attacks(s, occ) TB_BISHOP_ATTACKS(s, occ) +#define bishop_attacks_init() /* NOP */ +#else /* TB_BISHOP_ATTACKS */ + +static uint64_t diag_attacks_table[64][64]; +static uint64_t anti_attacks_table[64][64]; + +static const unsigned square2diag_table[64] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, + 14, 0, 1, 2, 3, 4, 5, 6, + 13, 14, 0, 1, 2, 3, 4, 5, + 12, 13, 14, 0, 1, 2, 3, 4, + 11, 12, 13, 14, 0, 1, 2, 3, + 10, 11, 12, 13, 14, 0, 1, 2, + 9, 10, 11, 12, 13, 14, 0, 1, + 8, 9, 10, 11, 12, 13, 14, 0 +}; + +static const unsigned square2anti_table[64] = +{ + 8, 9, 10, 11, 12, 13, 14, 0, + 9, 10, 11, 12, 13, 14, 0, 1, + 10, 11, 12, 13, 14, 0, 1, 2, + 11, 12, 13, 14, 0, 1, 2, 3, + 12, 13, 14, 0, 1, 2, 3, 4, + 13, 14, 0, 1, 2, 3, 4, 5, + 14, 0, 1, 2, 3, 4, 5, 6, + 0, 1, 2, 3, 4, 5, 6, 7 +}; + +static const uint64_t diag2board_table[15] = +{ + 0x8040201008040201ull, + 0x0080402010080402ull, + 0x0000804020100804ull, + 0x0000008040201008ull, + 0x0000000080402010ull, + 0x0000000000804020ull, + 0x0000000000008040ull, + 0x0000000000000080ull, + 0x0100000000000000ull, + 0x0201000000000000ull, + 0x0402010000000000ull, + 0x0804020100000000ull, + 0x1008040201000000ull, + 0x2010080402010000ull, + 0x4020100804020100ull, +}; + +static const uint64_t anti2board_table[15] = +{ + 0x0102040810204080ull, + 0x0204081020408000ull, + 0x0408102040800000ull, + 0x0810204080000000ull, + 0x1020408000000000ull, + 0x2040800000000000ull, + 0x4080000000000000ull, + 0x8000000000000000ull, + 0x0000000000000001ull, + 0x0000000000000102ull, + 0x0000000000010204ull, + 0x0000000001020408ull, + 0x0000000102040810ull, + 0x0000010204081020ull, + 0x0001020408102040ull, +}; + +static inline size_t diag2index(uint64_t b) +{ + b *= 0x0101010101010101ull; + b >>= 56; + b >>= 1; + return (size_t)b; +} + +static inline size_t anti2index(uint64_t b) +{ + return diag2index(b); +} + +#define diag(s) square2diag_table[(s)] +#define anti(s) square2anti_table[(s)] +#define diag2board(d) diag2board_table[(d)] +#define anti2board(a) anti2board_table[(a)] + +static uint64_t bishop_attacks(unsigned sq, uint64_t occ) +{ + occ &= ~board(sq); + unsigned d = diag(sq), a = anti(sq); + uint64_t d_occ = occ & (diag2board(d) & ~BOARD_EDGE); + uint64_t a_occ = occ & (anti2board(a) & ~BOARD_EDGE); + size_t d_idx = diag2index(d_occ); + size_t a_idx = anti2index(a_occ); + uint64_t d_attacks = diag_attacks_table[sq][d_idx]; + uint64_t a_attacks = anti_attacks_table[sq][a_idx]; + return d_attacks | a_attacks; +} + +static void bishop_attacks_init(void) +{ + for (unsigned idx = 0; idx < 64; idx++) + { + unsigned idx1 = idx << 1; + for (unsigned s = 0; s < 64; s++) + { + int r = rank(s); + int f = file(s); + uint64_t b = 0; + for (int i = -1; f + i >= 0 && r + i >= 0; i--) + { + unsigned occ = (1 << (f + i)); + b |= board(square(r + i, f + i)); + if (idx1 & occ) + break; + } + for (int i = 1; f + i <= 7 && r + i <= 7; i++) + { + unsigned occ = (1 << (f + i)); + b |= board(square(r + i, f + i)); + if (idx1 & occ) + break; + } + diag_attacks_table[s][idx] = b; + } + } + + for (unsigned idx = 0; idx < 64; idx++) + { + unsigned idx1 = idx << 1; + for (unsigned s = 0; s < 64; s++) + { + int r = rank(s); + int f = file(s); + uint64_t b = 0; + for (int i = -1; f + i >= 0 && r - i <= 7; i--) + { + unsigned occ = (1 << (f + i)); + b |= board(square(r - i, f + i)); + if (idx1 & occ) + break; + } + for (int i = 1; f + i <= 7 && r - i >= 0; i++) + { + unsigned occ = (1 << (f + i)); + b |= board(square(r - i, f + i)); + if (idx1 & occ) + break; + } + anti_attacks_table[s][idx] = b; + } + } +} + +#endif /* TB_BISHOP_ATTACKS */ + +#ifdef TB_ROOK_ATTACKS +#define rook_attacks(s, occ) TB_ROOK_ATTACKS(s, occ) +#define rook_attacks_init() /* NOP */ +#else /* TB_ROOK_ATTACKS */ + +static uint64_t rank_attacks_table[64][64]; +static uint64_t file_attacks_table[64][64]; + +static inline size_t rank2index(uint64_t b, unsigned r) +{ + b >>= (8 * r); + b >>= 1; + return (size_t)b; +} + +static inline size_t file2index(uint64_t b, unsigned f) +{ + b >>= f; + b *= 0x0102040810204080ull; + b >>= 56; + b >>= 1; + return (size_t)b; +} + +#define rank2board(r) (0xFFull << (8 * (r))) +#define file2board(f) (0x0101010101010101ull << (f)) + +static uint64_t rook_attacks(unsigned sq, uint64_t occ) +{ + occ &= ~board(sq); + unsigned r = rank(sq), f = file(sq); + uint64_t r_occ = occ & (rank2board(r) & ~BOARD_RANK_EDGE); + uint64_t f_occ = occ & (file2board(f) & ~BOARD_FILE_EDGE); + size_t r_idx = rank2index(r_occ, r); + size_t f_idx = file2index(f_occ, f); + uint64_t r_attacks = rank_attacks_table[sq][r_idx]; + uint64_t f_attacks = file_attacks_table[sq][f_idx]; + return r_attacks | f_attacks; +} + +static void rook_attacks_init(void) +{ + for (unsigned idx = 0; idx < 64; idx++) + { + unsigned idx1 = idx << 1, occ; + for (int f = 0; f <= 7; f++) + { + uint64_t b = 0; + if (f > 0) + { + int i = f-1; + do + { + occ = (1 << i); + b |= board(square(0, i)); + i--; + } + while (!(idx1 & occ) && i >= 0); + } + if (f < 7) + { + int i = f+1; + do + { + occ = (1 << i); + b |= board(square(0, i)); + i++; + } + while (!(idx1 & occ) && i <= 7); + } + for (int r = 0; r <= 7; r++) + { + rank_attacks_table[square(r, f)][idx] = b; + b <<= 8; + } + } + } + for (unsigned idx = 0; idx < 64; idx++) + { + unsigned idx1 = idx << 1, occ; + for (int r = 0; r <= 7; r++) + { + uint64_t b = 0; + if (r > 0) + { + int i = r-1; + do + { + occ = (1 << i); + b |= board(square(i, 0)); + i--; + } + while (!(idx1 & occ) && i >= 0); + } + if (r < 7) + { + int i = r+1; + do + { + occ = (1 << i); + b |= board(square(i, 0)); + i++; + } + while (!(idx1 & occ) && i <= 7); + } + for (int f = 0; f <= 7; f++) + { + file_attacks_table[square(r, f)][idx] = b; + b <<= 1; + } + } + } +} + +#endif /* TB_ROOK_ATTACKS */ + +#ifdef TB_QUEEN_ATTACKS +#define queen_attacks(s, occ) TB_QUEEN_ATTACKS(s, occ) +#else /* TB_QUEEN_ATTACKS */ +#define queen_attacks(s, occ) \ + (rook_attacks((s), (occ)) | bishop_attacks((s), (occ))) +#endif /* TB_QUEEN_ATTACKS */ + +#ifdef TB_PAWN_ATTACKS +#define pawn_attacks(s, c) TB_PAWN_ATTACKS(s, c) +#define pawn_attacks_init() /* NOP */ +#else /* TB_PAWN_ATTACKS */ + +static uint64_t pawn_attacks_table[2][64]; + +#define pawn_attacks(s, c) pawn_attacks_table[(c)][(s)] + +static void pawn_attacks_init(void) +{ + for (unsigned s = 0; s < 64; s++) + { + int r = rank(s); + int f = file(s); + + uint64_t b = 0; + if (r != 7) + { + if (f != 0) + b |= board(square(r+1, f-1)); + if (f != 7) + b |= board(square(r+1, f+1)); + } + pawn_attacks_table[1][s] = b; + + b = 0; + if (r != 0) + { + if (f != 0) + b |= board(square(r-1, f-1)); + if (f != 7) + b |= board(square(r-1, f+1)); + } + pawn_attacks_table[0][s] = b; + } +} + +#endif /* TB_PAWN_ATTACKS */ + +/* + * Given a position, produce a 64-bit material signature key. + */ +static uint64_t calc_key(const Pos *pos, bool mirror) +{ + uint64_t white = pos->white, black = pos->black; + if (mirror) + { + uint64_t tmp = white; + white = black; + black = tmp; + } + return tb_pop_count(white & pos->queens) * PRIME_WHITE_QUEEN + + tb_pop_count(white & pos->rooks) * PRIME_WHITE_ROOK + + tb_pop_count(white & pos->bishops) * PRIME_WHITE_BISHOP + + tb_pop_count(white & pos->knights) * PRIME_WHITE_KNIGHT + + tb_pop_count(white & pos->pawns) * PRIME_WHITE_PAWN + + tb_pop_count(black & pos->queens) * PRIME_BLACK_QUEEN + + tb_pop_count(black & pos->rooks) * PRIME_BLACK_ROOK + + tb_pop_count(black & pos->bishops) * PRIME_BLACK_BISHOP + + tb_pop_count(black & pos->knights) * PRIME_BLACK_KNIGHT + + tb_pop_count(black & pos->pawns) * PRIME_BLACK_PAWN; +} + +// Produce a 64-bit material key corresponding to the material combination +// defined by pcs[16], where pcs[1], ..., pcs[6] are the number of white +// pawns, ..., kings and pcs[9], ..., pcs[14] are the number of black +// pawns, ..., kings. +static uint64_t calc_key_from_pcs(int *pcs, int mirror) +{ + mirror = (mirror? 8: 0); + return pcs[WHITE_QUEEN ^ mirror] * PRIME_WHITE_QUEEN + + pcs[WHITE_ROOK ^ mirror] * PRIME_WHITE_ROOK + + pcs[WHITE_BISHOP ^ mirror] * PRIME_WHITE_BISHOP + + pcs[WHITE_KNIGHT ^ mirror] * PRIME_WHITE_KNIGHT + + pcs[WHITE_PAWN ^ mirror] * PRIME_WHITE_PAWN + + pcs[BLACK_QUEEN ^ mirror] * PRIME_BLACK_QUEEN + + pcs[BLACK_ROOK ^ mirror] * PRIME_BLACK_ROOK + + pcs[BLACK_BISHOP ^ mirror] * PRIME_BLACK_BISHOP + + pcs[BLACK_KNIGHT ^ mirror] * PRIME_BLACK_KNIGHT + + pcs[BLACK_PAWN ^ mirror] * PRIME_BLACK_PAWN; +} + +// Produce a 64-bit material key corresponding to the material combination +// piece[0], ..., piece[num - 1], where each value corresponds to a piece +// (1-6 for white pawn-king, 9-14 for black pawn-king). +static uint64_t calc_key_from_pieces(uint8_t *piece, int num) +{ + uint64_t key = 0; + static const uint64_t keys[16] = {0,PRIME_WHITE_PAWN,PRIME_WHITE_KNIGHT, + PRIME_WHITE_BISHOP,PRIME_WHITE_ROOK, + PRIME_WHITE_QUEEN,0,0,PRIME_BLACK_PAWN, + PRIME_BLACK_KNIGHT,PRIME_BLACK_BISHOP, + PRIME_BLACK_ROOK,PRIME_BLACK_QUEEN,0}; + for (int i = 0; i < num; i++) { + assert(piece[i]<16); + key += keys[piece[i]]; + } + return key; +} + +#define make_move(promote, from, to) \ + ((((promote) & 0x7) << 12) | (((from) & 0x3F) << 6) | ((to) & 0x3F)) +#define move_from(move) \ + (((move) >> 6) & 0x3F) +#define move_to(move) \ + ((move) & 0x3F) +#define move_promotes(move) \ + (((move) >> 12) & 0x7) + +static inline int type_of_piece_moved(Pos *pos, TbMove move) { + for (int i = PAWN; i <= KING; i++) { + if ((pieces_by_type(pos,(Color)(pos->turn == WHITE),(PieceType)i) & board(move_from(move))) != 0) { + return i; + } + } + assert(0); + return 0; +} + +#define MAX_MOVES TB_MAX_MOVES +#define MOVE_STALEMATE 0xFFFF +#define MOVE_CHECKMATE 0xFFFE + +static TbMove *add_move(TbMove *moves, bool promotes, unsigned from, + unsigned to) +{ + if (!promotes) + *moves++ = make_move(TB_PROMOTES_NONE, from, to); + else + { + *moves++ = make_move(TB_PROMOTES_QUEEN, from, to); + *moves++ = make_move(TB_PROMOTES_KNIGHT, from, to); + *moves++ = make_move(TB_PROMOTES_ROOK, from, to); + *moves++ = make_move(TB_PROMOTES_BISHOP, from, to); + } + return moves; +} + +/* + * Generate all captures, including all underpomotions + */ +static TbMove *gen_captures(const Pos *pos, TbMove *moves) +{ + uint64_t occ = pos->white | pos->black; + uint64_t us = (pos->turn? pos->white: pos->black), + them = (pos->turn? pos->black: pos->white); + uint64_t b, att; + { + unsigned from = tb_lsb(pos->kings & us); + assert(from < 64); + for (att = king_attacks(from) & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->queens; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = queen_attacks(from, occ) & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->rooks; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = rook_attacks(from, occ) & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->bishops; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = bishop_attacks(from, occ) & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->knights; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = knight_attacks(from) & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->pawns; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + att = pawn_attacks(from, pos->turn); + if (pos->ep != 0 && ((att & board(pos->ep)) != 0)) + { + unsigned to = pos->ep; + moves = add_move(moves, false, from, to); + } + for (att = att & them; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, (rank(to) == 7 || rank(to) == 0), from, + to); + } + } + return moves; +} + +/* + * Generate all moves. + */ +static TbMove *gen_moves(const Pos *pos, TbMove *moves) +{ + uint64_t occ = pos->white | pos->black; + uint64_t us = (pos->turn? pos->white: pos->black), + them = (pos->turn? pos->black: pos->white); + uint64_t b, att; + + { + unsigned from = tb_lsb(pos->kings & us); + for (att = king_attacks(from) & ~us; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->queens; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = queen_attacks(from, occ) & ~us; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->rooks; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = rook_attacks(from, occ) & ~us; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->bishops; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = bishop_attacks(from, occ) & ~us; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->knights; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + for (att = knight_attacks(from) & ~us; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, false, from, to); + } + } + for (b = us & pos->pawns; b; b = tb_pop_lsb(b)) + { + unsigned from = tb_lsb(b); + unsigned next = from + (pos->turn? 8: -8); + att = pawn_attacks(from, pos->turn); + if (pos->ep != 0 && ((att & board(pos->ep)) != 0)) + { + unsigned to = pos->ep; + moves = add_move(moves, false, from, to); + } + att &= them; + if ((board(next) & occ) == 0) + { + att |= board(next); + unsigned next2 = from + (pos->turn? 16: -16); + if ((pos->turn? rank(from) == 1: rank(from) == 6) && + ((board(next2) & occ) == 0)) + att |= board(next2); + } + for (; att; att = tb_pop_lsb(att)) + { + unsigned to = tb_lsb(att); + moves = add_move(moves, (rank(to) == 7 || rank(to) == 0), from, + to); + } + } + return moves; +} + +/* + * Test if the given move is an en passant capture. + */ +static bool is_en_passant(const Pos *pos, TbMove move) +{ + uint16_t from = move_from(move); + uint16_t to = move_to(move); + uint64_t us = (pos->turn? pos->white: pos->black); + if (pos->ep == 0) + return false; + if (to != pos->ep) + return false; + if ((board(from) & us & pos->pawns) == 0) + return false; + return true; +} + + +/* + * Test if the given move is a capture. + */ +static bool is_capture(const Pos *pos, TbMove move) +{ + uint16_t to = move_to(move); + uint64_t them = (pos->turn? pos->black: pos->white); + return (them & board(to)) != 0 || is_en_passant(pos,move); +} + + +/* + * Test if the given position is legal. + * (Pawns on backrank? Can the king be captured?) + */ +static bool is_legal(const Pos *pos) +{ + uint64_t occ = pos->white | pos->black; + uint64_t us = (pos->turn? pos->black: pos->white), + them = (pos->turn? pos->white: pos->black); + uint64_t king = pos->kings & us; + if (!king) + return false; + unsigned sq = tb_lsb(king); + if (king_attacks(sq) & (pos->kings & them)) + return false; + uint64_t ratt = rook_attacks(sq, occ); + uint64_t batt = bishop_attacks(sq, occ); + if (ratt & (pos->rooks & them)) + return false; + if (batt & (pos->bishops & them)) + return false; + if ((ratt | batt) & (pos->queens & them)) + return false; + if (knight_attacks(sq) & (pos->knights & them)) + return false; + if (pawn_attacks(sq, !pos->turn) & (pos->pawns & them)) + return false; + return true; +} + +/* + * Test if the king is in check. + */ +static bool is_check(const Pos *pos) +{ + uint64_t occ = pos->white | pos->black; + uint64_t us = (pos->turn? pos->white: pos->black), + them = (pos->turn? pos->black: pos->white); + uint64_t king = pos->kings & us; + assert(king != 0); + unsigned sq = tb_lsb(king); + uint64_t ratt = rook_attacks(sq, occ); + uint64_t batt = bishop_attacks(sq, occ); + if (ratt & (pos->rooks & them)) + return true; + if (batt & (pos->bishops & them)) + return true; + if ((ratt | batt) & (pos->queens & them)) + return true; + if (knight_attacks(sq) & (pos->knights & them)) + return true; + if (pawn_attacks(sq, pos->turn) & (pos->pawns & them)) + return true; + return false; +} + +/* + * Test if the position is valid. + */ +static bool is_valid(const Pos *pos) +{ + if (tb_pop_count(pos->kings) != 2) + return false; + if (tb_pop_count(pos->kings & pos->white) != 1) + return false; + if (tb_pop_count(pos->kings & pos->black) != 1) + return false; + if ((pos->white & pos->black) != 0) + return false; + if ((pos->kings & pos->queens) != 0) + return false; + if ((pos->kings & pos->rooks) != 0) + return false; + if ((pos->kings & pos->bishops) != 0) + return false; + if ((pos->kings & pos->knights) != 0) + return false; + if ((pos->kings & pos->pawns) != 0) + return false; + if ((pos->queens & pos->rooks) != 0) + return false; + if ((pos->queens & pos->bishops) != 0) + return false; + if ((pos->queens & pos->knights) != 0) + return false; + if ((pos->queens & pos->pawns) != 0) + return false; + if ((pos->rooks & pos->bishops) != 0) + return false; + if ((pos->rooks & pos->knights) != 0) + return false; + if ((pos->rooks & pos->pawns) != 0) + return false; + if ((pos->bishops & pos->knights) != 0) + return false; + if ((pos->bishops & pos->pawns) != 0) + return false; + if ((pos->knights & pos->pawns) != 0) + return false; + if (pos->pawns & BOARD_FILE_EDGE) + return false; + if ((pos->white | pos->black) != + (pos->kings | pos->queens | pos->rooks | pos->bishops | pos->knights | + pos->pawns)) + return false; + return is_legal(pos); +} + +#define do_bb_move(b, from, to) \ + (((b) & (~board(to)) & (~board(from))) | \ + ((((b) >> (from)) & 0x1) << (to))) + +static bool do_move(Pos *pos, const Pos *pos0, TbMove move) +{ + unsigned from = move_from(move); + unsigned to = move_to(move); + unsigned promotes = move_promotes(move); + pos->turn = !pos0->turn; + pos->white = do_bb_move(pos0->white, from, to); + pos->black = do_bb_move(pos0->black, from, to); + pos->kings = do_bb_move(pos0->kings, from, to); + pos->queens = do_bb_move(pos0->queens, from, to); + pos->rooks = do_bb_move(pos0->rooks, from, to); + pos->bishops = do_bb_move(pos0->bishops, from, to); + pos->knights = do_bb_move(pos0->knights, from, to); + pos->pawns = do_bb_move(pos0->pawns, from, to); + pos->ep = 0; + if (promotes != TB_PROMOTES_NONE) + { + pos->pawns &= ~board(to); // Promotion + switch (promotes) + { + case TB_PROMOTES_QUEEN: + pos->queens |= board(to); break; + case TB_PROMOTES_ROOK: + pos->rooks |= board(to); break; + case TB_PROMOTES_BISHOP: + pos->bishops |= board(to); break; + case TB_PROMOTES_KNIGHT: + pos->knights |= board(to); break; + } + pos->rule50 = 0; + } + else if ((board(from) & pos0->pawns) != 0) + { + pos->rule50 = 0; // Pawn move + if (rank(from) == 1 && rank(to) == 3 && + (pawn_attacks(from+8, true) & pos0->pawns & pos0->black) != 0) + pos->ep = from+8; + else if (rank(from) == 6 && rank(to) == 4 && + (pawn_attacks(from-8, false) & pos0->pawns & pos0->white) != 0) + pos->ep = from-8; + else if (to == pos0->ep) + { + unsigned ep_to = (pos0->turn? to-8: to+8); + uint64_t ep_mask = ~board(ep_to); + pos->white &= ep_mask; + pos->black &= ep_mask; + pos->pawns &= ep_mask; + } + } + else if ((board(to) & (pos0->white | pos0->black)) != 0) + pos->rule50 = 0; // Capture + else + pos->rule50 = pos0->rule50 + 1; // Normal move + if (!is_legal(pos)) + return false; + return true; +} + +static bool legal_move(const Pos *pos, TbMove move) { + struct Pos pos1; + return do_move(&pos1, pos, move); +} + +/* + * Test if the king is in checkmate. + */ +static bool is_mate(const Pos *pos) +{ + if (!is_check(pos)) + return false; + uint16_t moves0[MAX_MOVES]; + uint16_t *moves = moves0; + uint16_t *end = gen_moves(pos, moves); + for (; moves < end; moves++) + { + Pos pos1; + if (do_move(&pos1, pos, *moves)) + return false; + } + return true; +} + +/* + * Generate all legal moves. + */ +static TbMove *gen_legal(const Pos *pos, TbMove *moves) +{ + TbMove pl_moves[TB_MAX_MOVES]; + TbMove *end = gen_moves(pos, pl_moves); + TbMove *results = moves; + for (TbMove *m = pl_moves; m < end; m++) { + if (legal_move(pos,*m)) { + *results++ = *m; + } + } + return results; +} + +#ifdef __cplusplus +}; +#endif + diff --git a/syzygy/tbconfig.h b/syzygy/tbconfig.h new file mode 100644 index 0000000..5836386 --- /dev/null +++ b/syzygy/tbconfig.h @@ -0,0 +1,150 @@ +/* + * tbconfig.h + * (C) 2015 basil, all rights reserved, + * Modifications Copyright 2016-2017 Jon Dart + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef TBCONFIG_H +#define TBCONFIG_H + +/****************************************************************************/ +/* BUILD CONFIG: */ +/****************************************************************************/ + +/* + * Define TB_CUSTOM_POP_COUNT to override the internal popcount + * implementation. To do this supply a macro or function definition + * here: + */ +/* #define TB_CUSTOM_POP_COUNT(x) */ + +/* + * Define TB_CUSTOM_LSB to override the internal lsb + * implementation. To do this supply a macro or function definition + * here: + */ +/* #define TB_CUSTOM_LSB(x) */ + +/* + * Define TB_NO_STDINT if you do not want to use or it is not + * available. + */ +/* #define TB_NO_STDINT */ + +/* + * Define TB_NO_STDBOOL if you do not want to use or it is not + * available or unnecessary (e.g. C++). + */ +/* #define TB_NO_STDBOOL */ + +/* + * Define TB_NO_THREADS if your program is not multi-threaded. + */ +/* #define TB_NO_THREADS */ + +/* + * Define TB_NO_HELPER_API if you do not need the helper API. + */ +/* #define TB_NO_HELPER_API */ + +/* + * Define TB_NO_HW_POP_COUNT if there is no hardware popcount instruction. + * + * Note: if defined, TB_CUSTOM_POP_COUNT is always used in preference + * to any built-in popcount functions. + * + * If no custom popcount function is defined, and if the following + * define is not set, the code will attempt to use an available hardware + * popcnt (currently supported on x86_64 architecture only) and otherwise + * will fall back to a software implementation. + */ +/* #define TB_NO_HW_POP_COUNT */ + +/***************************************************************************/ +/* SCORING CONSTANTS */ +/***************************************************************************/ +/* + * Fathom can produce scores for tablebase moves. These depend on the + * value of a pawn, and the magnitude of mate scores. The following + * constants are representative values but will likely need + * modification to adapt to an engine's own internal score values. + */ +#define TB_VALUE_PAWN 100 /* value of pawn in endgame */ +#define TB_VALUE_MATE 32000 +#define TB_VALUE_INFINITE 32767 /* value above all normal score values */ +#define TB_VALUE_DRAW 0 +#define TB_MAX_MATE_PLY 255 + +/***************************************************************************/ +/* ENGINE INTEGRATION CONFIG */ +/***************************************************************************/ + +/* + * If you are integrating tbprobe into an engine, you can replace some of + * tbprobe's built-in functionality with that already provided by the engine. + * This is OPTIONAL. If no definition are provided then tbprobe will use its + * own internal defaults. That said, for engines it is generally a good idea + * to avoid redundancy. + */ + +/* + * Define TB_KING_ATTACKS(square) to return the king attacks bitboard for a + * king at `square'. + */ +/* #define TB_KING_ATTACKS(square) */ + +/* + * Define TB_KNIGHT_ATTACKS(square) to return the knight attacks bitboard for + * a knight at `square'. + */ +/* #define TB_KNIGHT_ATTACKS(square) */ + +/* + * Define TB_ROOK_ATTACKS(square, occ) to return the rook attacks bitboard + * for a rook at `square' assuming the given `occ' occupancy bitboard. + */ +/* #define TB_ROOK_ATTACKS(square, occ) */ + +/* + * Define TB_BISHOP_ATTACKS(square, occ) to return the bishop attacks bitboard + * for a bishop at `square' assuming the given `occ' occupancy bitboard. + */ +/* #define TB_BISHOP_ATTACKS(square, occ) */ + +/* + * Define TB_QUEEN_ATTACKS(square, occ) to return the queen attacks bitboard + * for a queen at `square' assuming the given `occ' occupancy bitboard. + * NOTE: If no definition is provided then tbprobe will use: + * TB_ROOK_ATTACKS(square, occ) | TB_BISHOP_ATTACKS(square, occ) + */ +/* #define TB_QUEEN_ATTACKS(square, occ) */ + +/* + * Define TB_PAWN_ATTACKS(square, color) to return the pawn attacks bitboard + * for a `color' pawn at `square'. + * NOTE: This definition must work for pawns on ranks 1 and 8. For example, + * a white pawn on e1 attacks d2 and f2. A black pawn on e1 attacks + * nothing. Etc. + * NOTE: This definition must not include en passant captures. + */ +/* #define TB_PAWN_ATTACKS(square, color) */ + +#endif diff --git a/syzygy/tbprobe.c b/syzygy/tbprobe.c new file mode 100644 index 0000000..a5764e4 --- /dev/null +++ b/syzygy/tbprobe.c @@ -0,0 +1,2633 @@ +/* +Copyright (c) 2015 basil00 +Modifications Copyright (c) 2016-2019 by Jon Dart + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#ifdef __cplusplus +#include +#else +#include +#endif +#include +#include +#include +#include +#ifdef TB_NO_STDBOOL +#typedef uint8 bool +#else +#include +#endif +#include "tbprobe.h" + +#ifdef __cplusplus +using namespace std; +#endif + +#define TB_PIECES 7 +#define TB_HASHBITS (TB_PIECES < 7 ? 11 : 12) +#define TB_MAX_PIECE (TB_PIECES < 7 ? 254 : 650) +#define TB_MAX_PAWN (TB_PIECES < 7 ? 256 : 861) +#define TB_MAX_SYMS 4096 + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#define SEP_CHAR ':' +#define FD int +#define FD_ERR -1 +typedef size_t map_t; +#else +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#define SEP_CHAR ';' +#define FD HANDLE +#define FD_ERR INVALID_HANDLE_VALUE +typedef HANDLE map_t; +#endif + +#define DECOMP64 + +// Threading support +#ifndef TB_NO_THREADS +#if defined(__cplusplus) && (__cplusplus >= 201103L) + +#include +#define LOCK_T std::mutex +#define LOCK_INIT(x) +#define LOCK_DESTROY(x) +#define LOCK(x) x.lock() +#define UNLOCK(x) x.unlock() + +#else +#ifndef _WIN32 +#define LOCK_T pthread_mutex_t +#define LOCK_INIT(x) pthread_mutex_init(&(x), NULL) +#define LOCK_DESTROY(x) pthread_mutex_destroy(&(x)) +#define LOCK(x) pthread_mutex_lock(&(x)) +#define UNLOCK(x) pthread_mutex_unlock(&(x)) +#else +#define LOCK_T HANDLE +#define LOCK_INIT(x) do { x = CreateMutex(NULL, FALSE, NULL); } while (0) +#define LOCK_DESTROY(x) CloseHandle(x) +#define LOCK(x) WaitForSingleObject(x, INFINITE) +#define UNLOCK(x) ReleaseMutex(x) +#endif + +#endif +#else /* TB_NO_THREADS */ +#define LOCK_T int +#define LOCK_INIT(x) /* NOP */ +#define LOCK_DESTROY(x) /* NOP */ +#define LOCK(x) /* NOP */ +#define UNLOCK(x) /* NOP */ +#endif + +// population count implementation +#undef TB_SOFTWARE_POP_COUNT + +#if defined(TB_CUSTOM_POP_COUNT) +#define popcount(x) TB_CUSTOM_POP_COUNT(x) +#elif defined(TB_NO_HW_POP_COUNT) +#define TB_SOFTWARE_POP_COUNT +#elif defined (__GNUC__) && defined(__x86_64__) && defined(__SSE4_2__) +#include +#define popcount(x) (int)_mm_popcnt_u64((x)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1500) && defined(_M_AMD64) +#include +#define popcount(x) (int)_mm_popcnt_u64((x)) +#else +#define TB_SOFTWARE_POP_COUNT +#endif + +#ifdef TB_SOFTWARE_POP_COUNT +// Not a recognized compiler/architecture that has popcount: +// fall back to a software popcount. This one is still reasonably +// fast. +static inline unsigned tb_software_popcount(uint64_t x) +{ + x = x - ((x >> 1) & 0x5555555555555555ull); + x = (x & 0x3333333333333333ull) + ((x >> 2) & 0x3333333333333333ull); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0full; + return (x * 0x0101010101010101ull) >> 56; +} + +#define popcount(x) tb_software_popcount(x) +#endif + +// LSB (least-significant bit) implementation +#ifdef TB_CUSTOM_LSB +#define lsb(b) TB_CUSTOM_LSB(b) +#else +#if defined(__GNUC__) +static inline unsigned lsb(uint64_t b) { + assert(b != 0); + return __builtin_ffsll(b)-1; +} +#elif defined(_MSC_VER) +static inline unsigned lsb(uint64_t b) { + assert(b != 0); + DWORD index; +#ifdef _WIN64 + _BitScanForward64(&index,b); + return (unsigned)index; +#else + if (b & 0xffffffffULL) { + _BitScanForward(&index,(unsigned long)(b & 0xffffffffULL)); + return (unsigned)index; + } + else { + _BitScanForward(&index,(unsigned long)(b >> 32)); + return 32 + (unsigned)index; + } +#endif +} +#else +/* not a compiler/architecture with recognized builtins */ +static uint32_t get_bit32(uint64_t x) { + return (uint32_t)(((int32_t)(x))&-((int32_t)(x))); +} +static const unsigned MAGIC32 = 0xe89b2be; +static const uint32_t MagicTable32[32] = {31,0,9,1,10,20,13,2,7,11,21,23,17,14,3,25,30,8,19,12,6,22,16,24,29,18,5,15,28,4,27,26}; +static unsigned lsb(uint64_t b) { + if (b & 0xffffffffULL) + return MagicTable32[(get_bit32(b & 0xffffffffULL)*MAGIC32)>>27]; + else + return MagicTable32[(get_bit32(b >> 32)*MAGIC32)>>27]+32; +} +#endif +#endif + +#define max(a,b) a > b ? a : b +#define min(a,b) a < b ? a : b + +#include "stdendian.h" + +#if _BYTE_ORDER == _BIG_ENDIAN + +/* (unused) +static uint64_t from_le_u64(uint64_t input) { + return bswap64(input); +} +*/ + +static uint32_t from_le_u32(uint32_t input) { + return bswap32(input); +} + +static uint16_t from_le_u16(uint16_t input) { + return bswap16(input); +} + +static uint64_t from_be_u64(uint64_t x) { + return x; +} + +static uint32_t from_be_u32(uint32_t x) { + return x; +} + +/* (unused) + static uint16_t from_be_u16(uint16_t x) { + return x; + }*/ + +#else + +/* (unused) +static uint64_t from_le_u64(uint64_t x) { + return x; +} +*/ + +static uint32_t from_le_u32(uint32_t x) { + return x; +} + +static uint16_t from_le_u16(uint16_t x) { + return x; +} + +static uint64_t from_be_u64(uint64_t input) { + return bswap64(input); +} + +static uint32_t from_be_u32(uint32_t input) { + return bswap32(input); +} + +/* (unused) +static uint16_t from_be_u16(const uint16_t input) { + return bswap16(input); +} +*/ + +#endif + +inline static uint32_t read_le_u32(void *p) +{ + return from_le_u32(*(uint32_t *)p); +} + +inline static uint16_t read_le_u16(void *p) +{ + return from_le_u16(*(uint16_t *)p); +} + +static size_t file_size(FD fd) { +#ifdef _WIN32 + LARGE_INTEGER fileSize; + if (GetFileSizeEx(fd, &fileSize)==0) { + return 0; + } + return (size_t)fileSize.QuadPart; +#else + struct stat buf; + if (fstat(fd,&buf)) { + return 0; + } else { + return buf.st_size; + } +#endif +} + +#ifndef TB_NO_THREADS +static LOCK_T tbMutex; +#endif +static int initialized = 0; +static int numPaths = 0; +static char *pathString = NULL; +static char **paths = NULL; + +static FD open_tb(const char *str, const char *suffix) +{ + int i; + FD fd; + char *file; + + for (i = 0; i < numPaths; i++) { + file = (char*)malloc(strlen(paths[i]) + strlen(str) + + strlen(suffix) + 2); + strcpy(file, paths[i]); +#ifdef _WIN32 + strcat(file,"\\"); +#else + strcat(file,"/"); +#endif + strcat(file, str); + strcat(file, suffix); +#ifndef _WIN32 + fd = open(file, O_RDONLY); +#else +#ifdef _UNICODE + wchar_t ucode_name[4096]; + size_t len; + mbstowcs_s(&len, ucode_name, 4096, file, _TRUNCATE); + fd = CreateFile(ucode_name, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#else + fd = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif +#endif + free(file); + if (fd != FD_ERR) { + return fd; + } + } + return FD_ERR; +} + +static void close_tb(FD fd) +{ +#ifndef _WIN32 + close(fd); +#else + CloseHandle(fd); +#endif +} + +static void *map_file(FD fd, map_t *mapping) +{ +#ifndef _WIN32 + struct stat statbuf; + if (fstat(fd, &statbuf)) { + perror("fstat"); + close_tb(fd); + return NULL; + } + *mapping = statbuf.st_size; + void *data = mmap(NULL, statbuf.st_size, PROT_READ, + MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + perror("mmap"); + return NULL; + } +#else + DWORD size_low, size_high; + size_low = GetFileSize(fd, &size_high); + HANDLE map = CreateFileMapping(fd, NULL, PAGE_READONLY, size_high, size_low, + NULL); + if (map == NULL) { + fprintf(stderr,"CreateFileMapping() failed, error = %lu.\n", GetLastError()); + return NULL; + } + *mapping = (map_t)map; + void *data = (void *)MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if (data == NULL) { + fprintf(stderr,"MapViewOfFile() failed, error = %lu.\n", GetLastError()); + } +#endif + return data; +} + +#ifndef _WIN32 +static void unmap_file(void *data, map_t size) +{ + if (!data) return; + if (!munmap(data, size)) { + perror("munmap"); + } +} +#else +static void unmap_file(void *data, map_t mapping) +{ + if (!data) return; + if (!UnmapViewOfFile(data)) { + fprintf(stderr, "unmap failed, error code %lu\n", GetLastError()); + } + if (!CloseHandle((HANDLE)mapping)) { + fprintf(stderr, "CloseHandle failed, error code %lu\n", GetLastError()); + } +} +#endif + +#define poplsb(x) ((x) & ((x) - 1)) + +int TB_MaxCardinality = 0, TB_MaxCardinalityDTM = 0; +unsigned TB_LARGEST = 0; +//extern int TB_CardinalityDTM; + +static const char *tbSuffix[] = { ".rtbw", ".rtbm", ".rtbz" }; +static uint32_t tbMagic[] = { 0x5d23e871, 0x88ac504b, 0xa50c66d7 }; + +enum { WDL, DTM, DTZ }; +enum { PIECE_ENC, FILE_ENC, RANK_ENC }; + +// Attack and move generation code +#include "tbchess.c" + +struct PairsData { + uint8_t *indexTable; + uint16_t *sizeTable; + uint8_t *data; + uint16_t *offset; + uint8_t *symLen; + uint8_t *symPat; + uint8_t blockSize; + uint8_t idxBits; + uint8_t minLen; + uint8_t constValue[2]; + uint64_t base[1]; +}; + +struct EncInfo { + struct PairsData *precomp; + size_t factor[TB_PIECES]; + uint8_t pieces[TB_PIECES]; + uint8_t norm[TB_PIECES]; +}; + +struct BaseEntry { + uint64_t key; + uint8_t *data[3]; + map_t mapping[3]; +#ifdef __cplusplus + atomic ready[3]; +#else + atomic_bool ready[3]; +#endif + uint8_t num; + bool symmetric, hasPawns, hasDtm, hasDtz; + union { + bool kk_enc; + uint8_t pawns[2]; + }; + bool dtmLossOnly; +}; + +struct PieceEntry { + struct BaseEntry be; + struct EncInfo ei[5]; // 2 + 2 + 1 + uint16_t *dtmMap; + uint16_t dtmMapIdx[2][2]; + void *dtzMap; + uint16_t dtzMapIdx[4]; + uint8_t dtzFlags; +}; + +struct PawnEntry { + struct BaseEntry be; + struct EncInfo ei[24]; // 4 * 2 + 6 * 2 + 4 + uint16_t *dtmMap; + uint16_t dtmMapIdx[6][2][2]; + void *dtzMap; + uint16_t dtzMapIdx[4][4]; + uint8_t dtzFlags[4]; + bool dtmSwitched; +}; + +struct TbHashEntry { + uint64_t key; + struct BaseEntry *ptr; +}; + +static int tbNumPiece, tbNumPawn; +static int numWdl, numDtm, numDtz; + +static struct PieceEntry *pieceEntry; +static struct PawnEntry *pawnEntry; +static struct TbHashEntry tbHash[1 << TB_HASHBITS]; + +static void init_indices(void); + +// Forward declarations. These functions without the tb_ +// prefix take a pos structure as input. +static int probe_wdl(Pos *pos, int *success); +static int probe_dtz(Pos *pos, int *success); +static int root_probe_wdl(const Pos *pos, bool useRule50, struct TbRootMoves *rm); +static int root_probe_dtz(const Pos *pos, bool hasRepeated, bool useRule50, struct TbRootMoves *rm); +static uint16_t probe_root(Pos *pos, int *score, unsigned *results); + +unsigned tb_probe_wdl_impl( + uint64_t white, + uint64_t black, + uint64_t kings, + uint64_t queens, + uint64_t rooks, + uint64_t bishops, + uint64_t knights, + uint64_t pawns, + unsigned ep, + bool turn) +{ + Pos pos = + { + white, + black, + kings, + queens, + rooks, + bishops, + knights, + pawns, + 0, + (uint8_t)ep, + turn + }; + int success; + int v = probe_wdl(&pos, &success); + if (success == 0) + return TB_RESULT_FAILED; + return (unsigned)(v + 2); +} + +static unsigned dtz_to_wdl(int cnt50, int dtz) +{ + int wdl = 0; + if (dtz > 0) + wdl = (dtz + cnt50 <= 100? 2: 1); + else if (dtz < 0) + wdl = (-dtz + cnt50 <= 100? -2: -1); + return wdl + 2; +} + +unsigned tb_probe_root_impl( + uint64_t white, + uint64_t black, + uint64_t kings, + uint64_t queens, + uint64_t rooks, + uint64_t bishops, + uint64_t knights, + uint64_t pawns, + unsigned rule50, + unsigned ep, + bool turn, + unsigned *results) +{ + Pos pos = + { + white, + black, + kings, + queens, + rooks, + bishops, + knights, + pawns, + (uint8_t)rule50, + (uint8_t)ep, + turn + }; + int dtz; + if (!is_valid(&pos)) + return TB_RESULT_FAILED; + TbMove move = probe_root(&pos, &dtz, results); + if (move == 0) + return TB_RESULT_FAILED; + if (move == MOVE_CHECKMATE) + return TB_RESULT_CHECKMATE; + if (move == MOVE_STALEMATE) + return TB_RESULT_STALEMATE; + unsigned res = 0; + res = TB_SET_WDL(res, dtz_to_wdl(rule50, dtz)); + res = TB_SET_DTZ(res, (dtz < 0? -dtz: dtz)); + res = TB_SET_FROM(res, move_from(move)); + res = TB_SET_TO(res, move_to(move)); + res = TB_SET_PROMOTES(res, move_promotes(move)); + res = TB_SET_EP(res, is_en_passant(&pos, move)); + return res; +} + +int tb_probe_root_dtz( + uint64_t white, + uint64_t black, + uint64_t kings, + uint64_t queens, + uint64_t rooks, + uint64_t bishops, + uint64_t knights, + uint64_t pawns, + unsigned rule50, + unsigned castling, + unsigned ep, + bool turn, + bool hasRepeated, + bool useRule50, + struct TbRootMoves *results) { + Pos pos = + { + white, + black, + kings, + queens, + rooks, + bishops, + knights, + pawns, + (uint8_t)rule50, + (uint8_t)ep, + turn + }; + if (castling != 0) return 0; + return root_probe_dtz(&pos, hasRepeated, useRule50, results); +} + +int tb_probe_root_wdl( + uint64_t white, + uint64_t black, + uint64_t kings, + uint64_t queens, + uint64_t rooks, + uint64_t bishops, + uint64_t knights, + uint64_t pawns, + unsigned rule50, + unsigned castling, + unsigned ep, + bool turn, + bool useRule50, + struct TbRootMoves *results) { + Pos pos = + { + white, + black, + kings, + queens, + rooks, + bishops, + knights, + pawns, + (uint8_t)rule50, + (uint8_t)ep, + turn + }; + if (castling != 0) return 0; + return root_probe_wdl(&pos, useRule50, results); +} + +// Given a position, produce a text string of the form KQPvKRP, where +// "KQP" represents the white pieces if flip == false and the black pieces +// if flip == true. +static void prt_str(const Pos *pos, char *str, bool flip) +{ + int color = flip ? BLACK : WHITE; + + for (int pt = KING; pt >= PAWN; pt--) + for (int i = popcount(pieces_by_type(pos, (Color)color, (PieceType)pt)); i > 0; i--) + *str++ = piece_to_char[pt]; + *str++ = 'v'; + color ^= 1; + for (int pt = KING; pt >= PAWN; pt--) + for (int i = popcount(pieces_by_type(pos, (Color)color, (PieceType)pt)); i > 0; i--) + *str++ = piece_to_char[pt]; + *str++ = 0; +} + +static bool test_tb(const char *str, const char *suffix) +{ + FD fd = open_tb(str, suffix); + if (fd != FD_ERR) { + size_t size = file_size(fd); + close_tb(fd); + if ((size & 63) != 16) { + fprintf(stderr, "Incomplete tablebase file %s.%s\n", str, suffix); + printf("info string Incomplete tablebase file %s.%s\n", str, suffix); + fd = FD_ERR; + } + } + return fd != FD_ERR; +} + +static void *map_tb(const char *name, const char *suffix, map_t *mapping) +{ + FD fd = open_tb(name, suffix); + if (fd == FD_ERR) + return NULL; + + void *data = map_file(fd, mapping); + if (data == NULL) { + fprintf(stderr, "Could not map %s%s into memory.\n", name, suffix); + exit(EXIT_FAILURE); + } + + close_tb(fd); + + return data; +} + +static void add_to_hash(struct BaseEntry *ptr, uint64_t key) +{ + int idx; + + idx = key >> (64 - TB_HASHBITS); + while (tbHash[idx].ptr) + idx = (idx + 1) & ((1 << TB_HASHBITS) - 1); + + tbHash[idx].key = key; + tbHash[idx].ptr = ptr; +} + +#define pchr(i) piece_to_char[QUEEN - (i)] +#define Swap(a,b) {int tmp=a;a=b;b=tmp;} + +static void init_tb(char *str) +{ + if (!test_tb(str, tbSuffix[WDL])) + return; + + int pcs[16]; + for (int i = 0; i < 16; i++) + pcs[i] = 0; + int color = 0; + for (char *s = str; *s; s++) + if (*s == 'v') + color = 8; + else { + int piece_type = char_to_piece_type(*s); + if (piece_type) { + assert((piece_type | color) < 16); + pcs[piece_type | color]++; + } + } + + uint64_t key = calc_key_from_pcs(pcs, false); + uint64_t key2 = calc_key_from_pcs(pcs, true); + + bool hasPawns = pcs[W_PAWN] || pcs[B_PAWN]; + + struct BaseEntry *be = hasPawns ? &pawnEntry[tbNumPawn++].be + : &pieceEntry[tbNumPiece++].be; + be->hasPawns = hasPawns; + be->key = key; + be->symmetric = key == key2; + be->num = 0; + for (int i = 0; i < 16; i++) + be->num += pcs[i]; + + numWdl++; + numDtm += be->hasDtm = test_tb(str, tbSuffix[DTM]); + numDtz += be->hasDtz = test_tb(str, tbSuffix[DTZ]); + + if (be->num > TB_MaxCardinality) { + TB_MaxCardinality = be->num; + } + if (be->hasDtm) + if (be->num > TB_MaxCardinalityDTM) { + TB_MaxCardinalityDTM = be->num; + } + + for (int type = 0; type < 3; type++) + atomic_init(&be->ready[type], false); + + if (!be->hasPawns) { + int j = 0; + for (int i = 0; i < 16; i++) + if (pcs[i] == 1) j++; + be->kk_enc = j == 2; + } else { + be->pawns[0] = pcs[W_PAWN]; + be->pawns[1] = pcs[B_PAWN]; + if (pcs[B_PAWN] && (!pcs[W_PAWN] || pcs[W_PAWN] > pcs[B_PAWN])) + Swap(be->pawns[0], be->pawns[1]); + } + + add_to_hash(be, key); + if (key != key2) + add_to_hash(be, key2); +} + +#define PIECE(x) ((struct PieceEntry *)(x)) +#define PAWN(x) ((struct PawnEntry *)(x)) + +int num_tables(struct BaseEntry *be, const int type) +{ + return be->hasPawns ? type == DTM ? 6 : 4 : 1; +} + +struct EncInfo *first_ei(struct BaseEntry *be, const int type) +{ + return be->hasPawns + ? &PAWN(be)->ei[type == WDL ? 0 : type == DTM ? 8 : 20] + : &PIECE(be)->ei[type == WDL ? 0 : type == DTM ? 2 : 4]; +} + +static void free_tb_entry(struct BaseEntry *be) +{ + for (int type = 0; type < 3; type++) { + if (atomic_load_explicit(&be->ready[type], memory_order_relaxed)) { + unmap_file((void*)(be->data[type]), be->mapping[type]); + int num = num_tables(be, type); + struct EncInfo *ei = first_ei(be, type); + for (int t = 0; t < num; t++) { + free(ei[t].precomp); + if (type != DTZ) + free(ei[num + t].precomp); + } + atomic_store_explicit(&be->ready[type], false, memory_order_relaxed); + } + } +} + +bool tb_init(const char *path) +{ + if (!initialized) { + init_indices(); + king_attacks_init(); + knight_attacks_init(); + bishop_attacks_init(); + rook_attacks_init(); + pawn_attacks_init(); + initialized = 1; + } + + // if pathString is set, we need to clean up first. + if (pathString) { + free(pathString); + free(paths); + + for (int i = 0; i < tbNumPiece; i++) + free_tb_entry((struct BaseEntry *)&pieceEntry[i]); + for (int i = 0; i < tbNumPawn; i++) + free_tb_entry((struct BaseEntry *)&pawnEntry[i]); + + LOCK_DESTROY(tbMutex); + + pathString = NULL; + numWdl = numDtm = numDtz = 0; + } + + // if path is an empty string or equals "", we are done. + const char *p = path; + if (strlen(p) == 0 || !strcmp(p, "")) return true; + + pathString = (char*)malloc(strlen(p) + 1); + strcpy(pathString, p); + numPaths = 0; + for (int i = 0;; i++) { + if (pathString[i] != SEP_CHAR) + numPaths++; + while (pathString[i] && pathString[i] != SEP_CHAR) + i++; + if (!pathString[i]) break; + pathString[i] = 0; + } + paths = (char**)malloc(numPaths * sizeof(*paths)); + for (int i = 0, j = 0; i < numPaths; i++) { + while (!pathString[j]) j++; + paths[i] = &pathString[j]; + while (pathString[j]) j++; + } + + LOCK_INIT(tbMutex); + + tbNumPiece = tbNumPawn = 0; + TB_MaxCardinality = TB_MaxCardinalityDTM = 0; + TB_LARGEST = 0; + + if (!pieceEntry) { + pieceEntry = (struct PieceEntry*)malloc(TB_MAX_PIECE * sizeof(*pieceEntry)); + pawnEntry = (struct PawnEntry*)malloc(TB_MAX_PAWN * sizeof(*pawnEntry)); + if (!pieceEntry || !pawnEntry) { + fprintf(stderr, "Out of memory.\n"); + exit(EXIT_FAILURE); + } + } + + for (int i = 0; i < (1 << TB_HASHBITS); i++) { + tbHash[i].key = 0; + tbHash[i].ptr = NULL; + } + + char str[16]; + int i, j, k, l, m; + + for (i = 0; i < 5; i++) { + sprintf(str, "K%cvK", pchr(i)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) { + sprintf(str, "K%cvK%c", pchr(i), pchr(j)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) { + sprintf(str, "K%c%cvK", pchr(i), pchr(j)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = 0; k < 5; k++) { + sprintf(str, "K%c%cvK%c", pchr(i), pchr(j), pchr(k)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) { + sprintf(str, "K%c%c%cvK", pchr(i), pchr(j), pchr(k)); + init_tb(str); + } + + // 6- and 7-piece TBs make sense only with a 64-bit address space + if (sizeof(size_t) < 8 || TB_PIECES < 6) + goto finished; + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = i; k < 5; k++) + for (l = (i == k) ? j : k; l < 5; l++) { + sprintf(str, "K%c%cvK%c%c", pchr(i), pchr(j), pchr(k), pchr(l)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) + for (l = 0; l < 5; l++) { + sprintf(str, "K%c%c%cvK%c", pchr(i), pchr(j), pchr(k), pchr(l)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) + for (l = k; l < 5; l++) { + sprintf(str, "K%c%c%c%cvK", pchr(i), pchr(j), pchr(k), pchr(l)); + init_tb(str); + } + + if (TB_PIECES < 7) + goto finished; + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) + for (l = k; l < 5; l++) + for (m = l; m < 5; m++) { + sprintf(str, "K%c%c%c%c%cvK", pchr(i), pchr(j), pchr(k), pchr(l), pchr(m)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) + for (l = k; l < 5; l++) + for (m = 0; m < 5; m++) { + sprintf(str, "K%c%c%c%cvK%c", pchr(i), pchr(j), pchr(k), pchr(l), pchr(m)); + init_tb(str); + } + + for (i = 0; i < 5; i++) + for (j = i; j < 5; j++) + for (k = j; k < 5; k++) + for (l = 0; l < 5; l++) + for (m = l; m < 5; m++) { + sprintf(str, "K%c%c%cvK%c%c", pchr(i), pchr(j), pchr(k), pchr(l), pchr(m)); + init_tb(str); + } + +finished: + /* TBD - assumes UCI + printf("info string Found %d WDL, %d DTM and %d DTZ tablebase files.\n", + numWdl, numDtm, numDtz); + fflush(stdout); + */ + // Set TB_LARGEST, for backward compatibility with pre-7-man Fathom + TB_LARGEST = (unsigned)TB_MaxCardinality; + if ((unsigned)TB_MaxCardinalityDTM > TB_LARGEST) { + TB_LARGEST = TB_MaxCardinalityDTM; + } + return true; +} + +void tb_free(void) +{ + tb_init(""); + free(pieceEntry); + free(pawnEntry); +} + +static const int8_t OffDiag[] = { + 0,-1,-1,-1,-1,-1,-1,-1, + 1, 0,-1,-1,-1,-1,-1,-1, + 1, 1, 0,-1,-1,-1,-1,-1, + 1, 1, 1, 0,-1,-1,-1,-1, + 1, 1, 1, 1, 0,-1,-1,-1, + 1, 1, 1, 1, 1, 0,-1,-1, + 1, 1, 1, 1, 1, 1, 0,-1, + 1, 1, 1, 1, 1, 1, 1, 0 +}; + +static const uint8_t Triangle[] = { + 6, 0, 1, 2, 2, 1, 0, 6, + 0, 7, 3, 4, 4, 3, 7, 0, + 1, 3, 8, 5, 5, 8, 3, 1, + 2, 4, 5, 9, 9, 5, 4, 2, + 2, 4, 5, 9, 9, 5, 4, 2, + 1, 3, 8, 5, 5, 8, 3, 1, + 0, 7, 3, 4, 4, 3, 7, 0, + 6, 0, 1, 2, 2, 1, 0, 6 +}; + +static const uint8_t FlipDiag[] = { + 0, 8, 16, 24, 32, 40, 48, 56, + 1, 9, 17, 25, 33, 41, 49, 57, + 2, 10, 18, 26, 34, 42, 50, 58, + 3, 11, 19, 27, 35, 43, 51, 59, + 4, 12, 20, 28, 36, 44, 52, 60, + 5, 13, 21, 29, 37, 45, 53, 61, + 6, 14, 22, 30, 38, 46, 54, 62, + 7, 15, 23, 31, 39, 47, 55, 63 +}; + +static const uint8_t Lower[] = { + 28, 0, 1, 2, 3, 4, 5, 6, + 0, 29, 7, 8, 9, 10, 11, 12, + 1, 7, 30, 13, 14, 15, 16, 17, + 2, 8, 13, 31, 18, 19, 20, 21, + 3, 9, 14, 18, 32, 22, 23, 24, + 4, 10, 15, 19, 22, 33, 25, 26, + 5, 11, 16, 20, 23, 25, 34, 27, + 6, 12, 17, 21, 24, 26, 27, 35 +}; + +static const uint8_t Diag[] = { + 0, 0, 0, 0, 0, 0, 0, 8, + 0, 1, 0, 0, 0, 0, 9, 0, + 0, 0, 2, 0, 0, 10, 0, 0, + 0, 0, 0, 3, 11, 0, 0, 0, + 0, 0, 0, 12, 4, 0, 0, 0, + 0, 0, 13, 0, 0, 5, 0, 0, + 0, 14, 0, 0, 0, 0, 6, 0, + 15, 0, 0, 0, 0, 0, 0, 7 +}; + +static const uint8_t Flap[2][64] = { + { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 6, 12, 18, 18, 12, 6, 0, + 1, 7, 13, 19, 19, 13, 7, 1, + 2, 8, 14, 20, 20, 14, 8, 2, + 3, 9, 15, 21, 21, 15, 9, 3, + 4, 10, 16, 22, 22, 16, 10, 4, + 5, 11, 17, 23, 23, 17, 11, 5, + 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 3, 2, 1, 0, + 4, 5, 6, 7, 7, 6, 5, 4, + 8, 9, 10, 11, 11, 10, 9, 8, + 12, 13, 14, 15, 15, 14, 13, 12, + 16, 17, 18, 19, 19, 18, 17, 16, + 20, 21, 22, 23, 23, 22, 21, 20, + 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static const uint8_t PawnTwist[2][64] = { + { 0, 0, 0, 0, 0, 0, 0, 0, + 47, 35, 23, 11, 10, 22, 34, 46, + 45, 33, 21, 9, 8, 20, 32, 44, + 43, 31, 19, 7, 6, 18, 30, 42, + 41, 29, 17, 5, 4, 16, 28, 40, + 39, 27, 15, 3, 2, 14, 26, 38, + 37, 25, 13, 1, 0, 12, 24, 36, + 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, + 47, 45, 43, 41, 40, 42, 44, 46, + 39, 37, 35, 33, 32, 34, 36, 38, + 31, 29, 27, 25, 24, 26, 28, 30, + 23, 21, 19, 17, 16, 18, 20, 22, + 15, 13, 11, 9, 8, 10, 12, 14, + 7, 5, 3, 1, 0, 2, 4, 6, + 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +static const int16_t KKIdx[10][64] = { + { -1, -1, -1, 0, 1, 2, 3, 4, + -1, -1, -1, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57 }, + { 58, -1, -1, -1, 59, 60, 61, 62, + 63, -1, -1, -1, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, + 100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115}, + {116,117, -1, -1, -1,118,119,120, + 121,122, -1, -1, -1,123,124,125, + 126,127,128,129,130,131,132,133, + 134,135,136,137,138,139,140,141, + 142,143,144,145,146,147,148,149, + 150,151,152,153,154,155,156,157, + 158,159,160,161,162,163,164,165, + 166,167,168,169,170,171,172,173 }, + {174, -1, -1, -1,175,176,177,178, + 179, -1, -1, -1,180,181,182,183, + 184, -1, -1, -1,185,186,187,188, + 189,190,191,192,193,194,195,196, + 197,198,199,200,201,202,203,204, + 205,206,207,208,209,210,211,212, + 213,214,215,216,217,218,219,220, + 221,222,223,224,225,226,227,228 }, + {229,230, -1, -1, -1,231,232,233, + 234,235, -1, -1, -1,236,237,238, + 239,240, -1, -1, -1,241,242,243, + 244,245,246,247,248,249,250,251, + 252,253,254,255,256,257,258,259, + 260,261,262,263,264,265,266,267, + 268,269,270,271,272,273,274,275, + 276,277,278,279,280,281,282,283 }, + {284,285,286,287,288,289,290,291, + 292,293, -1, -1, -1,294,295,296, + 297,298, -1, -1, -1,299,300,301, + 302,303, -1, -1, -1,304,305,306, + 307,308,309,310,311,312,313,314, + 315,316,317,318,319,320,321,322, + 323,324,325,326,327,328,329,330, + 331,332,333,334,335,336,337,338 }, + { -1, -1,339,340,341,342,343,344, + -1, -1,345,346,347,348,349,350, + -1, -1,441,351,352,353,354,355, + -1, -1, -1,442,356,357,358,359, + -1, -1, -1, -1,443,360,361,362, + -1, -1, -1, -1, -1,444,363,364, + -1, -1, -1, -1, -1, -1,445,365, + -1, -1, -1, -1, -1, -1, -1,446 }, + { -1, -1, -1,366,367,368,369,370, + -1, -1, -1,371,372,373,374,375, + -1, -1, -1,376,377,378,379,380, + -1, -1, -1,447,381,382,383,384, + -1, -1, -1, -1,448,385,386,387, + -1, -1, -1, -1, -1,449,388,389, + -1, -1, -1, -1, -1, -1,450,390, + -1, -1, -1, -1, -1, -1, -1,451 }, + {452,391,392,393,394,395,396,397, + -1, -1, -1, -1,398,399,400,401, + -1, -1, -1, -1,402,403,404,405, + -1, -1, -1, -1,406,407,408,409, + -1, -1, -1, -1,453,410,411,412, + -1, -1, -1, -1, -1,454,413,414, + -1, -1, -1, -1, -1, -1,455,415, + -1, -1, -1, -1, -1, -1, -1,456 }, + {457,416,417,418,419,420,421,422, + -1,458,423,424,425,426,427,428, + -1, -1, -1, -1, -1,429,430,431, + -1, -1, -1, -1, -1,432,433,434, + -1, -1, -1, -1, -1,435,436,437, + -1, -1, -1, -1, -1,459,438,439, + -1, -1, -1, -1, -1, -1,460,440, + -1, -1, -1, -1, -1, -1, -1,461 } +}; + +static const uint8_t FileToFile[] = { 0, 1, 2, 3, 3, 2, 1, 0 }; +static const int WdlToMap[5] = { 1, 3, 0, 2, 0 }; +static const uint8_t PAFlags[5] = { 8, 0, 0, 0, 4 }; + +static size_t Binomial[7][64]; +static size_t PawnIdx[2][6][24]; +static size_t PawnFactorFile[6][4]; +static size_t PawnFactorRank[6][6]; + +static void init_indices(void) +{ + int i, j, k; + + // Binomial[k][n] = Bin(n, k) + for (i = 0; i < 7; i++) + for (j = 0; j < 64; j++) { + size_t f = 1; + size_t l = 1; + for (k = 0; k < i; k++) { + f *= (j - k); + l *= (k + 1); + } + Binomial[i][j] = f / l; + } + + for (i = 0; i < 6; i++) { + size_t s = 0; + for (j = 0; j < 24; j++) { + PawnIdx[0][i][j] = s; + s += Binomial[i][PawnTwist[0][(1 + (j % 6)) * 8 + (j / 6)]]; + if ((j + 1) % 6 == 0) { + PawnFactorFile[i][j / 6] = s; + s = 0; + } + } + } + + for (i = 0; i < 6; i++) { + size_t s = 0; + for (j = 0; j < 24; j++) { + PawnIdx[1][i][j] = s; + s += Binomial[i][PawnTwist[1][(1 + (j / 4)) * 8 + (j % 4)]]; + if ((j + 1) % 4 == 0) { + PawnFactorRank[i][j / 4] = s; + s = 0; + } + } + } +} + +int leading_pawn(int *p, struct BaseEntry *be, const int enc) +{ + for (int i = 1; i < be->pawns[0]; i++) + if (Flap[enc-1][p[0]] > Flap[enc-1][p[i]]) + Swap(p[0], p[i]); + + return enc == FILE_ENC ? FileToFile[p[0] & 7] : (p[0] - 8) >> 3; +} + +size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, + const int enc) +{ + int n = be->num; + size_t idx; + int k; + + if (p[0] & 0x04) + for (int i = 0; i < n; i++) + p[i] ^= 0x07; + + if (enc == PIECE_ENC) { + if (p[0] & 0x20) + for (int i = 0; i < n; i++) + p[i] ^= 0x38; + + for (int i = 0; i < n; i++) + if (OffDiag[p[i]]) { + if (OffDiag[p[i]] > 0 && i < (be->kk_enc ? 2 : 3)) + for (int j = 0; j < n; j++) + p[j] = FlipDiag[p[j]]; + break; + } + + if (be->kk_enc) { + idx = KKIdx[Triangle[p[0]]][p[1]]; + k = 2; + } else { + int s1 = (p[1] > p[0]); + int s2 = (p[2] > p[0]) + (p[2] > p[1]); + + if (OffDiag[p[0]]) + idx = Triangle[p[0]] * 63*62 + (p[1] - s1) * 62 + (p[2] - s2); + else if (OffDiag[p[1]]) + idx = 6*63*62 + Diag[p[0]] * 28*62 + Lower[p[1]] * 62 + p[2] - s2; + else if (OffDiag[p[2]]) + idx = 6*63*62 + 4*28*62 + Diag[p[0]] * 7*28 + (Diag[p[1]] - s1) * 28 + Lower[p[2]]; + else + idx = 6*63*62 + 4*28*62 + 4*7*28 + Diag[p[0]] * 7*6 + (Diag[p[1]] - s1) * 6 + (Diag[p[2]] - s2); + k = 3; + } + idx *= ei->factor[0]; + } else { + for (int i = 1; i < be->pawns[0]; i++) + for (int j = i + 1; j < be->pawns[0]; j++) + if (PawnTwist[enc-1][p[i]] < PawnTwist[enc-1][p[j]]) + Swap(p[i], p[j]); + + k = be->pawns[0]; + idx = PawnIdx[enc-1][k-1][Flap[enc-1][p[0]]]; + for (int i = 1; i < k; i++) + idx += Binomial[k-i][PawnTwist[enc-1][p[i]]]; + idx *= ei->factor[0]; + + // Pawns of other color + if (be->pawns[1]) { + int t = k + be->pawns[1]; + for (int i = k; i < t; i++) + for (int j = i + 1; j < t; j++) + if (p[i] > p[j]) Swap(p[i], p[j]); + size_t s = 0; + for (int i = k; i < t; i++) { + int sq = p[i]; + int skips = 0; + for (int j = 0; j < k; j++) + skips += (sq > p[j]); + s += Binomial[i - k + 1][sq - skips - 8]; + } + idx += s * ei->factor[k]; + k = t; + } + } + + for (; k < n;) { + int t = k + ei->norm[k]; + for (int i = k; i < t; i++) + for (int j = i + 1; j < t; j++) + if (p[i] > p[j]) Swap(p[i], p[j]); + size_t s = 0; + for (int i = k; i < t; i++) { + int sq = p[i]; + int skips = 0; + for (int j = 0; j < k; j++) + skips += (sq > p[j]); + s += Binomial[i - k + 1][sq - skips]; + } + idx += s * ei->factor[k]; + k = t; + } + + return idx; +} + +static size_t encode_piece(int *p, struct EncInfo *ei, struct BaseEntry *be) +{ + return encode(p, ei, be, PIECE_ENC); +} + +static size_t encode_pawn_f(int *p, struct EncInfo *ei, struct BaseEntry *be) +{ + return encode(p, ei, be, FILE_ENC); +} + +static size_t encode_pawn_r(int *p, struct EncInfo *ei, struct BaseEntry *be) +{ + return encode(p, ei, be, RANK_ENC); +} + +// Count number of placements of k like pieces on n squares +static size_t subfactor(size_t k, size_t n) +{ + size_t f = n; + size_t l = 1; + for (size_t i = 1; i < k; i++) { + f *= n - i; + l *= i + 1; + } + + return f / l; +} + +static size_t init_enc_info(struct EncInfo *ei, struct BaseEntry *be, + uint8_t *tb, int shift, int t, const int enc) +{ + bool morePawns = enc != PIECE_ENC && be->pawns[1] > 0; + + for (int i = 0; i < be->num; i++) { + ei->pieces[i] = (tb[i + 1 + morePawns] >> shift) & 0x0f; + ei->norm[i] = 0; + } + + int order = (tb[0] >> shift) & 0x0f; + int order2 = morePawns ? (tb[1] >> shift) & 0x0f : 0x0f; + + int k = ei->norm[0] = enc != PIECE_ENC ? be->pawns[0] + : be->kk_enc ? 2 : 3; + + if (morePawns) { + ei->norm[k] = be->pawns[1]; + k += ei->norm[k]; + } + + for (int i = k; i < be->num; i += ei->norm[i]) + for (int j = i; j < be->num && ei->pieces[j] == ei->pieces[i]; j++) + ei->norm[i]++; + + int n = 64 - k; + size_t f = 1; + + for (int i = 0; k < be->num || i == order || i == order2; i++) { + if (i == order) { + ei->factor[0] = f; + f *= enc == FILE_ENC ? PawnFactorFile[ei->norm[0] - 1][t] + : enc == RANK_ENC ? PawnFactorRank[ei->norm[0] - 1][t] + : be->kk_enc ? 462 : 31332; + } else if (i == order2) { + ei->factor[ei->norm[0]] = f; + f *= subfactor(ei->norm[ei->norm[0]], 48 - ei->norm[0]); + } else { + ei->factor[k] = f; + f *= subfactor(ei->norm[k], n); + n -= ei->norm[k]; + k += ei->norm[k]; + } + } + + return f; +} + +static void calc_symLen(struct PairsData *d, uint32_t s, char *tmp) +{ + uint8_t *w = d->symPat + 3 * s; + uint32_t s2 = (w[2] << 4) | (w[1] >> 4); + if (s2 == 0x0fff) + d->symLen[s] = 0; + else { + uint32_t s1 = ((w[1] & 0xf) << 8) | w[0]; + if (!tmp[s1]) calc_symLen(d, s1, tmp); + if (!tmp[s2]) calc_symLen(d, s2, tmp); + d->symLen[s] = d->symLen[s1] + d->symLen[s2] + 1; + } + tmp[s] = 1; +} + +static struct PairsData *setup_pairs(uint8_t **ptr, size_t tb_size, + size_t *size, uint8_t *flags, int type) +{ + struct PairsData *d; + uint8_t *data = *ptr; + + *flags = data[0]; + if (data[0] & 0x80) { + d = (struct PairsData*)malloc(sizeof(struct PairsData)); + d->idxBits = 0; + d->constValue[0] = type == WDL ? data[1] : 0; + d->constValue[1] = 0; + *ptr = data + 2; + size[0] = size[1] = size[2] = 0; + return d; + } + + uint8_t blockSize = data[1]; + uint8_t idxBits = data[2]; + uint32_t realNumBlocks = read_le_u32(data+4); + uint32_t numBlocks = realNumBlocks + data[3]; + int maxLen = data[8]; + int minLen = data[9]; + int h = maxLen - minLen + 1; + uint32_t numSyms = (uint32_t)read_le_u16(data + 10 + 2 * h); + d = (struct PairsData*)malloc(sizeof(struct PairsData) + h * sizeof(uint64_t) + numSyms); + d->blockSize = blockSize; + d->idxBits = idxBits; + d->offset = (uint16_t *)(&data[10]); + d->symLen = (uint8_t *)d + sizeof(struct PairsData) + h * sizeof(uint64_t); + d->symPat = &data[12 + 2 * h]; + d->minLen = minLen; + *ptr = &data[12 + 2 * h + 3 * numSyms + (numSyms & 1)]; + + size_t num_indices = (tb_size + (1ULL << idxBits) - 1) >> idxBits; + size[0] = 6ULL * num_indices; + size[1] = 2ULL * numBlocks; + size[2] = (size_t)realNumBlocks << blockSize; + + assert(numSyms < TB_MAX_SYMS); + char tmp[TB_MAX_SYMS]; + memset(tmp, 0, numSyms); + for (uint32_t s = 0; s < numSyms; s++) + if (!tmp[s]) + calc_symLen(d, s, tmp); + + d->base[h - 1] = 0; + for (int i = h - 2; i >= 0; i--) + d->base[i] = (d->base[i + 1] + read_le_u16((uint8_t *)(d->offset + i)) - read_le_u16((uint8_t *)(d->offset + i + 1))) / 2; +#ifdef DECOMP64 + for (int i = 0; i < h; i++) + d->base[i] <<= 64 - (minLen + i); +#else + for (int i = 0; i < h; i++) + d->base[i] <<= 32 - (minLen + i); +#endif + d->offset -= d->minLen; + + return d; +} + +static bool init_table(struct BaseEntry *be, const char *str, int type) +{ + uint8_t *data = (uint8_t*)map_tb(str, tbSuffix[type], &be->mapping[type]); + if (!data) return false; + + if (read_le_u32(data) != tbMagic[type]) { + fprintf(stderr, "Corrupted table.\n"); + unmap_file((void*)data, be->mapping[type]); + return false; + } + + be->data[type] = data; + + bool split = type != DTZ && (data[4] & 0x01); + if (type == DTM) + be->dtmLossOnly = data[4] & 0x04; + + data += 5; + + size_t tb_size[6][2]; + int num = num_tables(be, type); + struct EncInfo *ei = first_ei(be, type); + int enc = !be->hasPawns ? PIECE_ENC : type != DTM ? FILE_ENC : RANK_ENC; + + for (int t = 0; t < num; t++) { + tb_size[t][0] = init_enc_info(&ei[t], be, data, 0, t, enc); + if (split) + tb_size[t][1] = init_enc_info(&ei[num + t], be, data, 4, t, enc); + data += be->num + 1 + (be->hasPawns && be->pawns[1]); + } + data += (uintptr_t)data & 1; + + size_t size[6][2][3]; + for (int t = 0; t < num; t++) { + uint8_t flags; + ei[t].precomp = setup_pairs(&data, tb_size[t][0], size[t][0], &flags, type); + if (type == DTZ) { + if (!be->hasPawns) + PIECE(be)->dtzFlags = flags; + else + PAWN(be)->dtzFlags[t] = flags; + } + if (split) + ei[num + t].precomp = setup_pairs(&data, tb_size[t][1], size[t][1], &flags, type); + else if (type != DTZ) + ei[num + t].precomp = NULL; + } + + if (type == DTM && !be->dtmLossOnly) { + uint16_t *map = (uint16_t *)data; + *(be->hasPawns ? &PAWN(be)->dtmMap : &PIECE(be)->dtmMap) = map; + uint16_t (*mapIdx)[2][2] = be->hasPawns ? &PAWN(be)->dtmMapIdx[0] + : &PIECE(be)->dtmMapIdx; + for (int t = 0; t < num; t++) { + for (int i = 0; i < 2; i++) { + mapIdx[t][0][i] = (uint16_t)(data + 1 - (uint8_t*)map); + data += 2 + 2 * read_le_u16(data); + } + if (split) { + for (int i = 0; i < 2; i++) { + mapIdx[t][1][i] = (uint16_t)(data + 1 - (uint8_t*)map); + data += 2 + 2 * read_le_u16(data); + } + } + } + } + + if (type == DTZ) { + void *map = data; + *(be->hasPawns ? &PAWN(be)->dtzMap : &PIECE(be)->dtzMap) = map; + uint16_t (*mapIdx)[4] = be->hasPawns ? &PAWN(be)->dtzMapIdx[0] + : &PIECE(be)->dtzMapIdx; + uint8_t *flags = be->hasPawns ? &PAWN(be)->dtzFlags[0] + : &PIECE(be)->dtzFlags; + for (int t = 0; t < num; t++) { + if (flags[t] & 2) { + if (!(flags[t] & 16)) { + for (int i = 0; i < 4; i++) { + mapIdx[t][i] = (uint16_t)(data + 1 - (uint8_t *)map); + data += 1 + data[0]; + } + } else { + data += (uintptr_t)data & 0x01; + for (int i = 0; i < 4; i++) { + mapIdx[t][i] = (uint16_t)(data + 1 - (uint8_t *)map); + data += 2 + 2 * read_le_u16(data); + } + } + } + } + data += (uintptr_t)data & 0x01; + } + + for (int t = 0; t < num; t++) { + ei[t].precomp->indexTable = data; + data += size[t][0][0]; + if (split) { + ei[num + t].precomp->indexTable = data; + data += size[t][1][0]; + } + } + + for (int t = 0; t < num; t++) { + ei[t].precomp->sizeTable = (uint16_t *)data; + data += size[t][0][1]; + if (split) { + ei[num + t].precomp->sizeTable = (uint16_t *)data; + data += size[t][1][1]; + } + } + + for (int t = 0; t < num; t++) { + data = (uint8_t *)(((uintptr_t)data + 0x3f) & ~0x3f); + ei[t].precomp->data = data; + data += size[t][0][2]; + if (split) { + data = (uint8_t *)(((uintptr_t)data + 0x3f) & ~0x3f); + ei[num + t].precomp->data = data; + data += size[t][1][2]; + } + } + + if (type == DTM && be->hasPawns) + PAWN(be)->dtmSwitched = + calc_key_from_pieces(ei[0].pieces, be->num) != be->key; + + return true; +} + +static uint8_t *decompress_pairs(struct PairsData *d, size_t idx) +{ + if (!d->idxBits) + return d->constValue; + + uint32_t mainIdx = (uint32_t)(idx >> d->idxBits); + int litIdx = (idx & (((size_t)1 << d->idxBits) - 1)) - ((size_t)1 << (d->idxBits - 1)); + uint32_t block; + memcpy(&block, d->indexTable + 6 * mainIdx, sizeof(block)); + block = from_le_u32(block); + + uint16_t idxOffset = *(uint16_t *)(d->indexTable + 6 * mainIdx + 4); + litIdx += from_le_u16(idxOffset); + + if (litIdx < 0) + while (litIdx < 0) + litIdx += d->sizeTable[--block] + 1; + else + while (litIdx > d->sizeTable[block]) + litIdx -= d->sizeTable[block++] + 1; + + uint32_t *ptr = (uint32_t *)(d->data + ((size_t)block << d->blockSize)); + + int m = d->minLen; + uint16_t *offset = d->offset; + uint64_t *base = d->base - m; + uint8_t *symLen = d->symLen; + uint32_t sym, bitCnt; + +#ifdef DECOMP64 + uint64_t code = from_be_u64(*(uint64_t *)ptr); + + ptr += 2; + bitCnt = 0; // number of "empty bits" in code + for (;;) { + int l = m; + while (code < base[l]) l++; + sym = from_le_u16(offset[l]); + sym += (uint32_t)((code - base[l]) >> (64 - l)); + if (litIdx < (int)symLen[sym] + 1) break; + litIdx -= (int)symLen[sym] + 1; + code <<= l; + bitCnt += l; + if (bitCnt >= 32) { + bitCnt -= 32; + uint32_t tmp = from_be_u32(*ptr++); + code |= (uint64_t)tmp << bitCnt; + } + } +#else + uint32_t next = 0; + uint32_t data = *ptr++; + uint32_t code = from_be_u32(data); + bitCnt = 0; // number of bits in next + for (;;) { + int l = m; + while (code < base[l]) l++; + sym = offset[l] + ((code - base[l]) >> (32 - l)); + if (litIdx < (int)symLen[sym] + 1) break; + litIdx -= (int)symLen[sym] + 1; + code <<= l; + if (bitCnt < l) { + if (bitCnt) { + code |= (next >> (32 - l)); + l -= bitCnt; + } + data = *ptr++; + next = from_be_u32(data); + bitCnt = 32; + } + code |= (next >> (32 - l)); + next <<= l; + bitCnt -= l; + } +#endif + uint8_t *symPat = d->symPat; + while (symLen[sym] != 0) { + uint8_t *w = symPat + (3 * sym); + int s1 = ((w[1] & 0xf) << 8) | w[0]; + if (litIdx < (int)symLen[s1] + 1) + sym = s1; + else { + litIdx -= (int)symLen[s1] + 1; + sym = (w[2] << 4) | (w[1] >> 4); + } + } + + return &symPat[3 * sym]; +} + +// p[i] is to contain the square 0-63 (A1-H8) for a piece of type +// pc[i] ^ flip, where 1 = white pawn, ..., 14 = black king and pc ^ flip +// flips between white and black if flip == true. +// Pieces of the same type are guaranteed to be consecutive. +inline static int fill_squares(const Pos *pos, uint8_t *pc, bool flip, int mirror, int *p, + int i) +{ + Color color = ColorOfPiece(pc[i]); + if (flip) color = (Color)(!(int)color); + uint64_t bb = pieces_by_type(pos, color, TypeOfPiece(pc[i])); + unsigned sq; + do { + sq = lsb(bb); + p[i++] = sq ^ mirror; + bb = poplsb(bb); + } while (bb); + return i; +} + +int probe_table(const Pos *pos, int s, int *success, const int type) +{ + // Obtain the position's material-signature key + uint64_t key = calc_key(pos,false); + + // Test for KvK + // Note: Cfish has key == 2ULL for KvK but we have 0 + if (type == WDL && key == 0ULL) + return 0; + + int hashIdx = key >> (64 - TB_HASHBITS); + while (tbHash[hashIdx].key && tbHash[hashIdx].key != key) + hashIdx = (hashIdx + 1) & ((1 << TB_HASHBITS) - 1); + if (!tbHash[hashIdx].ptr) { + *success = 0; + return 0; + } + + struct BaseEntry *be = tbHash[hashIdx].ptr; + if ((type == DTM && !be->hasDtm) || (type == DTZ && !be->hasDtz)) { + *success = 0; + return 0; + } + + // Use double-checked locking to reduce locking overhead + if (!atomic_load_explicit(&be->ready[type], memory_order_acquire)) { + LOCK(tbMutex); + if (!atomic_load_explicit(&be->ready[type], memory_order_relaxed)) { + char str[16]; + prt_str(pos, str, be->key != key); + if (!init_table(be, str, type)) { + tbHash[hashIdx].ptr = NULL; // mark as deleted + *success = 0; + UNLOCK(tbMutex); + return 0; + } + atomic_store_explicit(&be->ready[type], true, memory_order_release); + } + UNLOCK(tbMutex); + } + + bool bside, flip; + if (!be->symmetric) { + flip = key != be->key; + bside = (pos->turn == WHITE) == flip; + if (type == DTM && be->hasPawns && PAWN(be)->dtmSwitched) { + flip = !flip; + bside = !bside; + } + } else { + flip = pos->turn != WHITE; + bside = false; + } + + struct EncInfo *ei = first_ei(be, type); + int p[TB_PIECES]; + size_t idx; + int t = 0; + uint8_t flags = 0; // initialize to fix GCC warning + + if (!be->hasPawns) { + if (type == DTZ) { + flags = PIECE(be)->dtzFlags; + if ((flags & 1) != bside && !be->symmetric) { + *success = -1; + return 0; + } + } + ei = type != DTZ ? &ei[bside] : ei; + for (int i = 0; i < be->num;) + i = fill_squares(pos, ei->pieces, flip, 0, p, i); + idx = encode_piece(p, ei, be); + } else { + int i = fill_squares(pos, ei->pieces, flip, flip ? 0x38 : 0, p, 0); + t = leading_pawn(p, be, type != DTM ? FILE_ENC : RANK_ENC); + if (type == DTZ) { + flags = PAWN(be)->dtzFlags[t]; + if ((flags & 1) != bside && !be->symmetric) { + *success = -1; + return 0; + } + } + ei = type == WDL ? &ei[t + 4 * bside] + : type == DTM ? &ei[t + 6 * bside] : &ei[t]; + while (i < be->num) + i = fill_squares(pos, ei->pieces, flip, flip ? 0x38 : 0, p, i); + idx = type != DTM ? encode_pawn_f(p, ei, be) : encode_pawn_r(p, ei, be); + } + + uint8_t *w = decompress_pairs(ei->precomp, idx); + + if (type == WDL) + return (int)w[0] - 2; + + int v = w[0] + ((w[1] & 0x0f) << 8); + + if (type == DTM) { + if (!be->dtmLossOnly) + v = (int)from_le_u16(be->hasPawns + ? PAWN(be)->dtmMap[PAWN(be)->dtmMapIdx[t][bside][s] + v] + : PIECE(be)->dtmMap[PIECE(be)->dtmMapIdx[bside][s] + v]); + } else { + if (flags & 2) { + int m = WdlToMap[s + 2]; + if (!(flags & 16)) + v = be->hasPawns + ? ((uint8_t *)PAWN(be)->dtzMap)[PAWN(be)->dtzMapIdx[t][m] + v] + : ((uint8_t *)PIECE(be)->dtzMap)[PIECE(be)->dtzMapIdx[m] + v]; + else + v = (int)from_le_u16(be->hasPawns + ? ((uint16_t *)PAWN(be)->dtzMap)[PAWN(be)->dtzMapIdx[t][m] + v] + : ((uint16_t *)PIECE(be)->dtzMap)[PIECE(be)->dtzMapIdx[m] + v]); + } + if (!(flags & PAFlags[s + 2]) || (s & 1)) + v *= 2; + } + + return v; +} + +static int probe_wdl_table(const Pos *pos, int *success) +{ + return probe_table(pos, 0, success, WDL); +} + +static int probe_dtm_table(const Pos *pos, int won, int *success) +{ + return probe_table(pos, won, success, DTM); +} + +static int probe_dtz_table(const Pos *pos, int wdl, int *success) +{ + return probe_table(pos, wdl, success, DTZ); +} + +// probe_ab() is not called for positions with en passant captures. +static int probe_ab(const Pos *pos, int alpha, int beta, int *success) +{ + assert(pos->ep == 0); + + TbMove moves0[TB_MAX_CAPTURES]; + TbMove *m = moves0; + // Generate (at least) all legal captures including (under)promotions. + // It is OK to generate more, as long as they are filtered out below. + TbMove *end = gen_captures(pos, m); + for (; m < end; m++) { + Pos pos1; + TbMove move = *m; + if (!is_capture(pos, move)) + continue; + if (!do_move(&pos1, pos, move)) + continue; // illegal move + int v = -probe_ab(&pos1, -beta, -alpha, success); + if (*success == 0) return 0; + if (v > alpha) { + if (v >= beta) + return v; + alpha = v; + } + } + + int v = probe_wdl_table(pos, success); + + return alpha >= v ? alpha : v; +} + +// Probe the WDL table for a particular position. +// +// If *success != 0, the probe was successful. +// +// If *success == 2, the position has a winning capture, or the position +// is a cursed win and has a cursed winning capture, or the position +// has an ep capture as only best move. +// This is used in probe_dtz(). +// +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +int probe_wdl(Pos *pos, int *success) +{ + *success = 1; + + // Generate (at least) all legal captures including (under)promotions. + TbMove moves0[TB_MAX_CAPTURES]; + TbMove *m = moves0; + TbMove *end = gen_captures(pos, m); + int bestCap = -3, bestEp = -3; + + // We do capture resolution, letting bestCap keep track of the best + // capture without ep rights and letting bestEp keep track of still + // better ep captures if they exist. + + for (; m < end; m++) { + Pos pos1; + TbMove move = *m; + if (!is_capture(pos, move)) + continue; + if (!do_move(&pos1, pos, move)) + continue; // illegal move + int v = -probe_ab(&pos1, -2, -bestCap, success); + if (*success == 0) return 0; + if (v > bestCap) { + if (v == 2) { + *success = 2; + return 2; + } + if (!is_en_passant(pos,move)) + bestCap = v; + else if (v > bestEp) + bestEp = v; + } + } + + int v = probe_wdl_table(pos, success); + if (*success == 0) return 0; + + // Now max(v, bestCap) is the WDL value of the position without ep rights. + // If the position without ep rights is not stalemate or no ep captures + // exist, then the value of the position is max(v, bestCap, bestEp). + // If the position without ep rights is stalemate and bestEp > -3, + // then the value of the position is bestEp (and we will have v == 0). + + if (bestEp > bestCap) { + if (bestEp > v) { // ep capture (possibly cursed losing) is best. + *success = 2; + return bestEp; + } + bestCap = bestEp; + } + + // Now max(v, bestCap) is the WDL value of the position unless + // the position without ep rights is stalemate and bestEp > -3. + + if (bestCap >= v) { + // No need to test for the stalemate case here: either there are + // non-ep captures, or bestCap == bestEp >= v anyway. + *success = 1 + (bestCap > 0); + return bestCap; + } + + // Now handle the stalemate case. + if (bestEp > -3 && v == 0) { + TbMove moves[TB_MAX_MOVES]; + TbMove *endMove = gen_moves(pos, moves); + // Check for stalemate in the position with ep captures. + for (m = moves; m < endMove; m++) { + if (!is_en_passant(pos,*m) && legal_move(pos, *m)) break; + } + if (m == endMove && !is_check(pos)) { + // stalemate score from tb (w/o e.p.), but an en-passant capture + // is possible. + *success = 2; + return bestEp; + } + } + // Stalemate / en passant not an issue, so v is the correct value. + + return v; +} + +#if 0 +// This will not be called for positions with en passant captures +static Value probe_dtm_dc(const Pos *pos, int won, int *success) +{ + assert(ep_square() == 0); + + Value v, bestCap = -TB_VALUE_INFINITE; + + TbMove moves0[TB_MAX_CAPTURES]; + TbMove *end, *m = moves0; + + // Generate at least all legal captures including (under)promotions + end = gen_captures(pos, m); + Pos pos1; + for (; m < end; m++) { + TbMove move = m->move; + if (!is_capture(pos, move)) + continue; + if (!do_move(&pos1, pos, move)) + continue; + if (!won) + v = -probe_dtm_dc(&pos1, 1, success) + 1; + else if (probe_ab(&pos1, -1, 0, success) < 0 && *success) + v = -probe_dtm_dc(&pos1, 0, success) - 1; + else + v = -TB_VALUE_INFINITE; + bestCap = max(bestCap,v); + if (*success == 0) return 0; + } + + int dtm = probe_dtm_table(pos, won, success); + v = won ? TB_VALUE_MATE - 2 * dtm + 1 : -TB_VALUE_MATE + 2 * dtm; + + return max(bestCap,v); +} +#endif + +static Value probe_dtm_win(const Pos *pos, int *success); + +// Probe a position known to lose by probing the DTM table and looking +// at captures. +static Value probe_dtm_loss(const Pos *pos, int *success) +{ + Value v, best = -TB_VALUE_INFINITE, numEp = 0; + + TbMove moves0[TB_MAX_CAPTURES]; + // Generate at least all legal captures including (under)promotions + TbMove *end, *m = moves0; + end = gen_captures(pos, m); + + Pos pos1; + for (; m < end; m++) { + TbMove move = *m; + if (!is_capture(pos, move) || !legal_move(pos, move)) + continue; + if (is_en_passant(pos, move)) + numEp++; + do_move(&pos1, pos, move); + v = -probe_dtm_win(&pos1, success) + 1; + if (v > best) { + best = v; + } + if (*success == 0) + return 0; + } + + // If there are en passant captures, the position without ep rights + // may be a stalemate. If it is, we must avoid probing the DTM table. + if (numEp != 0 && gen_legal(pos, m) == m + numEp) + return best; + + v = -TB_VALUE_MATE + 2 * probe_dtm_table(pos, 0, success); + return best > v ? best : v; +} + +static Value probe_dtm_win(const Pos *pos, int *success) +{ + Value v, best = -TB_VALUE_INFINITE; + + // Generate all moves + TbMove moves0[TB_MAX_CAPTURES]; + TbMove *m = moves0; + TbMove *end = gen_moves(pos, m); + // Perform a 1-ply search + Pos pos1; + for (; m < end; m++) { + TbMove move = *m; + if (do_move(&pos1, pos, move)) { + // not legal + continue; + } + if ((pos1.ep > 0 ? probe_wdl(&pos1, success) + : probe_ab(&pos1, -1, 0, success)) < 0 + && *success) + v = -probe_dtm_loss(&pos1, success) - 1; + else + v = -TB_VALUE_INFINITE; + if (v > best) { + best = v; + } + if (*success == 0) return 0; + } + + return best; +} + +//Value TB_probe_dtm(const Pos *pos, int wdl, int *success) +//{ +// assert(wdl != 0); +// +// *success = 1; +// +// return wdl > 0 ? probe_dtm_win(pos, success) +// : probe_dtm_loss(pos, success); +//} + +#if 0 +// To be called only for non-drawn positions. +Value TB_probe_dtm2(const Pos *pos, int wdl, int *success) +{ + assert(wdl != 0); + + *success = 1; + Value v, bestCap = -TB_VALUE_INFINITE, bestEp = -TB_VALUE_INFINITE; + + TbMove moves0[TB_MAX_CAPTURES]; + TbMove *end, *m = moves0; + + // Generate at least all legal captures including (under)promotions + end = gen_captures(pos, m); + Pos pos0 = *pos; + + // Resolve captures, letting bestCap keep track of the best non-ep + // capture and letting bestEp keep track of the best ep capture. + Pos pos1; + for (; m < end; m++) { + TbMove move = *m; + if (!is_capture(pos, move)) + continue; + if (!do_move(&pos1, pos, move)) + continue; + if (wdl < 0) + v = -probe_dtm_dc(&pos1, 1, success) + 1; + else if (probe_ab(&pos1, -1, 0, success) < 0 && *success) + v = -probe_dtm_dc(&pos1, 0, success) - 1; + else + v = -TB_VALUE_MATE; + if (is_en_passant(&pos1, move)) + bestEp = max(bestEp,v); + else + bestCap = max(bestCap,v); + if (*success == 0) + return 0; + } + + // If there are en passant captures, we have to determine the WDL value + // for the position without ep rights if it might be different. + if (bestEp > -TB_VALUE_INFINITE && (bestEp < 0 || bestCap < 0)) { + assert(ep_square() != 0); + uint8_t s = pos->st->epSquare; + pos->st->epSquare = 0; + wdl = probe_ab(pos, -2, 2, success); + pos->st->epSquare = s; + if (*success == 0) + return 0; + if (wdl == 0) + return bestEp; + } + + bestCap = max(bestCap,v); + int dtm = probe_dtm_table(pos, wdl > 0, success); + v = wdl > 0 ? TB_VALUE_MATE - 2 * dtm + 1 : -TB_VALUE_MATE + 2 * dtm; + return max(bestCap,v); +} +#endif + +static int WdlToDtz[] = { -1, -101, 0, 101, 1 }; + +// Probe the DTZ table for a particular position. +// If *success != 0, the probe was successful. +// The return value is from the point of view of the side to move: +// n < -100 : loss, but draw under 50-move rule +// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) +// 0 : draw +// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) +// 100 < n : win, but draw under 50-move rule +// +// If the position mate, -1 is returned instead of 0. +// +// The return value n can be off by 1: a return value -n can mean a loss +// in n+1 ply and a return value +n can mean a win in n+1 ply. This +// cannot happen for tables with positions exactly on the "edge" of +// the 50-move rule. +// +// This means that if dtz > 0 is returned, the position is certainly +// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine +// picks moves that preserve dtz + 50-move-counter <= 99. +// +// If n = 100 immediately after a capture or pawn move, then the position +// is also certainly a win, and during the whole phase until the next +// capture or pawn move, the inequality to be preserved is +// dtz + 50-movecounter <= 100. +// +// In short, if a move is available resulting in dtz + 50-move-counter <= 99, +// then do not accept moves leading to dtz + 50-move-counter == 100. +// +int probe_dtz(Pos *pos, int *success) +{ + int wdl = probe_wdl(pos, success); + if (*success == 0) return 0; + + // If draw, then dtz = 0. + if (wdl == 0) return 0; + + // Check for winning capture or en passant capture as only best move. + if (*success == 2) + return WdlToDtz[wdl + 2]; + + TbMove moves[TB_MAX_MOVES]; + TbMove *m = moves, *end = NULL; + Pos pos1; + + // If winning, check for a winning pawn move. + if (wdl > 0) { + // Generate at least all legal non-capturing pawn moves + // including non-capturing promotions. + // (The following call in fact generates all moves.) + end = gen_legal(pos, moves); + + for (m = moves; m < end; m++) { + TbMove move = *m; + if (type_of_piece_moved(pos,move) != PAWN || is_capture(pos, move)) + continue; + if (!do_move(&pos1, pos, move)) + continue; // not legal + int v = -probe_wdl(&pos1, success); + if (*success == 0) return 0; + if (v == wdl) { + assert(wdl < 3); + return WdlToDtz[wdl + 2]; + } + } + } + + // If we are here, we know that the best move is not an ep capture. + // In other words, the value of wdl corresponds to the WDL value of + // the position without ep rights. It is therefore safe to probe the + // DTZ table with the current value of wdl. + + int dtz = probe_dtz_table(pos, wdl, success); + if (*success >= 0) + return WdlToDtz[wdl + 2] + ((wdl > 0) ? dtz : -dtz); + + // *success < 0 means we need to probe DTZ for the other side to move. + int best; + if (wdl > 0) { + best = INT32_MAX; + } else { + // If (cursed) loss, the worst case is a losing capture or pawn move + // as the "best" move, leading to dtz of -1 or -101. + // In case of mate, this will cause -1 to be returned. + best = WdlToDtz[wdl + 2]; + // If wdl < 0, we still have to generate all moves. + end = gen_moves(pos, m); + } + assert(end != NULL); + + for (m = moves; m < end; m++) { + TbMove move = *m; + // We can skip pawn moves and captures. + // If wdl > 0, we already caught them. If wdl < 0, the initial value + // of best already takes account of them. + if (is_capture(pos, move) || type_of_piece_moved(pos, move) == PAWN) + continue; + if (!do_move(&pos1, pos, move)) { + // move was not legal + continue; + } + int v = -probe_dtz(&pos1, success); + // Check for the case of mate in 1 + if (v == 1 && is_mate(&pos1)) + best = 1; + else if (wdl > 0) { + if (v > 0 && v + 1 < best) + best = v + 1; + } else { + if (v - 1 < best) + best = v - 1; + } + if (*success == 0) return 0; + } + return best; +} + +// Use the DTZ tables to rank and score all root moves in the list. +// A return value of 0 means that not all probes were successful. +static int root_probe_dtz(const Pos *pos, bool hasRepeated, bool useRule50, struct TbRootMoves *rm) +{ + int v, success; + + // Obtain 50-move counter for the root position. + int cnt50 = pos->rule50; + + // The border between draw and win lies at rank 1 or rank 900, depending + // on whether the 50-move rule is used. + int bound = useRule50 ? 900 : 1; + + // Probe, rank and score each move. + TbMove rootMoves[TB_MAX_MOVES]; + TbMove * end = gen_legal(pos,rootMoves); + rm->size = (unsigned)(end-rootMoves); + Pos pos1; + for (unsigned i = 0; i < rm->size; i++) { + struct TbRootMove *m = &(rm->moves[i]); + m->move = rootMoves[i]; + do_move(&pos1, pos, m->move); + + // Calculate dtz for the current move counting from the root position. + if (pos1.rule50 == 0) { + // If the move resets the 50-move counter, dtz is -101/-1/0/1/101. + v = -probe_wdl(&pos1, &success); + assert(v < 3); + v = WdlToDtz[v + 2]; + } else { + // Otherwise, take dtz for the new position and correct by 1 ply. + v = -probe_dtz(&pos1, &success); + if (v > 0) v++; + else if (v < 0) v--; + } + // Make sure that a mating move gets value 1. + if (v == 2 && is_mate(&pos1)) { + v = 1; + } + + if (!success) return 0; + + // Better moves are ranked higher. Guaranteed wins are ranked equally. + // Losing moves are ranked equally unless a 50-move draw is in sight. + // Note that moves ranked 900 have dtz + cnt50 == 100, which in rare + // cases may be insufficient to win as dtz may be one off (see the + // comments before TB_probe_dtz()). + int r = v > 0 ? (v + cnt50 <= 99 && !hasRepeated ? 1000 : 1000 - (v + cnt50)) + : v < 0 ? (-v * 2 + cnt50 < 100 ? -1000 : -1000 + (-v + cnt50)) + : 0; + m->tbRank = r; + + // Determine the score to be displayed for this move. Assign at least + // 1 cp to cursed wins and let it grow to 49 cp as the position gets + // closer to a real win. + m->tbScore = r >= bound ? TB_VALUE_MATE - TB_MAX_MATE_PLY - 1 + : r > 0 ? max( 3, r - 800) * TB_VALUE_PAWN / 200 + : r == 0 ? TB_VALUE_DRAW + : r > -bound ? min(-3, r + 800) * TB_VALUE_PAWN / 200 + : -TB_VALUE_MATE + TB_MAX_MATE_PLY + 1; + } + return 1; +} + +// Use the WDL tables to rank all root moves in the list. +// This is a fallback for the case that some or all DTZ tables are missing. +// A return value of 0 means that not all probes were successful. +int root_probe_wdl(const Pos *pos, bool useRule50, struct TbRootMoves *rm) +{ + static int WdlToRank[] = { -1000, -899, 0, 899, 1000 }; + static Value WdlToValue[] = { + -TB_VALUE_MATE + TB_MAX_MATE_PLY + 1, + TB_VALUE_DRAW - 2, + TB_VALUE_DRAW, + TB_VALUE_DRAW + 2, + TB_VALUE_MATE - TB_MAX_MATE_PLY - 1 + }; + + int v, success; + + // Probe, rank and score each move. + TbMove moves[TB_MAX_MOVES]; + TbMove *end = gen_legal(pos,moves); + rm->size = (unsigned)(end-moves); + Pos pos1; + for (unsigned i = 0; i < rm->size; i++) { + struct TbRootMove *m = &rm->moves[i]; + m->move = moves[i]; + do_move(&pos1, pos, m->move); + v = -probe_wdl(&pos1, &success); + if (!success) return 0; + if (!useRule50) + v = v > 0 ? 2 : v < 0 ? -2 : 0; + m->tbRank = WdlToRank[v + 2]; + m->tbScore = WdlToValue[v + 2]; + } + + return 1; +} + +// Use the DTM tables to find mate scores. +// Either DTZ or WDL must have been probed successfully earlier. +// A return value of 0 means that not all probes were successful. +//int root_probe_dtm(const Pos *pos, struct TbRootMoves *rm) +//{ +// int success; +// Value tmpScore[TB_MAX_MOVES]; +// +// // Probe each move. +// for (unsigned i = 0; i < rm->size; i++) { +// Pos pos1; +// struct TbRootMove *m = &rm->moves[i]; +// +// // Use tbScore to find out if the position is won or lost. +// int wdl = m->tbScore > TB_VALUE_PAWN ? 2 +// : m->tbScore < -TB_VALUE_PAWN ? -2 : 0; +// +// if (wdl == 0) +// tmpScore[i] = 0; +// else { +// // Probe and adjust mate score by 1 ply. +// do_move(&pos1, pos, m->pv[0]); +// Value v = -TB_probe_dtm(&pos1, -wdl, &success); +// tmpScore[i] = wdl > 0 ? v - 1 : v + 1; +// if (success == 0) +// return 0; +// } +// } +// +// // All probes were successful. Now adjust TB scores and ranks. +// for (unsigned i = 0; i < rm->size; i++) { +// struct TbRootMove *m = &rm->moves[i]; +// +// m->tbScore = tmpScore[i]; +// +// // Let rank correspond to mate score, except for critical moves +// // ranked 900, which we rank below all other mates for safety. +// // By ranking mates above 1000 or below -1000, we let the search +// // know it need not search those moves. +// m->tbRank = m->tbRank == 900 ? 1001 : m->tbScore; +// } +// +// return 1; +//} + +// Use the DTM tables to complete a PV with mate score. +//void tb_expand_mate(Pos *pos, struct TbRootMove *move, Value moveScore, unsigned cardinalityDTM) +//{ +// int success = 1, chk = 0; +// Value v = moveScore, w = 0; +// int wdl = v > 0 ? 2 : -2; +// +// if (move->pvSize == TB_MAX_PLY) +// return; +// +// Pos root = *pos; +// // First get to the end of the incomplete PV. +// for (unsigned i = 0; i < move->pvSize; i++) { +// v = v > 0 ? -v - 1 : -v + 1; +// wdl = -wdl; +// Pos pos0 = *pos; +// do_move(pos, &pos0, move->pv[i]); +// } +// +// // Now try to expand until the actual mate. +// if ((int)popcount(pos->white | pos->black) <= (int)cardinalityDTM) { +// while (v != -TB_VALUE_MATE && move->pvSize < TB_MAX_PLY) { +// v = v > 0 ? -v - 1 : -v + 1; +// wdl = -wdl; +// TbMove moves[TB_MAX_MOVES]; +// TbMove *end = gen_legal(pos, moves); +// TbMove *m = moves; +// for (; m < end; m++) { +// Pos pos1; +// do_move(&pos1, pos, *m); +// if (wdl < 0) +// chk = probe_wdl(&pos1, &success); // verify that move wins +// w = success && (wdl > 0 || chk < 0) +// ? TB_probe_dtm(&pos1, wdl, &success) +// : 0; +// if (!success || v == w) break; +// } +// if (!success || v != w) +// break; +// move->pv[move->pvSize++] = *m; +// Pos pos0 = *pos; +// do_move(pos, &pos0, *m); +// } +// } +// // Get back to the root position. +// *pos = root; +//} + +static const int wdl_to_dtz[] = +{ + -1, -101, 0, 101, 1 +}; + +// This supports the original Fathom root probe API +static uint16_t probe_root(Pos *pos, int *score, unsigned *results) +{ + int success; + int dtz = probe_dtz(pos, &success); + if (!success) + return 0; + + int16_t scores[MAX_MOVES]; + uint16_t moves0[MAX_MOVES]; + uint16_t *moves = moves0; + uint16_t *end = gen_moves(pos, moves); + size_t len = end - moves; + size_t num_draw = 0; + unsigned j = 0; + for (unsigned i = 0; i < len; i++) + { + Pos pos1; + if (!do_move(&pos1, pos, moves[i])) + { + scores[i] = SCORE_ILLEGAL; + continue; + } + int v = 0; + // print_move(pos,moves[i]); + if (dtz > 0 && is_mate(&pos1)) + v = 1; + else + { + if (pos1.rule50 != 0) + { + v = -probe_dtz(&pos1, &success); + if (v > 0) + v++; + else if (v < 0) + v--; + } + else + { + v = -probe_wdl(&pos1, &success); + v = wdl_to_dtz[v + 2]; + } + } + num_draw += (v == 0); + if (!success) + return 0; + scores[i] = v; + if (results != NULL) + { + unsigned res = 0; + res = TB_SET_WDL(res, dtz_to_wdl(pos->rule50, v)); + res = TB_SET_FROM(res, move_from(moves[i])); + res = TB_SET_TO(res, move_to(moves[i])); + res = TB_SET_PROMOTES(res, move_promotes(moves[i])); + res = TB_SET_EP(res, is_en_passant(pos, moves[i])); + res = TB_SET_DTZ(res, (v < 0? -v: v)); + results[j++] = res; + } + } + if (results != NULL) + results[j++] = TB_RESULT_FAILED; + if (score != NULL) + *score = dtz; + + // Now be a bit smart about filtering out moves. + if (dtz > 0) // winning (or 50-move rule draw) + { + int best = BEST_NONE; + uint16_t best_move = 0; + for (unsigned i = 0; i < len; i++) + { + int v = scores[i]; + if (v == SCORE_ILLEGAL) + continue; + if (v > 0 && v < best) + { + best = v; + best_move = moves[i]; + } + } + return (best == BEST_NONE? 0: best_move); + } + else if (dtz < 0) // losing (or 50-move rule draw) + { + int best = 0; + uint16_t best_move = 0; + for (unsigned i = 0; i < len; i++) + { + int v = scores[i]; + if (v == SCORE_ILLEGAL) + continue; + if (v < best) + { + best = v; + best_move = moves[i]; + } + } + return (best == 0? MOVE_CHECKMATE: best_move); + } + else // drawing + { + // Check for stalemate: + if (num_draw == 0) + return MOVE_STALEMATE; + + // Select a "random" move that preserves the draw. + // Uses calc_key as the PRNG. + size_t count = calc_key(pos, !pos->turn) % num_draw; + for (unsigned i = 0; i < len; i++) + { + int v = scores[i]; + if (v == SCORE_ILLEGAL) + continue; + if (v == 0) + { + if (count == 0) + return moves[i]; + count--; + } + } + return 0; + } +} + +#ifndef TB_NO_HELPER_API + +unsigned tb_pop_count(uint64_t bb) +{ + return popcount(bb); +} + +unsigned tb_lsb(uint64_t bb) +{ + return lsb(bb); +} + +uint64_t tb_pop_lsb(uint64_t bb) +{ + return poplsb(bb); +} + +uint64_t tb_king_attacks(unsigned sq) +{ + return king_attacks(sq); +} + +uint64_t tb_queen_attacks(unsigned sq, uint64_t occ) +{ + return queen_attacks(sq, occ); +} + +uint64_t tb_rook_attacks(unsigned sq, uint64_t occ) +{ + return rook_attacks(sq, occ); +} + +uint64_t tb_bishop_attacks(unsigned sq, uint64_t occ) +{ + return bishop_attacks(sq, occ); +} + +uint64_t tb_knight_attacks(unsigned sq) +{ + return knight_attacks(sq); +} + +uint64_t tb_pawn_attacks(unsigned sq, bool color) +{ + return pawn_attacks(sq, color); +} + +#endif /* TB_NO_HELPER_API */ diff --git a/syzygy/tbprobe.h b/syzygy/tbprobe.h new file mode 100644 index 0000000..668b54c --- /dev/null +++ b/syzygy/tbprobe.h @@ -0,0 +1,401 @@ +/* + * tbprobe.h + * (C) 2015 basil, all rights reserved, + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef TBPROBE_H +#define TBPROBE_H + +#include "tbconfig.h" +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef TB_NO_STDINT +#include +#else +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned uint32_t; +typedef long long unsigned uint64_t; +typedef char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; +#endif + +#ifndef TB_NO_STDBOOL +#include +#else +#ifndef __cplusplus +typedef uint8_t bool; +#define true 1 +#define false 0 +#endif +#endif + +/* + * Internal definitions. Do not call these functions directly. + */ +extern bool tb_init_impl(const char *_path); +extern unsigned tb_probe_wdl_impl( + uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _ep, + bool _turn); +extern unsigned tb_probe_root_impl( + uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _rule50, + unsigned _ep, + bool _turn, + unsigned *_results); + +/****************************************************************************/ +/* MAIN API */ +/****************************************************************************/ + +#define TB_MAX_MOVES (192+1) +#define TB_MAX_CAPTURES 64 +#define TB_MAX_PLY 256 +#define TB_CASTLING_K 0x1 /* White king-side. */ +#define TB_CASTLING_Q 0x2 /* White queen-side. */ +#define TB_CASTLING_k 0x4 /* Black king-side. */ +#define TB_CASTLING_q 0x8 /* Black queen-side. */ + +#define TB_LOSS 0 /* LOSS */ +#define TB_BLESSED_LOSS 1 /* LOSS but 50-move draw */ +#define TB_DRAW 2 /* DRAW */ +#define TB_CURSED_WIN 3 /* WIN but 50-move draw */ +#define TB_WIN 4 /* WIN */ + +#define TB_PROMOTES_NONE 0 +#define TB_PROMOTES_QUEEN 1 +#define TB_PROMOTES_ROOK 2 +#define TB_PROMOTES_BISHOP 3 +#define TB_PROMOTES_KNIGHT 4 + +#define TB_RESULT_WDL_MASK 0x0000000F +#define TB_RESULT_TO_MASK 0x000003F0 +#define TB_RESULT_FROM_MASK 0x0000FC00 +#define TB_RESULT_PROMOTES_MASK 0x00070000 +#define TB_RESULT_EP_MASK 0x00080000 +#define TB_RESULT_DTZ_MASK 0xFFF00000 +#define TB_RESULT_WDL_SHIFT 0 +#define TB_RESULT_TO_SHIFT 4 +#define TB_RESULT_FROM_SHIFT 10 +#define TB_RESULT_PROMOTES_SHIFT 16 +#define TB_RESULT_EP_SHIFT 19 +#define TB_RESULT_DTZ_SHIFT 20 + +#define TB_GET_WDL(_res) \ + (((_res) & TB_RESULT_WDL_MASK) >> TB_RESULT_WDL_SHIFT) +#define TB_GET_TO(_res) \ + (((_res) & TB_RESULT_TO_MASK) >> TB_RESULT_TO_SHIFT) +#define TB_GET_FROM(_res) \ + (((_res) & TB_RESULT_FROM_MASK) >> TB_RESULT_FROM_SHIFT) +#define TB_GET_PROMOTES(_res) \ + (((_res) & TB_RESULT_PROMOTES_MASK) >> TB_RESULT_PROMOTES_SHIFT) +#define TB_GET_EP(_res) \ + (((_res) & TB_RESULT_EP_MASK) >> TB_RESULT_EP_SHIFT) +#define TB_GET_DTZ(_res) \ + (((_res) & TB_RESULT_DTZ_MASK) >> TB_RESULT_DTZ_SHIFT) + +#define TB_SET_WDL(_res, _wdl) \ + (((_res) & ~TB_RESULT_WDL_MASK) | \ + (((_wdl) << TB_RESULT_WDL_SHIFT) & TB_RESULT_WDL_MASK)) +#define TB_SET_TO(_res, _to) \ + (((_res) & ~TB_RESULT_TO_MASK) | \ + (((_to) << TB_RESULT_TO_SHIFT) & TB_RESULT_TO_MASK)) +#define TB_SET_FROM(_res, _from) \ + (((_res) & ~TB_RESULT_FROM_MASK) | \ + (((_from) << TB_RESULT_FROM_SHIFT) & TB_RESULT_FROM_MASK)) +#define TB_SET_PROMOTES(_res, _promotes) \ + (((_res) & ~TB_RESULT_PROMOTES_MASK) | \ + (((_promotes) << TB_RESULT_PROMOTES_SHIFT) & TB_RESULT_PROMOTES_MASK)) +#define TB_SET_EP(_res, _ep) \ + (((_res) & ~TB_RESULT_EP_MASK) | \ + (((_ep) << TB_RESULT_EP_SHIFT) & TB_RESULT_EP_MASK)) +#define TB_SET_DTZ(_res, _dtz) \ + (((_res) & ~TB_RESULT_DTZ_MASK) | \ + (((_dtz) << TB_RESULT_DTZ_SHIFT) & TB_RESULT_DTZ_MASK)) + +#define TB_RESULT_CHECKMATE TB_SET_WDL(0, TB_WIN) +#define TB_RESULT_STALEMATE TB_SET_WDL(0, TB_DRAW) +#define TB_RESULT_FAILED 0xFFFFFFFF + +/* + * The tablebase can be probed for any position where #pieces <= TB_LARGEST. + */ +extern unsigned TB_LARGEST; + +/* + * Initialize the tablebase. + * + * PARAMETERS: + * - path: + * The tablebase PATH string. + * + * RETURN: + * - true=succes, false=failed. The TB_LARGEST global will also be + * initialized. If no tablebase files are found, then `true' is returned + * and TB_LARGEST is set to zero. + */ +bool tb_init(const char *_path); + +/* + * Free any resources allocated by tb_init + */ +void tb_free(void); + +/* + * Probe the Win-Draw-Loss (WDL) table. + * + * PARAMETERS: + * - white, black, kings, queens, rooks, bishops, knights, pawns: + * The current position (bitboards). + * - rule50: + * The 50-move half-move clock. + * - castling: + * Castling rights. Set to zero if no castling is possible. + * - ep: + * The en passant square (if exists). Set to zero if there is no en passant + * square. + * - turn: + * true=white, false=black + * + * RETURN: + * - One of {TB_LOSS, TB_BLESSED_LOSS, TB_DRAW, TB_CURSED_WIN, TB_WIN}. + * Otherwise returns TB_RESULT_FAILED if the probe failed. + * + * NOTES: + * - Engines should use this function during search. + * - This function is thread safe assuming TB_NO_THREADS is disabled. + */ +static inline unsigned tb_probe_wdl( + uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _rule50, + unsigned _castling, + unsigned _ep, + bool _turn) +{ + if (_castling != 0) + return TB_RESULT_FAILED; + if (_rule50 != 0) + return TB_RESULT_FAILED; + return tb_probe_wdl_impl(_white, _black, _kings, _queens, _rooks, + _bishops, _knights, _pawns, _ep, _turn); +} + + + + +/* + * Probe the Distance-To-Zero (DTZ) table. + * + * PARAMETERS: + * - white, black, kings, queens, rooks, bishops, knights, pawns: + * The current position (bitboards). + * - rule50: + * The 50-move half-move clock. + * - castling: + * Castling rights. Set to zero if no castling is possible. + * - ep: + * The en passant square (if exists). Set to zero if there is no en passant + * square. + * - turn: + * true=white, false=black + * - results (OPTIONAL): + * Alternative results, one for each possible legal move. The passed array + * must be TB_MAX_MOVES in size. + * If alternative results are not desired then set results=NULL. + * + * RETURN: + * - A TB_RESULT value comprising: + * 1) The WDL value (TB_GET_WDL) + * 2) The suggested move (TB_GET_FROM, TB_GET_TO, TB_GET_PROMOTES, TB_GET_EP) + * 3) The DTZ value (TB_GET_DTZ) + * The suggested move is guaranteed to preserved the WDL value. + * + * Otherwise: + * 1) TB_RESULT_STALEMATE is returned if the position is in stalemate. + * 2) TB_RESULT_CHECKMATE is returned if the position is in checkmate. + * 3) TB_RESULT_FAILED is returned if the probe failed. + * + * If results!=NULL, then a TB_RESULT for each legal move will be generated + * and stored in the results array. The results array will be terminated + * by TB_RESULT_FAILED. + * + * NOTES: + * - Engines can use this function to probe at the root. This function should + * not be used during search. + * - DTZ tablebases can suggest unnatural moves, especially for losing + * positions. Engines may prefer to traditional search combined with WDL + * move filtering using the alternative results array. + * - This function is NOT thread safe. For engines this function should only + * be called once at the root per search. + */ +static inline unsigned tb_probe_root( + uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _rule50, + unsigned _castling, + unsigned _ep, + bool _turn, + unsigned *_results) +{ + if (_castling != 0) + return TB_RESULT_FAILED; + return tb_probe_root_impl(_white, _black, _kings, _queens, _rooks, + _bishops, _knights, _pawns, _rule50, _ep, _turn, _results); +} + +typedef uint16_t TbMove; + +#define TB_MOVE_FROM(move) \ + (((move) >> 6) & 0x3F) +#define TB_MOVE_TO(move) \ + ((move) & 0x3F) +#define TB_MOVE_PROMOTES(move) \ + (((move) >> 12) & 0x7) + +struct TbRootMove { + TbMove move; + TbMove pv[TB_MAX_PLY]; + unsigned pvSize; + int32_t tbScore, tbRank; +}; + +struct TbRootMoves { + unsigned size; + struct TbRootMove moves[TB_MAX_MOVES]; +}; + +/* + * Use the DTZ tables to rank and score all root moves. + * INPUT: as for tb_probe_root + * OUTPUT: TbRootMoves structure is filled in. This contains + * an array of TbRootMove structures. + * Each structure instance contains a rank, a score, and a + * predicted principal variation. + * RETURN VALUE: + * non-zero if ok, 0 means not all probes were successful + * + */ +int tb_probe_root_dtz( + uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _rule50, + unsigned _castling, + unsigned _ep, + bool _turn, + bool hasRepeated, + bool useRule50, + struct TbRootMoves *_results); + +/* +// Use the WDL tables to rank and score all root moves. +// This is a fallback for the case that some or all DTZ tables are missing. + * INPUT: as for tb_probe_root + * OUTPUT: TbRootMoves structure is filled in. This contains + * an array of TbRootMove structures. + * Each structure instance contains a rank, a score, and a + * predicted principal variation. + * RETURN VALUE: + * non-zero if ok, 0 means not all probes were successful + * + */ +int tb_probe_root_wdl(uint64_t _white, + uint64_t _black, + uint64_t _kings, + uint64_t _queens, + uint64_t _rooks, + uint64_t _bishops, + uint64_t _knights, + uint64_t _pawns, + unsigned _rule50, + unsigned _castling, + unsigned _ep, + bool _turn, + bool useRule50, + struct TbRootMoves *_results); + +/****************************************************************************/ +/* HELPER API */ +/****************************************************************************/ + +/* + * The HELPER API provides some useful additional functions. It is optional + * and can be disabled by defining TB_NO_HELPER_API. Engines should disable + * the HELPER API. + */ + +#ifndef TB_NO_HELPER_API + +extern unsigned tb_pop_count(uint64_t _bb); +extern unsigned tb_lsb(uint64_t _bb); +extern uint64_t tb_pop_lsb(uint64_t _bb); +extern uint64_t tb_king_attacks(unsigned _square); +extern uint64_t tb_queen_attacks(unsigned _square, uint64_t _occ); +extern uint64_t tb_rook_attacks(unsigned _square, uint64_t _occ); +extern uint64_t tb_bishop_attacks(unsigned _square, uint64_t _occ); +extern uint64_t tb_knight_attacks(unsigned _square); +extern uint64_t tb_pawn_attacks(unsigned _square, bool _color); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif