diff --git a/src/Makefile b/src/Makefile index 8b9b16e470c..aa33a0274f4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -38,7 +38,8 @@ PGOBENCH = ./$(EXE) bench ### Object files OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \ - search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o + search.o thread.o timeman.o tt.o uci.o ucioption.o book.o \ + syzygy/tbprobe.o ### Establish the operating system name KERNEL = $(shell uname -s) diff --git a/src/book.cpp b/src/book.cpp new file mode 100644 index 00000000000..e8278efffe4 --- /dev/null +++ b/src/book.cpp @@ -0,0 +1,188 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "misc.h" +#include "book.h" +#include "movegen.h" +#include +#include + +Book Books; + +Book::Book() +{ + max_book_ply = 400; + NumBookEntries = 0; + BookEntries = nullptr; +} + +Book::~Book() +{ + if (BookEntries != nullptr) + delete[] BookEntries; +} + +void Book::init(const std::string& filename) +{ + if (filename.length() == 0) return; + const char *fnam = filename.c_str(); + + if (strcmp(fnam, "") == 0) + { + return; + } + + FILE *fpt = fopen(fnam, "rb"); + if (fpt == NULL) + { + sync_cout << "info string Could not open " << filename << sync_endl; + return; + } + + if (BookEntries != nullptr) + { + free(BookEntries); + BookEntries = nullptr; + } + + fseek(fpt, 0L, SEEK_END); + int filesize = ftell(fpt); + fseek(fpt, 0L, SEEK_SET); + + NumBookEntries = filesize / sizeof(BookEntry); + BookEntries = new BookEntry[NumBookEntries]; + + size_t nRead = fread(BookEntries, 1, filesize, fpt); + fclose(fpt); + + if (nRead != 1) + { + free(BookEntries); + BookEntries = nullptr; + sync_cout << "info string Could not read " << filename << sync_endl; + return; + } + for (size_t i = 0; i < NumBookEntries; i++) + byteswap_bookentry(&BookEntries[i]); + + sync_cout << "info string Book loaded: " << filename << " (" << NumBookEntries << " entries)" << sync_endl; +} + +void Book::set_max_ply(int new_max_ply) +{ + max_book_ply = new_max_ply; +} + +Move Book::probe_root(Position& pos) +{ + if (BookEntries != nullptr && pos.game_ply() < max_book_ply) + { + int count = 0; + size_t index = find_first_entry(pos.key(), count); + if (count > 0) + { + PRNG rng(now()); + Move move = reconstruct_move(BookEntries[index + rng.rand() % count].move); + + // Add 'special move' flags and verify it is legal + for (const auto& m : MoveList(pos)) + { + if (move == (m.move & (~(3 << 14)))) // compare with MoveType (bit 14-15) masked out + return m; + } + } + } + return MOVE_NONE; +} + +void Book::probe(Position& pos, std::vector& bookmoves) +{ + if (BookEntries != nullptr && pos.game_ply() < max_book_ply) + { + int count = 0; + size_t index = find_first_entry(pos.key(), count); + if (count > 0) + { + for (int i = 0; i < count; i++) + bookmoves.push_back(Move(BookEntries[index + i].move)); + } + } +} + +Move Book::reconstruct_move(uint16_t book_move) +{ + Move move = Move(book_move); + + int pt = (move >> 12) & 7; + if (pt) + return make(from_sq(move), to_sq(move), PieceType(pt + 1)); + + return move; +} + +size_t Book::find_first_entry(uint64_t key, int& index_count) +{ + size_t start = 0; + size_t end = NumBookEntries; + + for (;;) + { + size_t mid = (end + start) / 2; + + if (BookEntries[mid].key < key) + start = mid; + else + { + if (BookEntries[mid].key > key) + end = mid; + else + { + start = std::max(mid - 4, (size_t)0); + end = std::min(mid + 4, NumBookEntries); + } + } + + if (end - start < 9) + break; + } + + for (size_t i = start; i < end; i++) + { + if (key == BookEntries[i].key) + { + while ((i > 0) && (key == BookEntries[i - 1].key)) + i--; + index_count = 1; + end = i; + while ((++end < NumBookEntries) && (key == BookEntries[end].key)) + index_count++; + return i; + } + } + return 0; +} + +void Book::byteswap_bookentry(BookEntry *be) +{ + be->key = number(&be->key); + be->move = number(&be->move); + be->weight = number(&be->weight); + be->score = number(&be->score); +} diff --git a/src/book.h b/src/book.h new file mode 100644 index 00000000000..9225d0b1bd4 --- /dev/null +++ b/src/book.h @@ -0,0 +1,53 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BOOK_H_INCLUDED +#define BOOK_H_INCLUDED + +#include "position.h" +#include + +class Book +{ + typedef struct { + uint64_t key; + uint16_t move; + uint16_t weight; + uint32_t score; + } BookEntry; +public: + Book(); + ~Book(); + void init(const std::string& filename); + void set_max_ply(int new_max_ply); + Move probe_root(Position& pos); + void probe(Position& pos, std::vector& bookmoves); +private: + size_t find_first_entry(uint64_t key, int& index_count); + Move reconstruct_move(uint16_t book_move); + size_t NumBookEntries; + BookEntry *BookEntries; + void byteswap_bookentry(BookEntry *be); + int max_book_ply; +}; + +extern Book Books; + +#endif // #ifndef BOOK_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index a093b5bf0a2..03782eb3c88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "book.h" namespace PSQT { void init(); @@ -44,6 +45,7 @@ int main(int argc, char* argv[]) { Search::init(); Pawns::init(); Threads.set(Options["Threads"]); + Books.init(Options["BookFile"]); Search::clear(); // After threads are up UCI::loop(argc, argv); diff --git a/src/misc.h b/src/misc.h index 3cba486796b..c355a7968fe 100644 --- a/src/misc.h +++ b/src/misc.h @@ -26,6 +26,7 @@ #include #include #include +#include // For std::memset and std::memcpy #include "types.h" @@ -111,4 +112,33 @@ namespace WinProcGroup { void bindThisThread(size_t idx); } +enum { BigEndian, LittleEndian }; +template +inline void swap_endian(T& x) +{ + static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); + + uint8_t tmp, *c = (uint8_t*)&x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; +} +template<> inline void swap_endian(uint8_t&) {} + +template T number(void* addr) +{ + static const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; + static const bool IsLittleEndian = (Le.c[0] == 4); + + T v; + + if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*)addr); + + if (LE != IsLittleEndian) + swap_endian(v); + return v; +} + #endif // #ifndef MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 756b2d5769f..f5c4b4528bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -25,6 +25,7 @@ #include #include +#include "book.h" #include "evaluate.h" #include "misc.h" #include "movegen.h" @@ -216,11 +217,24 @@ void MainThread::search() { } else { - for (Thread* th : Threads) - if (th != this) - th->start_searching(); + Move bookMove = MOVE_NONE; + + if (!Limits.infinite && !Limits.mate) + bookMove = Books.probe_root(rootPos); + + if (bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) + { + for (Thread* th : Threads) + std::swap(th->rootMoves[0], *std::find(th->rootMoves.begin(), th->rootMoves.end(), bookMove)); + } + else + { + for (Thread* th : Threads) + if (th != this) + th->start_searching(); - Thread::search(); // Let's start searching! + Thread::search(); // Let's start searching! + } } // When we reach the maximum depth, we can arrive here without a raise of @@ -884,6 +898,10 @@ namespace { contHist, countermove, ss->killers); + + std::vector bookMoves; + Books.probe(pos, bookMoves); + value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc skipQuiets = false; @@ -907,6 +925,9 @@ namespace { thisThread->rootMoves.begin() + thisThread->pvLast, move)) continue; + if (bookMoves.size() && !std::count(bookMoves.begin(), bookMoves.end(), move & (~(3 << 14)))) + continue; + ss->moveCount = ++moveCount; if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 60979a56874..bbf6e0ce225 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -57,7 +57,6 @@ namespace { constexpr int TBPIECES = 7; // Max number of supported pieces -enum { BigEndian, LittleEndian }; enum TBType { KEY, WDL, DTZ }; // Used as template parameter // Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables @@ -90,34 +89,6 @@ constexpr Value WDL_to_value[] = { VALUE_MATE - MAX_PLY - 1 }; -template -inline void swap_endian(T& x) -{ - static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); - - uint8_t tmp, *c = (uint8_t*)&x; - for (int i = 0; i < Half; ++i) - tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; -} -template<> inline void swap_endian(uint8_t&) {} - -template T number(void* addr) -{ - static const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; - static const bool IsLittleEndian = (Le.c[0] == 4); - - T v; - - if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) - std::memcpy(&v, addr, sizeof(T)); - else - v = *((T*)addr); - - if (LE != IsLittleEndian) - swap_endian(v); - return v; -} - // DTZ tables don't store valid scores for moves that reset the rule50 counter // like captures and pawn moves but we can easily recover the correct dtz of the // previous move if we know the position's WDL score. diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 1c6ef777257..e7d0ed2acfa 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -28,6 +28,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "book.h" using std::string; @@ -41,6 +42,8 @@ void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } +void on_book_file(const Option& o) { Books.init(o); } +void on_book_depth(const Option& o) { Books.set_max_ply(o); } /// Our case insensitive less() function as required by UCI protocol @@ -77,6 +80,8 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["BookFile"] << Option("", on_book_file); + o["BookDepth"] << Option(400, 1, 400, on_book_depth); }