46 using value_t = int8_t;
48 static constexpr size_t SIZE_BYTES_8PLY_DB = 103'545;
50 static constexpr size_t SIZE_BYTES_12PLY_DB = 6'943'780;
52 static constexpr size_t SIZE_BYTES_12PLY_DB_WITH_DIST = 21'004'495;
55 static constexpr size_t SIZE_8PLY_DB = 34'515;
57 static constexpr size_t SIZE_12PLY_DB = 1'735'945;
59 static constexpr size_t SIZE_12PLY_DB_WITH_DIST = 4'200'899;
61 std::vector<std::tuple<key_t, value_t>>
63 bool m_withDistances{};
65 std::filesystem::path m_bookPath;
74 [[nodiscard]] value_t binarySearch(
const key_t& huffmanCode)
const {
77 int r = m_book.size() - 1;
79 const auto mid = (l + r + 1) / 2;
80 auto p = m_book.at(mid);
81 if (std::get<0>(p) == huffmanCode) {
82 return std::get<1>(p);
84 if (std::get<0>(p) > huffmanCode) {
92 return std::numeric_limits<value_t>::min();
97 static constexpr auto NONE_VALUE = std::numeric_limits<value_t>::min();
110 const bool is_8ply,
const bool with_distances) {
111 init(bookPath, is_8ply, with_distances);
123 if (!std::filesystem::exists(bookPath)) {
124 throw std::invalid_argument(
"Book file does not exist: " +
128 const auto fileSize = std::filesystem::file_size(bookPath);
130 const bool is8ply = (fileSize == SIZE_BYTES_8PLY_DB);
131 const bool withDistances = (fileSize == SIZE_BYTES_12PLY_DB_WITH_DIST);
133 init(bookPath, is8ply, withDistances);
151 void init(
const std::filesystem::path& bookPath,
const bool is_8ply,
152 const bool with_distances) {
153 assert(!is_8ply || !with_distances);
156 if (!std::filesystem::exists(bookPath)) {
157 throw std::invalid_argument(
"Book file does not exist: " +
163 const auto fileSize = std::filesystem::file_size(bookPath);
166 assert(fileSize == SIZE_BYTES_8PLY_DB);
167 }
else if (with_distances) {
169 SIZE_BYTES_12PLY_DB_WITH_DIST);
171 assert(fileSize == SIZE_BYTES_12PLY_DB);
174 this->m_withDistances = with_distances;
175 this->m_is8ply = is_8ply;
176 this->m_book =
readBook(bookPath, with_distances, is_8ply);
177 this->m_bookPath = bookPath;
178 this->m_nPly = (is_8ply ? 8 : 12);
180 assert(!with_distances || is_8ply ||
181 m_book.size() == SIZE_12PLY_DB_WITH_DIST);
183 assert(with_distances || is_8ply ||
184 m_book.size() == SIZE_12PLY_DB);
187 m_book.size() == SIZE_8PLY_DB);
196 [[nodiscard]]
auto getEntry(
const size_t entryIdx)
const {
197 return m_book.at(entryIdx);
216 static std::tuple<key_t, int>
readline(std::ifstream& file,
217 const bool with_distances,
218 const bool is_8ply) {
219 const decltype(file.gcount()) bytes_position = is_8ply ? 3 : 4;
221 file.read(buffer, bytes_position);
223 if (file.gcount() != bytes_position) {
229 key_t huffman_position = 0;
230 for (
decltype(file.gcount()) i = 0; i < bytes_position; ++i) {
232 (huffman_position << 8) | static_cast<unsigned char>(buffer[i]);
237 if (huffman_position & (1LL << ((bytes_position * 8) - 1))) {
238 huffman_position -= (1LL << (bytes_position * 8));
243 if (with_distances) {
246 if (file.read(&score_byte, 1)) {
247 score =
static_cast<int8_t
>(score_byte);
254 score = (
static_cast<value_t
>(huffman_position) & 3) * -1;
255 huffman_position = huffman_position & ~3;
258 return {huffman_position, score};
273 static std::vector<std::tuple<key_t, value_t>>
readBook(
274 const std::filesystem::path& filename,
const bool with_distances =
true,
275 const bool is_8ply =
false) {
276 std::vector<std::tuple<key_t, value_t>> book;
277 std::ifstream file(filename, std::ios::binary);
279 std::cerr <<
"Failed to open file: " << filename.string() <<
'\n';
284 auto [position, score] =
readline(file, with_distances, is_8ply);
288 book.emplace_back(position, score);
300 template <
typename T>
302 return (value > 0) - (value < 0);
318 if (!m_withDistances)
return value;
321 int movesLeft = std::abs(value) - 100 + b.
movesLeft();
322 return sign(value) * (movesLeft / 2 + 1);
359 int val = binarySearch(p);
366 val = binarySearch(p);
Bitboard-based representation of a Connect-4 position.
Connect-4 position represented as a pair of 64-bit bitboards.
TMovesCounter countTokens() const
Number of stones currently placed on the board.
Board mirror() const
Mirror the board around its central column.
TMovesCounter movesLeft() const
Number of plies remaining until the board is full.
int toHuffman() const
Encode the position as a compact Huffman-like integer.
int getNPly() const
Number of stones-on-board the book covers (8 or 12).
static std::tuple< key_t, int > readline(std::ifstream &file, const bool with_distances, const bool is_8ply)
Read a single entry from a binary book stream.
static std::vector< std::tuple< key_t, value_t > > readBook(const std::filesystem::path &filename, const bool with_distances=true, const bool is_8ply=false)
Slurp an entire opening book file into memory.
auto getBook() const
Copy of the underlying sorted (key, value) array.
auto getBookSize() const
Number of entries currently held in memory.
int convertValue(const int value, const Board &b) const
Translate a raw book value into the engine's score convention.
OpeningBook(const std::filesystem::path &bookPath)
Load an opening book and auto-detect its flavour.
auto getEntry(const size_t entryIdx) const
Random-access getter for raw book entries.
static constexpr auto NONE_VALUE
Sentinel returned when a position is not present in the book.
static int sign(T value)
Numerical sign function returning -1, 0 or +1.
void init(const std::filesystem::path &bookPath, const bool is_8ply, const bool with_distances)
Re-initialise the book in place.
bool isInBook(const Board &b) const
Test whether the exact (non-mirrored) position is in the book.
int getBoardValue(const Board &b) const
Retrieve the engine score for a position covered by the book.
OpeningBook(const std::filesystem::path &bookPath, const bool is_8ply, const bool with_distances)
Load an opening book with explicit flavour selection.
Top-level namespace for the BitBully Connect-4 engine.