mirror of
https://github.com/sockspls/badfish
synced 2025-04-29 16:23:09 +00:00
Refactor global variables
This aims to remove some of the annoying global structure which Stockfish has. Overall there is no major elo regression to be expected. Non regression SMP STC (paused, early version): https://tests.stockfishchess.org/tests/view/65983d7979aa8af82b9608f1 LLR: 0.23 (-2.94,2.94) <-1.75,0.25> Total: 76232 W: 19035 L: 19096 D: 38101 Ptnml(0-2): 92, 8735, 20515, 8690, 84 Non regression STC (early version): https://tests.stockfishchess.org/tests/view/6595b3a479aa8af82b95da7f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 185344 W: 47027 L: 46972 D: 91345 Ptnml(0-2): 571, 21285, 48943, 21264, 609 Non regression SMP STC: https://tests.stockfishchess.org/tests/view/65a0715c79aa8af82b96b7e4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142936 W: 35761 L: 35662 D: 71513 Ptnml(0-2): 209, 16400, 38135, 16531, 193 These global structures/variables add hidden dependencies and allow data to be mutable from where it shouldn't it be (i.e. options). They also prevent Stockfish from internal selfplay, which would be a nice thing to be able to do, i.e. instantiate two Stockfish instances and let them play against each other. It will also allow us to make Stockfish a library, which can be easier used on other platforms. For consistency with the old search code, `thisThread` has been kept, even though it is not strictly necessary anymore. This the first major refactor of this kind (in recent time), and future changes are required, to achieve the previously described goals. This includes cleaning up the dependencies, transforming the network to be self contained and coming up with a plan to deal with proper tablebase memory management (see comments for more information on this). The removal of these global structures has been discussed in parts with Vondele and Sopel. closes https://github.com/official-stockfish/Stockfish/pull/4968 No functional change
This commit is contained in:
parent
6deb88728f
commit
a107910951
27 changed files with 1200 additions and 1001 deletions
|
@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \
|
|||
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
|
||||
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
|
||||
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
|
||||
tt.h tune.h types.h uci.h
|
||||
tt.h tune.h types.h uci.h ucioption.h
|
||||
|
||||
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
@ -34,9 +35,10 @@
|
|||
#include "nnue/evaluate_nnue.h"
|
||||
#include "nnue/nnue_architecture.h"
|
||||
#include "position.h"
|
||||
#include "thread.h"
|
||||
#include "search.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
||||
// data in the engine binary (using incbin.h, by Dale Weiler).
|
||||
|
@ -62,10 +64,6 @@ namespace Stockfish {
|
|||
|
||||
namespace Eval {
|
||||
|
||||
std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles = {
|
||||
{NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}},
|
||||
{NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}};
|
||||
|
||||
|
||||
// Tries to load a NNUE network at startup time, or when the engine
|
||||
// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
|
||||
|
@ -74,38 +72,45 @@ std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles = {
|
|||
// network may be embedded in the binary), in the active working directory and
|
||||
// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
|
||||
// variable to have the engine search in a special directory in their distro.
|
||||
void NNUE::init() {
|
||||
NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory,
|
||||
const OptionsMap& options,
|
||||
NNUE::EvalFiles evalFiles) {
|
||||
|
||||
for (auto& [netSize, evalFile] : EvalFiles)
|
||||
for (auto& [netSize, evalFile] : evalFiles)
|
||||
{
|
||||
// Replace with
|
||||
// Options[evalFile.option_name]
|
||||
// options[evalFile.optionName]
|
||||
// once fishtest supports the uci option EvalFileSmall
|
||||
std::string user_eval_file =
|
||||
netSize == Small ? evalFile.default_name : Options[evalFile.option_name];
|
||||
netSize == Small ? evalFile.defaultName : options[evalFile.optionName];
|
||||
|
||||
if (user_eval_file.empty())
|
||||
user_eval_file = evalFile.default_name;
|
||||
user_eval_file = evalFile.defaultName;
|
||||
|
||||
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
|
||||
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
|
||||
stringify(DEFAULT_NNUE_DIRECTORY)};
|
||||
#else
|
||||
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
|
||||
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
|
||||
#endif
|
||||
|
||||
for (const std::string& directory : dirs)
|
||||
{
|
||||
if (evalFile.selected_name != user_eval_file)
|
||||
if (evalFile.current != user_eval_file)
|
||||
{
|
||||
if (directory != "<internal>")
|
||||
{
|
||||
std::ifstream stream(directory + user_eval_file, std::ios::binary);
|
||||
if (NNUE::load_eval(user_eval_file, stream, netSize))
|
||||
evalFile.selected_name = user_eval_file;
|
||||
auto description = NNUE::load_eval(stream, netSize);
|
||||
|
||||
if (description.has_value())
|
||||
{
|
||||
evalFile.current = user_eval_file;
|
||||
evalFile.netDescription = description.value();
|
||||
}
|
||||
}
|
||||
|
||||
if (directory == "<internal>" && user_eval_file == evalFile.default_name)
|
||||
if (directory == "<internal>" && user_eval_file == evalFile.defaultName)
|
||||
{
|
||||
// C++ way to prepare a buffer for a memory stream
|
||||
class MemoryBuffer: public std::basic_streambuf<char> {
|
||||
|
@ -124,28 +129,36 @@ void NNUE::init() {
|
|||
(void) gEmbeddedNNUESmallEnd;
|
||||
|
||||
std::istream stream(&buffer);
|
||||
if (NNUE::load_eval(user_eval_file, stream, netSize))
|
||||
evalFile.selected_name = user_eval_file;
|
||||
auto description = NNUE::load_eval(stream, netSize);
|
||||
|
||||
if (description.has_value())
|
||||
{
|
||||
evalFile.current = user_eval_file;
|
||||
evalFile.netDescription = description.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return evalFiles;
|
||||
}
|
||||
|
||||
// Verifies that the last net used was loaded successfully
|
||||
void NNUE::verify() {
|
||||
void NNUE::verify(const OptionsMap& options,
|
||||
const std::unordered_map<Eval::NNUE::NetSize, EvalFile>& evalFiles) {
|
||||
|
||||
for (const auto& [netSize, evalFile] : EvalFiles)
|
||||
for (const auto& [netSize, evalFile] : evalFiles)
|
||||
{
|
||||
// Replace with
|
||||
// Options[evalFile.option_name]
|
||||
// options[evalFile.optionName]
|
||||
// once fishtest supports the uci option EvalFileSmall
|
||||
std::string user_eval_file =
|
||||
netSize == Small ? evalFile.default_name : Options[evalFile.option_name];
|
||||
netSize == Small ? evalFile.defaultName : options[evalFile.optionName];
|
||||
if (user_eval_file.empty())
|
||||
user_eval_file = evalFile.default_name;
|
||||
user_eval_file = evalFile.defaultName;
|
||||
|
||||
if (evalFile.selected_name != user_eval_file)
|
||||
if (evalFile.current != user_eval_file)
|
||||
{
|
||||
std::string msg1 =
|
||||
"Network evaluation parameters compatible with the engine must be available.";
|
||||
|
@ -155,7 +168,7 @@ void NNUE::verify() {
|
|||
"including the directory name, to the network file.";
|
||||
std::string msg4 = "The default net can be downloaded from: "
|
||||
"https://tests.stockfishchess.org/api/nn/"
|
||||
+ evalFile.default_name;
|
||||
+ evalFile.defaultName;
|
||||
std::string msg5 = "The engine will be terminated now.";
|
||||
|
||||
sync_cout << "info string ERROR: " << msg1 << sync_endl;
|
||||
|
@ -183,7 +196,7 @@ int Eval::simple_eval(const Position& pos, Color c) {
|
|||
|
||||
// Evaluate is the evaluator for the outer world. It returns a static evaluation
|
||||
// of the position from the point of view of the side to move.
|
||||
Value Eval::evaluate(const Position& pos) {
|
||||
Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) {
|
||||
|
||||
assert(!pos.checkers());
|
||||
|
||||
|
@ -204,7 +217,7 @@ Value Eval::evaluate(const Position& pos) {
|
|||
Value nnue = smallNet ? NNUE::evaluate<NNUE::Small>(pos, true, &nnueComplexity)
|
||||
: NNUE::evaluate<NNUE::Big>(pos, true, &nnueComplexity);
|
||||
|
||||
int optimism = pos.this_thread()->optimism[stm];
|
||||
int optimism = workerThread.optimism[stm];
|
||||
|
||||
// Blend optimism and eval with nnue complexity and material imbalance
|
||||
optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512;
|
||||
|
@ -227,16 +240,16 @@ Value Eval::evaluate(const Position& pos) {
|
|||
// a string (suitable for outputting to stdout) that contains the detailed
|
||||
// descriptions and values of each evaluation term. Useful for debugging.
|
||||
// Trace scores are from white's point of view
|
||||
std::string Eval::trace(Position& pos) {
|
||||
std::string Eval::trace(Position& pos, Search::Worker& workerThread) {
|
||||
|
||||
if (pos.checkers())
|
||||
return "Final evaluation: none (in check)";
|
||||
|
||||
// Reset any global variable used in eval
|
||||
pos.this_thread()->bestValue = VALUE_ZERO;
|
||||
pos.this_thread()->rootSimpleEval = VALUE_ZERO;
|
||||
pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
|
||||
pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
|
||||
workerThread.iterBestValue = VALUE_ZERO;
|
||||
workerThread.rootSimpleEval = VALUE_ZERO;
|
||||
workerThread.optimism[WHITE] = VALUE_ZERO;
|
||||
workerThread.optimism[BLACK] = VALUE_ZERO;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
||||
|
@ -249,7 +262,7 @@ std::string Eval::trace(Position& pos) {
|
|||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n";
|
||||
|
||||
v = evaluate(pos);
|
||||
v = evaluate(pos, workerThread);
|
||||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)";
|
||||
ss << " [with scaled NNUE, ...]";
|
||||
|
|
|
@ -27,13 +27,18 @@
|
|||
namespace Stockfish {
|
||||
|
||||
class Position;
|
||||
class OptionsMap;
|
||||
|
||||
namespace Search {
|
||||
class Worker;
|
||||
}
|
||||
|
||||
namespace Eval {
|
||||
|
||||
std::string trace(Position& pos);
|
||||
std::string trace(Position& pos, Search::Worker& workerThread);
|
||||
|
||||
int simple_eval(const Position& pos, Color c);
|
||||
Value evaluate(const Position& pos);
|
||||
Value evaluate(const Position& pos, const Search::Worker& workerThread);
|
||||
|
||||
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
|
||||
// for the build process (profile-build and fishtest) to work. Do not change the
|
||||
|
@ -41,23 +46,28 @@ Value evaluate(const Position& pos);
|
|||
#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue"
|
||||
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
|
||||
|
||||
struct EvalFile {
|
||||
// UCI option name
|
||||
std::string optionName;
|
||||
// Default net name, will use one of the macros above
|
||||
std::string defaultName;
|
||||
// Selected net name, either via uci option or default
|
||||
std::string current;
|
||||
// Net description extracted from the net file
|
||||
std::string netDescription;
|
||||
};
|
||||
|
||||
namespace NNUE {
|
||||
|
||||
enum NetSize : int;
|
||||
|
||||
void init();
|
||||
void verify();
|
||||
using EvalFiles = std::unordered_map<Eval::NNUE::NetSize, EvalFile>;
|
||||
|
||||
EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles);
|
||||
void verify(const OptionsMap&, const EvalFiles&);
|
||||
|
||||
} // namespace NNUE
|
||||
|
||||
struct EvalFile {
|
||||
std::string option_name;
|
||||
std::string default_name;
|
||||
std::string selected_name;
|
||||
};
|
||||
|
||||
extern std::unordered_map<NNUE::NetSize, EvalFile> EvalFiles;
|
||||
|
||||
} // namespace Eval
|
||||
|
||||
} // namespace Stockfish
|
||||
|
|
19
src/main.cpp
19
src/main.cpp
|
@ -16,15 +16,13 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "evaluate.h"
|
||||
#include "misc.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "thread.h"
|
||||
#include "tune.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
|
@ -35,17 +33,16 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
std::cout << engine_info() << std::endl;
|
||||
|
||||
CommandLine::init(argc, argv);
|
||||
UCI::init(Options);
|
||||
Tune::init();
|
||||
Bitboards::init();
|
||||
Position::init();
|
||||
Threads.set(size_t(Options["Threads"]));
|
||||
Search::clear(); // After threads are up
|
||||
Eval::NNUE::init();
|
||||
|
||||
UCI::loop(argc, argv);
|
||||
UCI uci(argc, argv);
|
||||
|
||||
Tune::init(uci.options);
|
||||
|
||||
uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles);
|
||||
|
||||
uci.loop();
|
||||
|
||||
Threads.set(0);
|
||||
return 0;
|
||||
}
|
||||
|
|
15
src/misc.cpp
15
src/misc.cpp
|
@ -721,17 +721,13 @@ void bindThisThread(size_t idx) {
|
|||
#define GETCWD getcwd
|
||||
#endif
|
||||
|
||||
namespace CommandLine {
|
||||
|
||||
std::string argv0; // path+name of the executable binary, as given by argv[0]
|
||||
std::string binaryDirectory; // path of the executable directory
|
||||
std::string workingDirectory; // path of the working directory
|
||||
|
||||
void init([[maybe_unused]] int argc, char* argv[]) {
|
||||
CommandLine::CommandLine(int _argc, char** _argv) :
|
||||
argc(_argc),
|
||||
argv(_argv) {
|
||||
std::string pathSeparator;
|
||||
|
||||
// Extract the path+name of the executable binary
|
||||
argv0 = argv[0];
|
||||
std::string argv0 = argv[0];
|
||||
|
||||
#ifdef _WIN32
|
||||
pathSeparator = "\\";
|
||||
|
@ -766,7 +762,4 @@ void init([[maybe_unused]] int argc, char* argv[]) {
|
|||
binaryDirectory.replace(0, 1, workingDirectory);
|
||||
}
|
||||
|
||||
|
||||
} // namespace CommandLine
|
||||
|
||||
} // namespace Stockfish
|
||||
|
|
15
src/misc.h
15
src/misc.h
|
@ -176,12 +176,17 @@ namespace WinProcGroup {
|
|||
void bindThisThread(size_t idx);
|
||||
}
|
||||
|
||||
namespace CommandLine {
|
||||
void init(int argc, char* argv[]);
|
||||
|
||||
extern std::string binaryDirectory; // path of the executable directory
|
||||
extern std::string workingDirectory; // path of the working directory
|
||||
}
|
||||
struct CommandLine {
|
||||
public:
|
||||
CommandLine(int, char**);
|
||||
|
||||
int argc;
|
||||
char** argv;
|
||||
|
||||
std::string binaryDirectory; // path of the executable directory
|
||||
std::string workingDirectory; // path of the working directory
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
|
|
|
@ -26,8 +26,10 @@
|
|||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../evaluate.h"
|
||||
|
@ -51,8 +53,6 @@ AlignedPtr<Network<TransformedFeatureDimensionsBig, L2Big, L3Big>> network
|
|||
AlignedPtr<Network<TransformedFeatureDimensionsSmall, L2Small, L3Small>> networkSmall[LayerStacks];
|
||||
|
||||
// Evaluation function file names
|
||||
std::string fileName[2];
|
||||
std::string netDescription[2];
|
||||
|
||||
namespace Detail {
|
||||
|
||||
|
@ -136,10 +136,10 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st
|
|||
}
|
||||
|
||||
// Read network parameters
|
||||
static bool read_parameters(std::istream& stream, NetSize netSize) {
|
||||
static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) {
|
||||
|
||||
std::uint32_t hashValue;
|
||||
if (!read_header(stream, &hashValue, &netDescription[netSize]))
|
||||
if (!read_header(stream, &hashValue, &netDescription))
|
||||
return false;
|
||||
if (hashValue != HashValue[netSize])
|
||||
return false;
|
||||
|
@ -158,9 +158,10 @@ static bool read_parameters(std::istream& stream, NetSize netSize) {
|
|||
}
|
||||
|
||||
// Write network parameters
|
||||
static bool write_parameters(std::ostream& stream, NetSize netSize) {
|
||||
static bool
|
||||
write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) {
|
||||
|
||||
if (!write_header(stream, HashValue[netSize], netDescription[netSize]))
|
||||
if (!write_header(stream, HashValue[netSize], netDescription))
|
||||
return false;
|
||||
if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig))
|
||||
return false;
|
||||
|
@ -424,24 +425,30 @@ std::string trace(Position& pos) {
|
|||
|
||||
|
||||
// Load eval, from a file stream or a memory stream
|
||||
bool load_eval(const std::string name, std::istream& stream, NetSize netSize) {
|
||||
std::optional<std::string> load_eval(std::istream& stream, NetSize netSize) {
|
||||
|
||||
initialize(netSize);
|
||||
fileName[netSize] = name;
|
||||
return read_parameters(stream, netSize);
|
||||
std::string netDescription;
|
||||
return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
// Save eval, to a file stream or a memory stream
|
||||
bool save_eval(std::ostream& stream, NetSize netSize) {
|
||||
bool save_eval(std::ostream& stream,
|
||||
NetSize netSize,
|
||||
const std::string& name,
|
||||
const std::string& netDescription) {
|
||||
|
||||
if (fileName[netSize].empty())
|
||||
if (name.empty() || name == "None")
|
||||
return false;
|
||||
|
||||
return write_parameters(stream, netSize);
|
||||
return write_parameters(stream, netSize, netDescription);
|
||||
}
|
||||
|
||||
// Save eval, to a file given by its name
|
||||
bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
||||
bool save_eval(const std::optional<std::string>& filename,
|
||||
NetSize netSize,
|
||||
const std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile>& evalFiles) {
|
||||
|
||||
std::string actualFilename;
|
||||
std::string msg;
|
||||
|
@ -450,7 +457,7 @@ bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
|||
actualFilename = filename.value();
|
||||
else
|
||||
{
|
||||
if (EvalFiles.at(netSize).selected_name
|
||||
if (evalFiles.at(netSize).current
|
||||
!= (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig))
|
||||
{
|
||||
msg = "Failed to export a net. "
|
||||
|
@ -463,7 +470,8 @@ bool save_eval(const std::optional<std::string>& filename, NetSize netSize) {
|
|||
}
|
||||
|
||||
std::ofstream stream(actualFilename, std::ios_base::binary);
|
||||
bool saved = save_eval(stream, netSize);
|
||||
bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current,
|
||||
evalFiles.at(netSize).netDescription);
|
||||
|
||||
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
|
||||
|
||||
|
|
|
@ -26,14 +26,20 @@
|
|||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_feature_transformer.h"
|
||||
#include "../types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
class Position;
|
||||
|
||||
namespace Eval {
|
||||
struct EvalFile;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
@ -73,9 +79,14 @@ template<NetSize Net_Size>
|
|||
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
|
||||
void hint_common_parent_position(const Position& pos);
|
||||
|
||||
bool load_eval(const std::string name, std::istream& stream, NetSize netSize);
|
||||
bool save_eval(std::ostream& stream, NetSize netSize);
|
||||
bool save_eval(const std::optional<std::string>& filename, NetSize netSize);
|
||||
std::optional<std::string> load_eval(std::istream& stream, NetSize netSize);
|
||||
bool save_eval(std::ostream& stream,
|
||||
NetSize netSize,
|
||||
const std::string& name,
|
||||
const std::string& netDescription);
|
||||
bool save_eval(const std::optional<std::string>& filename,
|
||||
NetSize netSize,
|
||||
const std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile>&);
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include "position.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
|
@ -36,7 +35,6 @@
|
|||
#include "movegen.h"
|
||||
#include "nnue/nnue_common.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "thread.h"
|
||||
#include "tt.h"
|
||||
#include "uci.h"
|
||||
|
||||
|
@ -87,7 +85,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
|
||||
|
||||
Position p;
|
||||
p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
|
||||
p.set(pos.fen(), pos.is_chess960(), &st);
|
||||
Tablebases::ProbeState s1, s2;
|
||||
Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
|
||||
int dtz = Tablebases::probe_dtz(p, &s2);
|
||||
|
@ -160,7 +158,7 @@ void Position::init() {
|
|||
// Initializes the position object with the given FEN string.
|
||||
// This function is not very robust - make sure that input FENs are correct,
|
||||
// this is assumed to be the responsibility of the GUI.
|
||||
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
|
||||
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
|
||||
/*
|
||||
A FEN string defines a particular position using only the ASCII character set.
|
||||
|
||||
|
@ -286,8 +284,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||
// handle also common incorrect FEN with fullmove = 0.
|
||||
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
|
||||
|
||||
chess960 = isChess960;
|
||||
thisThread = th;
|
||||
chess960 = isChess960;
|
||||
set_state();
|
||||
|
||||
assert(pos_is_ok());
|
||||
|
@ -388,7 +385,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) {
|
|||
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
|
||||
+ char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
|
||||
|
||||
return set(fenStr, false, si, nullptr);
|
||||
return set(fenStr, false, si);
|
||||
}
|
||||
|
||||
|
||||
|
@ -667,7 +664,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||
assert(m.is_ok());
|
||||
assert(&newSt != st);
|
||||
|
||||
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
|
||||
Key k = st->key ^ Zobrist::side;
|
||||
|
||||
// Copy some fields of the old state to our new StateInfo object except the
|
||||
|
@ -959,7 +955,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
|
|||
|
||||
// Used to do a "null move": it flips
|
||||
// the side to move without executing any move on the board.
|
||||
void Position::do_null_move(StateInfo& newSt) {
|
||||
void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) {
|
||||
|
||||
assert(!checkers());
|
||||
assert(&newSt != st);
|
||||
|
@ -982,7 +978,7 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||
|
||||
st->key ^= Zobrist::side;
|
||||
++st->rule50;
|
||||
prefetch(TT.first_entry(key()));
|
||||
prefetch(tt.first_entry(key()));
|
||||
|
||||
st->pliesFromNull = 0;
|
||||
|
||||
|
@ -1235,7 +1231,7 @@ void Position::flip() {
|
|||
std::getline(ss, token); // Half and full moves
|
||||
f += token;
|
||||
|
||||
set(f, is_chess960(), st, this_thread());
|
||||
set(f, is_chess960(), st);
|
||||
|
||||
assert(pos_is_ok());
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
namespace Stockfish {
|
||||
|
||||
class TranspositionTable;
|
||||
|
||||
// StateInfo struct stores information needed to restore a Position object to
|
||||
// its previous state when we retract a move. Whenever a move is made on the
|
||||
// board (by calling Position::do_move), a StateInfo object must be passed.
|
||||
|
@ -75,8 +77,6 @@ using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
|||
// pieces, side to move, hash keys, castling info, etc. Important methods are
|
||||
// do_move() and undo_move(), used by the search to update node info when
|
||||
// traversing the search tree.
|
||||
class Thread;
|
||||
|
||||
class Position {
|
||||
public:
|
||||
static void init();
|
||||
|
@ -86,7 +86,7 @@ class Position {
|
|||
Position& operator=(const Position&) = delete;
|
||||
|
||||
// FEN string input/output
|
||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si);
|
||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||
std::string fen() const;
|
||||
|
||||
|
@ -139,7 +139,7 @@ class Position {
|
|||
void do_move(Move m, StateInfo& newSt);
|
||||
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
||||
void undo_move(Move m);
|
||||
void do_null_move(StateInfo& newSt);
|
||||
void do_null_move(StateInfo& newSt, TranspositionTable& tt);
|
||||
void undo_null_move();
|
||||
|
||||
// Static Exchange Evaluation
|
||||
|
@ -152,16 +152,15 @@ class Position {
|
|||
Key pawn_key() const;
|
||||
|
||||
// Other properties of the position
|
||||
Color side_to_move() const;
|
||||
int game_ply() const;
|
||||
bool is_chess960() const;
|
||||
Thread* this_thread() const;
|
||||
bool is_draw(int ply) const;
|
||||
bool has_game_cycle(int ply) const;
|
||||
bool has_repeated() const;
|
||||
int rule50_count() const;
|
||||
Value non_pawn_material(Color c) const;
|
||||
Value non_pawn_material() const;
|
||||
Color side_to_move() const;
|
||||
int game_ply() const;
|
||||
bool is_chess960() const;
|
||||
bool is_draw(int ply) const;
|
||||
bool has_game_cycle(int ply) const;
|
||||
bool has_repeated() const;
|
||||
int rule50_count() const;
|
||||
Value non_pawn_material(Color c) const;
|
||||
Value non_pawn_material() const;
|
||||
|
||||
// Position consistency check, for debugging
|
||||
bool pos_is_ok() const;
|
||||
|
@ -194,7 +193,6 @@ class Position {
|
|||
int castlingRightsMask[SQUARE_NB];
|
||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||
Thread* thisThread;
|
||||
StateInfo* st;
|
||||
int gamePly;
|
||||
Color sideToMove;
|
||||
|
@ -328,8 +326,6 @@ inline bool Position::capture_stage(Move m) const {
|
|||
|
||||
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
||||
|
||||
inline Thread* Position::this_thread() const { return thisThread; }
|
||||
|
||||
inline void Position::put_piece(Piece pc, Square s) {
|
||||
|
||||
board[s] = pc;
|
||||
|
|
506
src/search.cpp
506
src/search.cpp
File diff suppressed because it is too large
Load diff
155
src/search.h
155
src/search.h
|
@ -19,19 +19,37 @@
|
|||
#ifndef SEARCH_H_INCLUDED
|
||||
#define SEARCH_H_INCLUDED
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "misc.h"
|
||||
#include "movepick.h"
|
||||
#include "position.h"
|
||||
#include "timeman.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class Position;
|
||||
// Different node types, used as a template parameter
|
||||
enum NodeType {
|
||||
NonPV,
|
||||
PV,
|
||||
Root
|
||||
};
|
||||
|
||||
class TranspositionTable;
|
||||
class ThreadPool;
|
||||
class OptionsMap;
|
||||
class UCI;
|
||||
|
||||
namespace Search {
|
||||
|
||||
// Called at startup to initialize various lookup tables, after program startup
|
||||
void init(int);
|
||||
|
||||
// Stack struct keeps track of the information we need to remember from nodes
|
||||
// shallower and deeper in the tree during the search. Each search thread has
|
||||
|
@ -61,7 +79,7 @@ struct RootMove {
|
|||
|
||||
explicit RootMove(Move m) :
|
||||
pv(1, m) {}
|
||||
bool extract_ponder_from_tt(Position& pos);
|
||||
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
|
||||
bool operator==(const Move& m) const { return pv[0] == m; }
|
||||
// Sort in descending order
|
||||
bool operator<(const RootMove& m) const {
|
||||
|
@ -85,7 +103,6 @@ using RootMoves = std::vector<RootMove>;
|
|||
|
||||
// LimitsType struct stores information sent by GUI about available time to
|
||||
// search the current move, maximum depth/time, or if we are in analysis mode.
|
||||
|
||||
struct LimitsType {
|
||||
|
||||
// Init explicitly due to broken value-initialization of non POD in MSVC
|
||||
|
@ -103,10 +120,136 @@ struct LimitsType {
|
|||
int64_t nodes;
|
||||
};
|
||||
|
||||
extern LimitsType Limits;
|
||||
|
||||
void init();
|
||||
void clear();
|
||||
// The UCI stores the uci options, thread pool, and transposition table.
|
||||
// This struct is used to easily forward data to the Search::Worker class.
|
||||
struct SharedState {
|
||||
SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) :
|
||||
options(o),
|
||||
threads(tp),
|
||||
tt(t) {}
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
};
|
||||
|
||||
class Worker;
|
||||
|
||||
// Null Object Pattern, implement a common interface
|
||||
// for the SearchManagers. A Null Object will be given to
|
||||
// non-mainthread workers.
|
||||
class ISearchManager {
|
||||
public:
|
||||
virtual ~ISearchManager() {}
|
||||
virtual void check_time(Search::Worker&) = 0;
|
||||
};
|
||||
|
||||
// SearchManager manages the search from the main thread. It is responsible for
|
||||
// keeping track of the time, and storing data strictly related to the main thread.
|
||||
class SearchManager: public ISearchManager {
|
||||
public:
|
||||
void check_time(Search::Worker& worker) override;
|
||||
|
||||
Stockfish::TimeManagement tm;
|
||||
int callsCnt;
|
||||
std::atomic_bool ponder;
|
||||
|
||||
double previousTimeReduction;
|
||||
Value bestPreviousScore;
|
||||
Value bestPreviousAverageScore;
|
||||
Value iterValue[4];
|
||||
bool stopOnPonderhit;
|
||||
|
||||
size_t id;
|
||||
};
|
||||
|
||||
class NullSearchManager: public ISearchManager {
|
||||
public:
|
||||
void check_time(Search::Worker&) override {}
|
||||
};
|
||||
|
||||
// Search::Worker is the class that does the actual search.
|
||||
// It is instantiated once per thread, and it is responsible for keeping track
|
||||
// of the search history, and storing data required for the search.
|
||||
class Worker {
|
||||
public:
|
||||
Worker(SharedState&, std::unique_ptr<ISearchManager>, size_t);
|
||||
|
||||
// Reset histories, usually before a new game
|
||||
void clear();
|
||||
|
||||
// Called when the program receives the UCI 'go'
|
||||
// command. It searches from the root position and outputs the "bestmove".
|
||||
void start_searching();
|
||||
|
||||
bool is_mainthread() const { return thread_idx == 0; }
|
||||
|
||||
// Public because evaluate uses this
|
||||
Value iterBestValue, optimism[COLOR_NB];
|
||||
Value rootSimpleEval;
|
||||
|
||||
// Public because they need to be updatable by the stats
|
||||
CounterMoveHistory counterMoves;
|
||||
ButterflyHistory mainHistory;
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
CorrectionHistory correctionHistory;
|
||||
|
||||
private:
|
||||
void iterative_deepening();
|
||||
|
||||
// Main search function for both PV and non-PV nodes
|
||||
template<NodeType nodeType>
|
||||
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
|
||||
|
||||
// Quiescence search function, which is called by the main search
|
||||
template<NodeType nodeType>
|
||||
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
|
||||
|
||||
Depth reduction(bool i, Depth d, int mn, int delta) {
|
||||
int reductionScale = reductions[d] * reductions[mn];
|
||||
return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024
|
||||
+ (!i && reductionScale > 880);
|
||||
}
|
||||
|
||||
// Get a pointer to the search manager, only allowed to be called by the
|
||||
// main thread.
|
||||
SearchManager* main_manager() const {
|
||||
assert(thread_idx == 0);
|
||||
return static_cast<SearchManager*>(manager.get());
|
||||
}
|
||||
|
||||
LimitsType limits;
|
||||
|
||||
size_t pvIdx, pvLast;
|
||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||
int selDepth, nmpMinPly;
|
||||
|
||||
Position rootPos;
|
||||
StateInfo rootState;
|
||||
RootMoves rootMoves;
|
||||
Depth rootDepth, completedDepth;
|
||||
Value rootDelta;
|
||||
|
||||
size_t thread_idx;
|
||||
|
||||
// Reductions lookup table initialized at startup
|
||||
int reductions[MAX_MOVES]; // [depth or moveNumber]
|
||||
|
||||
// The main thread has a SearchManager, the others have a NullSearchManager
|
||||
std::unique_ptr<ISearchManager> manager;
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
|
||||
friend class Stockfish::ThreadPool;
|
||||
friend class Stockfish::UCI;
|
||||
friend class SearchManager;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Search
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
#include "../position.h"
|
||||
#include "../search.h"
|
||||
#include "../types.h"
|
||||
#include "../uci.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <fcntl.h>
|
||||
|
@ -1574,7 +1573,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
|||
// Use the DTZ tables to rank root moves.
|
||||
//
|
||||
// A return value false indicates that not all probes were successful.
|
||||
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
||||
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) {
|
||||
|
||||
ProbeState result = OK;
|
||||
StateInfo st;
|
||||
|
@ -1585,7 +1584,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
|||
// Check whether a position was repeated since the last zeroing move.
|
||||
bool rep = pos.has_repeated();
|
||||
|
||||
int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
|
||||
int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1;
|
||||
|
||||
// Probe and rank each move
|
||||
for (auto& m : rootMoves)
|
||||
|
@ -1647,7 +1646,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
|
|||
// This is a fallback for the case that some or all DTZ tables are missing.
|
||||
//
|
||||
// A return value false indicates that not all probes were successful.
|
||||
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
||||
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) {
|
||||
|
||||
static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
|
||||
|
||||
|
@ -1655,7 +1654,6 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
|
|||
StateInfo st;
|
||||
WDLScore wdl;
|
||||
|
||||
bool rule50 = Options["Syzygy50MoveRule"];
|
||||
|
||||
// Probe and rank each move
|
||||
for (auto& m : rootMoves)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
namespace Stockfish {
|
||||
class Position;
|
||||
class OptionsMap;
|
||||
}
|
||||
|
||||
namespace Stockfish::Tablebases {
|
||||
|
@ -47,12 +48,13 @@ enum ProbeState {
|
|||
|
||||
extern int MaxCardinality;
|
||||
|
||||
|
||||
void init(const std::string& paths);
|
||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||
int probe_dtz(Position& pos, ProbeState* result);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
|
||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
|
||||
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||
void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves);
|
||||
|
||||
} // namespace Stockfish::Tablebases
|
||||
|
||||
|
|
134
src/thread.cpp
134
src/thread.cpp
|
@ -23,9 +23,8 @@
|
|||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <initializer_list>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "evaluate.h"
|
||||
|
@ -33,18 +32,21 @@
|
|||
#include "movegen.h"
|
||||
#include "search.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "timeman.h"
|
||||
#include "tt.h"
|
||||
#include "uci.h"
|
||||
#include "types.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
ThreadPool Threads; // Global object
|
||||
|
||||
|
||||
// Constructor launches the thread and waits until it goes to sleep
|
||||
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
||||
Thread::Thread(size_t n) :
|
||||
Thread::Thread(Search::SharedState& sharedState,
|
||||
std::unique_ptr<Search::ISearchManager> sm,
|
||||
size_t n) :
|
||||
worker(std::make_unique<Search::Worker>(sharedState, std::move(sm), n)),
|
||||
idx(n),
|
||||
nthreads(sharedState.options["Threads"]),
|
||||
stdThread(&Thread::idle_loop, this) {
|
||||
|
||||
wait_for_search_finished();
|
||||
|
@ -62,24 +64,6 @@ Thread::~Thread() {
|
|||
stdThread.join();
|
||||
}
|
||||
|
||||
|
||||
// Reset histories, usually before a new game
|
||||
void Thread::clear() {
|
||||
|
||||
counterMoves.fill(Move::none());
|
||||
mainHistory.fill(0);
|
||||
captureHistory.fill(0);
|
||||
pawnHistory.fill(0);
|
||||
correctionHistory.fill(0);
|
||||
|
||||
for (bool inCheck : {false, true})
|
||||
for (StatsType c : {NoCaptures, Captures})
|
||||
for (auto& to : continuationHistory[inCheck][c])
|
||||
for (auto& h : to)
|
||||
h->fill(-71);
|
||||
}
|
||||
|
||||
|
||||
// Wakes up the thread that will start the search
|
||||
void Thread::start_searching() {
|
||||
mutex.lock();
|
||||
|
@ -108,7 +92,7 @@ void Thread::idle_loop() {
|
|||
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
|
||||
// just check if running threads are below a threshold, in this case, all this
|
||||
// NUMA machinery is not needed.
|
||||
if (Options["Threads"] > 8)
|
||||
if (nthreads > 8)
|
||||
WinProcGroup::bindThisThread(idx);
|
||||
|
||||
while (true)
|
||||
|
@ -123,36 +107,41 @@ void Thread::idle_loop() {
|
|||
|
||||
lk.unlock();
|
||||
|
||||
search();
|
||||
worker->start_searching();
|
||||
}
|
||||
}
|
||||
|
||||
// Creates/destroys threads to match the requested number.
|
||||
// Created and launched threads will immediately go to sleep in idle_loop.
|
||||
// Upon resizing, threads are recreated to allow for binding if necessary.
|
||||
void ThreadPool::set(size_t requested) {
|
||||
void ThreadPool::set(Search::SharedState sharedState) {
|
||||
|
||||
if (threads.size() > 0) // destroy any existing thread(s)
|
||||
{
|
||||
main()->wait_for_search_finished();
|
||||
main_thread()->wait_for_search_finished();
|
||||
|
||||
while (threads.size() > 0)
|
||||
delete threads.back(), threads.pop_back();
|
||||
}
|
||||
|
||||
const size_t requested = sharedState.options["Threads"];
|
||||
|
||||
if (requested > 0) // create new thread(s)
|
||||
{
|
||||
threads.push_back(new MainThread(0));
|
||||
threads.push_back(new Thread(
|
||||
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::SearchManager()), 0));
|
||||
|
||||
|
||||
while (threads.size() < requested)
|
||||
threads.push_back(new Thread(threads.size()));
|
||||
threads.push_back(new Thread(
|
||||
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::NullSearchManager()),
|
||||
threads.size()));
|
||||
clear();
|
||||
|
||||
// Reallocate the hash with the new threadpool size
|
||||
TT.resize(size_t(Options["Hash"]));
|
||||
main_thread()->wait_for_search_finished();
|
||||
|
||||
// Init thread number dependent search params.
|
||||
Search::init();
|
||||
// Reallocate the hash with the new threadpool size
|
||||
sharedState.tt.resize(sharedState.options["Hash"], requested);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,28 +150,31 @@ void ThreadPool::set(size_t requested) {
|
|||
void ThreadPool::clear() {
|
||||
|
||||
for (Thread* th : threads)
|
||||
th->clear();
|
||||
th->worker->clear();
|
||||
|
||||
main()->callsCnt = 0;
|
||||
main()->bestPreviousScore = VALUE_INFINITE;
|
||||
main()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||
main()->previousTimeReduction = 1.0;
|
||||
main_manager()->callsCnt = 0;
|
||||
main_manager()->bestPreviousScore = VALUE_INFINITE;
|
||||
main_manager()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||
main_manager()->previousTimeReduction = 1.0;
|
||||
main_manager()->tm.clear();
|
||||
}
|
||||
|
||||
|
||||
// Wakes up main thread waiting in idle_loop() and
|
||||
// returns immediately. Main thread will wake up other threads and start the search.
|
||||
void ThreadPool::start_thinking(Position& pos,
|
||||
StateListPtr& states,
|
||||
const Search::LimitsType& limits,
|
||||
bool ponderMode) {
|
||||
void ThreadPool::start_thinking(const OptionsMap& options,
|
||||
Position& pos,
|
||||
StateListPtr& states,
|
||||
Search::LimitsType limits,
|
||||
bool ponderMode) {
|
||||
|
||||
main()->wait_for_search_finished();
|
||||
main_thread()->wait_for_search_finished();
|
||||
|
||||
main_manager()->stopOnPonderhit = stop = false;
|
||||
main_manager()->ponder = ponderMode;
|
||||
|
||||
increaseDepth = true;
|
||||
|
||||
main()->stopOnPonderhit = stop = false;
|
||||
increaseDepth = true;
|
||||
main()->ponder = ponderMode;
|
||||
Search::Limits = limits;
|
||||
Search::RootMoves rootMoves;
|
||||
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
|
@ -191,7 +183,7 @@ void ThreadPool::start_thinking(Position& pos,
|
|||
rootMoves.emplace_back(m);
|
||||
|
||||
if (!rootMoves.empty())
|
||||
Tablebases::rank_root_moves(pos, rootMoves);
|
||||
Tablebases::rank_root_moves(options, pos, rootMoves);
|
||||
|
||||
// After ownership transfer 'states' becomes empty, so if we stop the search
|
||||
// and call 'go' again without setting a new position states.get() == nullptr.
|
||||
|
@ -207,15 +199,17 @@ void ThreadPool::start_thinking(Position& pos,
|
|||
// since they are read-only.
|
||||
for (Thread* th : threads)
|
||||
{
|
||||
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
||||
th->rootDepth = th->completedDepth = 0;
|
||||
th->rootMoves = rootMoves;
|
||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
|
||||
th->rootState = setupStates->back();
|
||||
th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move());
|
||||
th->worker->limits = limits;
|
||||
th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly =
|
||||
th->worker->bestMoveChanges = 0;
|
||||
th->worker->rootDepth = th->worker->completedDepth = 0;
|
||||
th->worker->rootMoves = rootMoves;
|
||||
th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);
|
||||
th->worker->rootState = setupStates->back();
|
||||
th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move());
|
||||
}
|
||||
|
||||
main()->start_searching();
|
||||
main_thread()->start_searching();
|
||||
}
|
||||
|
||||
Thread* ThreadPool::get_best_thread() const {
|
||||
|
@ -226,30 +220,32 @@ Thread* ThreadPool::get_best_thread() const {
|
|||
|
||||
// Find the minimum score of all threads
|
||||
for (Thread* th : threads)
|
||||
minScore = std::min(minScore, th->rootMoves[0].score);
|
||||
minScore = std::min(minScore, th->worker->rootMoves[0].score);
|
||||
|
||||
// Vote according to score and depth, and select the best thread
|
||||
auto thread_value = [minScore](Thread* th) {
|
||||
return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
||||
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
|
||||
};
|
||||
|
||||
for (Thread* th : threads)
|
||||
votes[th->rootMoves[0].pv[0]] += thread_value(th);
|
||||
votes[th->worker->rootMoves[0].pv[0]] += thread_value(th);
|
||||
|
||||
for (Thread* th : threads)
|
||||
if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||
if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||
{
|
||||
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
||||
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
||||
if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score)
|
||||
bestThread = th;
|
||||
}
|
||||
else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||
|| (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|
||||
|| (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
|
||||
&& thread_value(th) * int(th->rootMoves[0].pv.size() > 2)
|
||||
else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||
|| (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||
&& (votes[th->worker->rootMoves[0].pv[0]]
|
||||
> votes[bestThread->worker->rootMoves[0].pv[0]]
|
||||
|| (votes[th->worker->rootMoves[0].pv[0]]
|
||||
== votes[bestThread->worker->rootMoves[0].pv[0]]
|
||||
&& thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2)
|
||||
> thread_value(bestThread)
|
||||
* int(bestThread->rootMoves[0].pv.size() > 2)))))
|
||||
* int(bestThread->worker->rootMoves[0].pv.size() > 2)))))
|
||||
bestThread = th;
|
||||
|
||||
return bestThread;
|
||||
|
@ -257,7 +253,7 @@ Thread* ThreadPool::get_best_thread() const {
|
|||
|
||||
|
||||
// Start non-main threads
|
||||
|
||||
// Will be invoked by main thread after it has started searching
|
||||
void ThreadPool::start_searching() {
|
||||
|
||||
for (Thread* th : threads)
|
||||
|
|
115
src/thread.h
115
src/thread.h
|
@ -23,91 +23,76 @@
|
|||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "movepick.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "thread_win32_osx.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
// Thread class keeps together all the thread-related stuff.
|
||||
class Thread {
|
||||
class OptionsMap;
|
||||
using Value = int;
|
||||
|
||||
// Abstraction of a thread. It contains a pointer to the worker and a native thread.
|
||||
// After construction, the native thread is started with idle_loop()
|
||||
// waiting for a signal to start searching.
|
||||
// When the signal is received, the thread starts searching and when
|
||||
// the search is finished, it goes back to idle_loop() waiting for a new signal.
|
||||
class Thread {
|
||||
public:
|
||||
Thread(Search::SharedState&, std::unique_ptr<Search::ISearchManager>, size_t);
|
||||
virtual ~Thread();
|
||||
|
||||
void idle_loop();
|
||||
void start_searching();
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
|
||||
std::unique_ptr<Search::Worker> worker;
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
size_t idx;
|
||||
size_t idx, nthreads;
|
||||
bool exit = false, searching = true; // Set before starting std::thread
|
||||
NativeThread stdThread;
|
||||
|
||||
public:
|
||||
explicit Thread(size_t);
|
||||
virtual ~Thread();
|
||||
virtual void search();
|
||||
void clear();
|
||||
void idle_loop();
|
||||
void start_searching();
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
|
||||
size_t pvIdx, pvLast;
|
||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||
int selDepth, nmpMinPly;
|
||||
Value bestValue;
|
||||
|
||||
int optimism[COLOR_NB];
|
||||
|
||||
Position rootPos;
|
||||
StateInfo rootState;
|
||||
Search::RootMoves rootMoves;
|
||||
Depth rootDepth, completedDepth;
|
||||
int rootDelta;
|
||||
Value rootSimpleEval;
|
||||
CounterMoveHistory counterMoves;
|
||||
ButterflyHistory mainHistory;
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
CorrectionHistory correctionHistory;
|
||||
};
|
||||
|
||||
|
||||
// MainThread is a derived class specific for main thread
|
||||
struct MainThread: public Thread {
|
||||
|
||||
using Thread::Thread;
|
||||
|
||||
void search() override;
|
||||
void check_time();
|
||||
|
||||
double previousTimeReduction;
|
||||
Value bestPreviousScore;
|
||||
Value bestPreviousAverageScore;
|
||||
Value iterValue[4];
|
||||
int callsCnt;
|
||||
bool stopOnPonderhit;
|
||||
std::atomic_bool ponder;
|
||||
};
|
||||
|
||||
|
||||
// ThreadPool struct handles all the threads-related stuff like init, starting,
|
||||
// parking and, most importantly, launching a thread. All the access to threads
|
||||
// is done through this class.
|
||||
struct ThreadPool {
|
||||
class ThreadPool {
|
||||
|
||||
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
||||
public:
|
||||
~ThreadPool() {
|
||||
// destroy any existing thread(s)
|
||||
if (threads.size() > 0)
|
||||
{
|
||||
main_thread()->wait_for_search_finished();
|
||||
|
||||
while (threads.size() > 0)
|
||||
delete threads.back(), threads.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false);
|
||||
void clear();
|
||||
void set(size_t);
|
||||
void set(Search::SharedState);
|
||||
|
||||
MainThread* main() const { return static_cast<MainThread*>(threads.front()); }
|
||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
||||
Thread* get_best_thread() const;
|
||||
void start_searching();
|
||||
void wait_for_search_finished() const;
|
||||
Search::SearchManager* main_manager() const {
|
||||
return static_cast<Search::SearchManager*>(main_thread()->worker.get()->manager.get());
|
||||
};
|
||||
Thread* main_thread() const { return threads.front(); }
|
||||
uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); }
|
||||
uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); }
|
||||
Thread* get_best_thread() const;
|
||||
void start_searching();
|
||||
void wait_for_search_finished() const;
|
||||
|
||||
std::atomic_bool stop, increaseDepth;
|
||||
|
||||
|
@ -122,17 +107,15 @@ struct ThreadPool {
|
|||
StateListPtr setupStates;
|
||||
std::vector<Thread*> threads;
|
||||
|
||||
uint64_t accumulate(std::atomic<uint64_t> Thread::*member) const {
|
||||
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::*member) const {
|
||||
|
||||
uint64_t sum = 0;
|
||||
for (Thread* th : threads)
|
||||
sum += (th->*member).load(std::memory_order_relaxed);
|
||||
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
extern ThreadPool Threads;
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef THREAD_H_INCLUDED
|
||||
|
|
|
@ -30,31 +30,35 @@
|
|||
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
||||
|
||||
#include <pthread.h>
|
||||
#include <functional>
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
template<class T, class P = std::pair<T*, void (T::*)()>>
|
||||
void* start_routine(void* ptr) {
|
||||
P* p = reinterpret_cast<P*>(ptr);
|
||||
(p->first->*(p->second))(); // Call member function pointer
|
||||
delete p;
|
||||
// free function to be passed to pthread_create()
|
||||
inline void* start_routine(void* ptr) {
|
||||
auto func = reinterpret_cast<std::function<void()>*>(ptr);
|
||||
(*func)(); // Call the function
|
||||
delete func;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class NativeThread {
|
||||
|
||||
pthread_t thread;
|
||||
|
||||
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
public:
|
||||
template<class T, class P = std::pair<T*, void (T::*)()>>
|
||||
explicit NativeThread(void (T::*fun)(), T* obj) {
|
||||
template<class Function, class... Args>
|
||||
explicit NativeThread(Function&& fun, Args&&... args) {
|
||||
auto func = new std::function<void()>(
|
||||
std::bind(std::forward<Function>(fun), std::forward<Args>(args)...));
|
||||
|
||||
pthread_attr_t attr_storage, *attr = &attr_storage;
|
||||
pthread_attr_init(attr);
|
||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
||||
pthread_create(&thread, attr, start_routine, func);
|
||||
}
|
||||
|
||||
void join() { pthread_join(thread, nullptr); }
|
||||
};
|
||||
|
||||
|
|
|
@ -19,30 +19,47 @@
|
|||
#include "timeman.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "search.h"
|
||||
#include "uci.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
TimeManagement Time; // Our global time management object
|
||||
|
||||
TimePoint TimeManagement::optimum() const { return optimumTime; }
|
||||
TimePoint TimeManagement::maximum() const { return maximumTime; }
|
||||
TimePoint TimeManagement::elapsed(size_t nodes) const {
|
||||
return useNodesTime ? TimePoint(nodes) : now() - startTime;
|
||||
}
|
||||
|
||||
void TimeManagement::clear() {
|
||||
availableNodes = 0; // When in 'nodes as time' mode
|
||||
}
|
||||
|
||||
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
|
||||
assert(useNodesTime);
|
||||
availableNodes += nodes;
|
||||
}
|
||||
|
||||
// Called at the beginning of the search and calculates
|
||||
// the bounds of time allowed for the current game ply. We currently support:
|
||||
// 1) x basetime (+ z increment)
|
||||
// 2) x moves in y seconds (+ z increment)
|
||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
||||
|
||||
void TimeManagement::init(Search::LimitsType& limits,
|
||||
Color us,
|
||||
int ply,
|
||||
const OptionsMap& options) {
|
||||
// If we have no time, no need to initialize TM, except for the start time,
|
||||
// which is used by movetime.
|
||||
startTime = limits.startTime;
|
||||
if (limits.time[us] == 0)
|
||||
return;
|
||||
|
||||
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
||||
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
||||
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
|
||||
TimePoint npmsec = TimePoint(options["nodestime"]);
|
||||
|
||||
// optScale is a percentage of available time to use for the current move.
|
||||
// maxScale is a multiplier applied to optimumTime.
|
||||
|
@ -54,6 +71,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||
// must be much lower than the real engine speed.
|
||||
if (npmsec)
|
||||
{
|
||||
useNodesTime = true;
|
||||
|
||||
if (!availableNodes) // Only once at game start
|
||||
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
||||
|
||||
|
@ -100,7 +119,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||
maximumTime =
|
||||
TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
|
||||
|
||||
if (Options["Ponder"])
|
||||
if (options["Ponder"])
|
||||
optimumTime += optimumTime / 4;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,35 +19,41 @@
|
|||
#ifndef TIMEMAN_H_INCLUDED
|
||||
#define TIMEMAN_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "misc.h"
|
||||
#include "search.h"
|
||||
#include "thread.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
|
||||
namespace Search {
|
||||
struct LimitsType;
|
||||
}
|
||||
|
||||
// The TimeManagement class computes the optimal time to think depending on
|
||||
// the maximum available time, the game move number, and other parameters.
|
||||
class TimeManagement {
|
||||
public:
|
||||
void init(Search::LimitsType& limits, Color us, int ply);
|
||||
TimePoint optimum() const { return optimumTime; }
|
||||
TimePoint maximum() const { return maximumTime; }
|
||||
TimePoint elapsed() const {
|
||||
return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime;
|
||||
}
|
||||
void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options);
|
||||
|
||||
int64_t availableNodes; // When in 'nodes as time' mode
|
||||
TimePoint optimum() const;
|
||||
TimePoint maximum() const;
|
||||
TimePoint elapsed(std::size_t nodes) const;
|
||||
|
||||
void clear();
|
||||
void advance_nodes_time(std::int64_t nodes);
|
||||
|
||||
private:
|
||||
TimePoint startTime;
|
||||
TimePoint optimumTime;
|
||||
TimePoint maximumTime;
|
||||
};
|
||||
|
||||
extern TimeManagement Time;
|
||||
std::int64_t availableNodes = 0; // When in 'nodes as time' mode
|
||||
bool useNodesTime = false; // True if we are in 'nodes as time' mode
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
|
|
31
src/tt.cpp
31
src/tt.cpp
|
@ -26,16 +26,13 @@
|
|||
#include <vector>
|
||||
|
||||
#include "misc.h"
|
||||
#include "thread.h"
|
||||
#include "uci.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
TranspositionTable TT; // Our global transposition table
|
||||
|
||||
// Populates the TTEntry with a new node's data, possibly
|
||||
// overwriting an old position. The update is not atomic and can be racy.
|
||||
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
||||
void TTEntry::save(
|
||||
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
|
||||
|
||||
// Preserve any existing move for the same position
|
||||
if (m || uint16_t(k) != key16)
|
||||
|
@ -49,7 +46,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
|
|||
|
||||
key16 = uint16_t(k);
|
||||
depth8 = uint8_t(d - DEPTH_OFFSET);
|
||||
genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b);
|
||||
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
|
||||
value16 = int16_t(v);
|
||||
eval16 = int16_t(ev);
|
||||
}
|
||||
|
@ -59,10 +56,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
|
|||
// Sets the size of the transposition table,
|
||||
// measured in megabytes. Transposition table consists of a power of 2 number
|
||||
// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
||||
void TranspositionTable::resize(size_t mbSize) {
|
||||
|
||||
Threads.main()->wait_for_search_finished();
|
||||
|
||||
void TranspositionTable::resize(size_t mbSize, int threadCount) {
|
||||
aligned_large_pages_free(table);
|
||||
|
||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||
|
@ -74,28 +68,25 @@ void TranspositionTable::resize(size_t mbSize) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
clear();
|
||||
clear(threadCount);
|
||||
}
|
||||
|
||||
|
||||
// Initializes the entire transposition table to zero,
|
||||
// in a multi-threaded way.
|
||||
void TranspositionTable::clear() {
|
||||
|
||||
void TranspositionTable::clear(size_t threadCount) {
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx)
|
||||
for (size_t idx = 0; idx < size_t(threadCount); ++idx)
|
||||
{
|
||||
threads.emplace_back([this, idx]() {
|
||||
threads.emplace_back([this, idx, threadCount]() {
|
||||
// Thread binding gives faster search on systems with a first-touch policy
|
||||
if (Options["Threads"] > 8)
|
||||
if (threadCount > 8)
|
||||
WinProcGroup::bindThisThread(idx);
|
||||
|
||||
// Each thread will zero its part of the hash table
|
||||
const size_t stride = size_t(clusterCount / Options["Threads"]),
|
||||
start = size_t(stride * idx),
|
||||
len =
|
||||
idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start;
|
||||
const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx),
|
||||
len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start;
|
||||
|
||||
std::memset(&table[start], 0, len * sizeof(Cluster));
|
||||
});
|
||||
|
|
14
src/tt.h
14
src/tt.h
|
@ -45,7 +45,7 @@ struct TTEntry {
|
|||
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
|
||||
bool is_pv() const { return bool(genBound8 & 0x4); }
|
||||
Bound bound() const { return Bound(genBound8 & 0x3); }
|
||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
|
||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
|
||||
|
||||
private:
|
||||
friend class TranspositionTable;
|
||||
|
@ -88,23 +88,23 @@ class TranspositionTable {
|
|||
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
|
||||
TTEntry* probe(const Key key, bool& found) const;
|
||||
int hashfull() const;
|
||||
void resize(size_t mbSize);
|
||||
void clear();
|
||||
void resize(size_t mbSize, int threadCount);
|
||||
void clear(size_t threadCount);
|
||||
|
||||
TTEntry* first_entry(const Key key) const {
|
||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||
}
|
||||
|
||||
uint8_t generation() const { return generation8; }
|
||||
|
||||
private:
|
||||
friend struct TTEntry;
|
||||
|
||||
size_t clusterCount;
|
||||
Cluster* table;
|
||||
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
|
||||
Cluster* table = nullptr;
|
||||
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
|
||||
};
|
||||
|
||||
extern TranspositionTable TT;
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef TT_H_INCLUDED
|
||||
|
|
19
src/tune.cpp
19
src/tune.cpp
|
@ -24,14 +24,15 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "uci.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
bool Tune::update_on_last;
|
||||
const UCI::Option* LastOption = nullptr;
|
||||
const Option* LastOption = nullptr;
|
||||
OptionsMap* Tune::options;
|
||||
static std::map<std::string, int> TuneResults;
|
||||
|
||||
string Tune::next(string& names, bool pop) {
|
||||
|
@ -53,13 +54,13 @@ string Tune::next(string& names, bool pop) {
|
|||
return name;
|
||||
}
|
||||
|
||||
static void on_tune(const UCI::Option& o) {
|
||||
static void on_tune(const Option& o) {
|
||||
|
||||
if (!Tune::update_on_last || LastOption == &o)
|
||||
Tune::read_options();
|
||||
}
|
||||
|
||||
static void make_option(const string& n, int v, const SetRange& r) {
|
||||
static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) {
|
||||
|
||||
// Do not generate option when there is nothing to tune (ie. min = max)
|
||||
if (r(v).first == r(v).second)
|
||||
|
@ -68,8 +69,8 @@ static void make_option(const string& n, int v, const SetRange& r) {
|
|||
if (TuneResults.count(n))
|
||||
v = TuneResults[n];
|
||||
|
||||
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
|
||||
LastOption = &Options[n];
|
||||
(*options)[n] << Option(v, r(v).first, r(v).second, on_tune);
|
||||
LastOption = &((*options)[n]);
|
||||
|
||||
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
||||
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
|
||||
|
@ -79,13 +80,13 @@ static void make_option(const string& n, int v, const SetRange& r) {
|
|||
|
||||
template<>
|
||||
void Tune::Entry<int>::init_option() {
|
||||
make_option(name, value, range);
|
||||
make_option(options, name, value, range);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Tune::Entry<int>::read_option() {
|
||||
if (Options.count(name))
|
||||
value = int(Options[name]);
|
||||
if (options->count(name))
|
||||
value = int((*options)[name]);
|
||||
}
|
||||
|
||||
// Instead of a variable here we have a PostUpdate function: just call it
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
|
||||
using Range = std::pair<int, int>; // Option's min-max values
|
||||
using RangeFun = Range(int);
|
||||
|
||||
|
@ -151,7 +153,8 @@ class Tune {
|
|||
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),
|
||||
args...); // Remove trailing parenthesis
|
||||
}
|
||||
static void init() {
|
||||
static void init(OptionsMap& o) {
|
||||
options = &o;
|
||||
for (auto& e : instance().list)
|
||||
e->init_option();
|
||||
read_options();
|
||||
|
@ -160,7 +163,9 @@ class Tune {
|
|||
for (auto& e : instance().list)
|
||||
e->read_option();
|
||||
}
|
||||
static bool update_on_last;
|
||||
|
||||
static bool update_on_last;
|
||||
static OptionsMap* options;
|
||||
};
|
||||
|
||||
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
|
||||
|
|
527
src/uci.cpp
527
src/uci.cpp
|
@ -22,111 +22,156 @@
|
|||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "evaluate.h"
|
||||
#include "misc.h"
|
||||
#include "movegen.h"
|
||||
#include "nnue/evaluate_nnue.h"
|
||||
#include "nnue/nnue_architecture.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "thread.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "types.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
namespace {
|
||||
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
constexpr int NormalizeToPawnValue = 328;
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
|
||||
// FEN string for the initial position in standard chess
|
||||
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
UCI::UCI(int argc, char** argv) :
|
||||
cli(argc, argv) {
|
||||
|
||||
evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}},
|
||||
{Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}};
|
||||
|
||||
|
||||
// Called when the engine receives the "position" UCI command.
|
||||
// It sets up the position that is described in the given FEN string ("fen") or
|
||||
// the initial position ("startpos") and then makes the moves given in the following
|
||||
// move list ("moves").
|
||||
void position(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||
options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); });
|
||||
|
||||
Move m;
|
||||
std::string token, fen;
|
||||
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
|
||||
threads.set({options, threads, tt});
|
||||
});
|
||||
|
||||
is >> token;
|
||||
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) {
|
||||
threads.main_thread()->wait_for_search_finished();
|
||||
tt.resize(o, options["Threads"]);
|
||||
});
|
||||
|
||||
if (token == "startpos")
|
||||
{
|
||||
fen = StartFEN;
|
||||
is >> token; // Consume the "moves" token, if any
|
||||
}
|
||||
else if (token == "fen")
|
||||
while (is >> token && token != "moves")
|
||||
fen += token + " ";
|
||||
else
|
||||
return;
|
||||
options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); });
|
||||
options["Ponder"] << Option(false);
|
||||
options["MultiPV"] << Option(1, 1, 500);
|
||||
options["Skill Level"] << Option(20, 0, 20);
|
||||
options["Move Overhead"] << Option(10, 0, 5000);
|
||||
options["nodestime"] << Option(0, 0, 10000);
|
||||
options["UCI_Chess960"] << Option(false);
|
||||
options["UCI_LimitStrength"] << Option(false);
|
||||
options["UCI_Elo"] << Option(1320, 1320, 3190);
|
||||
options["UCI_ShowWDL"] << Option(false);
|
||||
options["SyzygyPath"] << Option("<empty>", [](const Option& o) { Tablebases::init(o); });
|
||||
options["SyzygyProbeDepth"] << Option(1, 1, 100);
|
||||
options["Syzygy50MoveRule"] << Option(true);
|
||||
options["SyzygyProbeLimit"] << Option(7, 0, 7);
|
||||
options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) {
|
||||
evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles);
|
||||
});
|
||||
|
||||
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
||||
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
|
||||
threads.set({options, threads, tt});
|
||||
|
||||
// Parse the move list, if any
|
||||
while (is >> token && (m = UCI::to_move(pos, token)) != Move::none())
|
||||
{
|
||||
states->emplace_back();
|
||||
pos.do_move(m, states->back());
|
||||
}
|
||||
search_clear(); // After threads are up
|
||||
}
|
||||
|
||||
// Prints the evaluation of the current position,
|
||||
// consistent with the UCI options set so far.
|
||||
void trace_eval(Position& pos) {
|
||||
void UCI::loop() {
|
||||
|
||||
Position pos;
|
||||
std::string token, cmd;
|
||||
StateListPtr states(new std::deque<StateInfo>(1));
|
||||
Position p;
|
||||
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
||||
|
||||
Eval::NNUE::verify();
|
||||
pos.set(StartFEN, false, &states->back());
|
||||
|
||||
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
||||
for (int i = 1; i < cli.argc; ++i)
|
||||
cmd += std::string(cli.argv[i]) + " ";
|
||||
|
||||
do
|
||||
{
|
||||
if (cli.argc == 1
|
||||
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
||||
cmd = "quit";
|
||||
|
||||
std::istringstream is(cmd);
|
||||
|
||||
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
||||
is >> std::skipws >> token;
|
||||
|
||||
if (token == "quit" || token == "stop")
|
||||
threads.stop = true;
|
||||
|
||||
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
|
||||
// So, 'ponderhit' is sent if pondering was done on the same move that the user
|
||||
// has played. The search should continue, but should also switch from pondering
|
||||
// to the normal search.
|
||||
else if (token == "ponderhit")
|
||||
threads.main_manager()->ponder = false; // Switch to the normal search
|
||||
|
||||
else if (token == "uci")
|
||||
sync_cout << "id name " << engine_info(true) << "\n"
|
||||
<< options << "\nuciok" << sync_endl;
|
||||
|
||||
else if (token == "setoption")
|
||||
setoption(is);
|
||||
else if (token == "go")
|
||||
go(pos, is, states);
|
||||
else if (token == "position")
|
||||
position(pos, is, states);
|
||||
else if (token == "ucinewgame")
|
||||
search_clear();
|
||||
else if (token == "isready")
|
||||
sync_cout << "readyok" << sync_endl;
|
||||
|
||||
// Add custom non-UCI commands, mainly for debugging purposes.
|
||||
// These commands must not be used during a search!
|
||||
else if (token == "flip")
|
||||
pos.flip();
|
||||
else if (token == "bench")
|
||||
bench(pos, is, states);
|
||||
else if (token == "d")
|
||||
sync_cout << pos << sync_endl;
|
||||
else if (token == "eval")
|
||||
trace_eval(pos);
|
||||
else if (token == "compiler")
|
||||
sync_cout << compiler_info() << sync_endl;
|
||||
else if (token == "export_net")
|
||||
{
|
||||
std::optional<std::string> filename;
|
||||
std::string f;
|
||||
if (is >> std::skipws >> f)
|
||||
filename = f;
|
||||
Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles);
|
||||
}
|
||||
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
||||
sync_cout
|
||||
<< "\nStockfish is a powerful chess engine for playing and analyzing."
|
||||
"\nIt is released as free software licensed under the GNU GPLv3 License."
|
||||
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
|
||||
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
|
||||
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
|
||||
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
|
||||
<< sync_endl;
|
||||
else if (!token.empty() && token[0] != '#')
|
||||
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
|
||||
<< sync_endl;
|
||||
|
||||
} while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot
|
||||
}
|
||||
|
||||
void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||
|
||||
// Called when the engine receives the "setoption" UCI command.
|
||||
// The function updates the UCI option ("name") to the given value ("value").
|
||||
|
||||
void setoption(std::istringstream& is) {
|
||||
|
||||
Threads.main()->wait_for_search_finished();
|
||||
|
||||
std::string token, name, value;
|
||||
|
||||
is >> token; // Consume the "name" token
|
||||
|
||||
// Read the option name (can contain spaces)
|
||||
while (is >> token && token != "value")
|
||||
name += (name.empty() ? "" : " ") + token;
|
||||
|
||||
// Read the option value (can contain spaces)
|
||||
while (is >> token)
|
||||
value += (value.empty() ? "" : " ") + token;
|
||||
|
||||
if (Options.count(name))
|
||||
Options[name] = value;
|
||||
else
|
||||
sync_cout << "No such option: " << name << sync_endl;
|
||||
}
|
||||
|
||||
|
||||
// Called when the engine receives the "go" UCI command. The function sets the
|
||||
// thinking time and other parameters from the input string then stars with a search
|
||||
|
||||
void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||
|
||||
Search::LimitsType limits;
|
||||
std::string token;
|
||||
|
@ -137,7 +182,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
|||
while (is >> token)
|
||||
if (token == "searchmoves") // Needs to be the last command on the line
|
||||
while (is >> token)
|
||||
limits.searchmoves.push_back(UCI::to_move(pos, token));
|
||||
limits.searchmoves.push_back(to_move(pos, token));
|
||||
|
||||
else if (token == "wtime")
|
||||
is >> limits.time[WHITE];
|
||||
|
@ -164,16 +209,12 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
|||
else if (token == "ponder")
|
||||
ponderMode = true;
|
||||
|
||||
Threads.start_thinking(pos, states, limits, ponderMode);
|
||||
Eval::NNUE::verify(options, evalFiles);
|
||||
|
||||
threads.start_thinking(options, pos, states, limits, ponderMode);
|
||||
}
|
||||
|
||||
|
||||
// Called when the engine receives the "bench" command.
|
||||
// First, a list of UCI commands is set up according to the bench
|
||||
// parameters, then it is run one by one, printing a summary at the end.
|
||||
|
||||
void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||
|
||||
void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||
std::string token;
|
||||
uint64_t num, nodes = 0, cnt = 1;
|
||||
|
||||
|
@ -196,8 +237,8 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
|||
if (token == "go")
|
||||
{
|
||||
go(pos, is, states);
|
||||
Threads.main()->wait_for_search_finished();
|
||||
nodes += Threads.nodes_searched();
|
||||
threads.main_thread()->wait_for_search_finished();
|
||||
nodes += threads.nodes_searched();
|
||||
}
|
||||
else
|
||||
trace_eval(pos);
|
||||
|
@ -208,9 +249,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
|||
position(pos, is, states);
|
||||
else if (token == "ucinewgame")
|
||||
{
|
||||
Search::clear();
|
||||
search_clear(); // Search::clear() may take a while
|
||||
elapsed = now();
|
||||
} // Search::clear() may take a while
|
||||
}
|
||||
}
|
||||
|
||||
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
|
||||
|
@ -222,6 +263,160 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) {
|
|||
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
|
||||
}
|
||||
|
||||
void UCI::trace_eval(Position& pos) {
|
||||
StateListPtr states(new std::deque<StateInfo>(1));
|
||||
Position p;
|
||||
p.set(pos.fen(), options["UCI_Chess960"], &states->back());
|
||||
|
||||
Eval::NNUE::verify(options, evalFiles);
|
||||
|
||||
sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl;
|
||||
}
|
||||
|
||||
void UCI::search_clear() {
|
||||
threads.main_thread()->wait_for_search_finished();
|
||||
|
||||
tt.clear(options["Threads"]);
|
||||
threads.clear();
|
||||
Tablebases::init(options["SyzygyPath"]); // Free mapped files
|
||||
}
|
||||
|
||||
void UCI::setoption(std::istringstream& is) {
|
||||
threads.main_thread()->wait_for_search_finished();
|
||||
options.setoption(is);
|
||||
}
|
||||
|
||||
void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||
Move m;
|
||||
std::string token, fen;
|
||||
|
||||
is >> token;
|
||||
|
||||
if (token == "startpos")
|
||||
{
|
||||
fen = StartFEN;
|
||||
is >> token; // Consume the "moves" token, if any
|
||||
}
|
||||
else if (token == "fen")
|
||||
while (is >> token && token != "moves")
|
||||
fen += token + " ";
|
||||
else
|
||||
return;
|
||||
|
||||
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
||||
pos.set(fen, options["UCI_Chess960"], &states->back());
|
||||
|
||||
// Parse the move list, if any
|
||||
while (is >> token && (m = to_move(pos, token)) != Move::none())
|
||||
{
|
||||
states->emplace_back();
|
||||
pos.do_move(m, states->back());
|
||||
}
|
||||
}
|
||||
|
||||
int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; }
|
||||
|
||||
std::string UCI::value(Value v) {
|
||||
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
ss << "cp " << to_cp(v);
|
||||
else if (std::abs(v) <= VALUE_TB)
|
||||
{
|
||||
const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
|
||||
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
||||
}
|
||||
else
|
||||
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string UCI::square(Square s) {
|
||||
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
||||
}
|
||||
|
||||
std::string UCI::move(Move m, bool chess960) {
|
||||
if (m == Move::none())
|
||||
return "(none)";
|
||||
|
||||
if (m == Move::null())
|
||||
return "0000";
|
||||
|
||||
Square from = m.from_sq();
|
||||
Square to = m.to_sq();
|
||||
|
||||
if (m.type_of() == CASTLING && !chess960)
|
||||
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
||||
|
||||
std::string move = square(from) + square(to);
|
||||
|
||||
if (m.type_of() == PROMOTION)
|
||||
move += " pnbrqk"[m.promotion_type()];
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
std::string UCI::pv(const Search::Worker& workerThread,
|
||||
TimePoint elapsed,
|
||||
uint64_t nodesSearched,
|
||||
uint64_t tb_hits,
|
||||
int hashfull,
|
||||
bool rootInTB) {
|
||||
std::stringstream ss;
|
||||
TimePoint time = elapsed + 1;
|
||||
const auto& rootMoves = workerThread.rootMoves;
|
||||
const auto& depth = workerThread.completedDepth;
|
||||
const auto& pos = workerThread.rootPos;
|
||||
size_t pvIdx = workerThread.pvIdx;
|
||||
size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size());
|
||||
uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0);
|
||||
|
||||
|
||||
for (size_t i = 0; i < multiPV; ++i)
|
||||
{
|
||||
bool updated = rootMoves[i].score != -VALUE_INFINITE;
|
||||
|
||||
if (depth == 1 && !updated && i > 0)
|
||||
continue;
|
||||
|
||||
Depth d = updated ? depth : std::max(1, depth - 1);
|
||||
Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;
|
||||
|
||||
if (v == -VALUE_INFINITE)
|
||||
v = VALUE_ZERO;
|
||||
|
||||
bool tb = rootInTB && std::abs(v) <= VALUE_TB;
|
||||
v = tb ? rootMoves[i].tbScore : v;
|
||||
|
||||
if (ss.rdbuf()->in_avail()) // Not at first line
|
||||
ss << "\n";
|
||||
|
||||
ss << "info"
|
||||
<< " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1
|
||||
<< " score " << value(v);
|
||||
|
||||
if (workerThread.options["UCI_ShowWDL"])
|
||||
ss << wdl(v, pos.game_ply());
|
||||
|
||||
if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact
|
||||
ss << (rootMoves[i].scoreLowerbound
|
||||
? " lowerbound"
|
||||
: (rootMoves[i].scoreUpperbound ? " upperbound" : ""));
|
||||
|
||||
ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull "
|
||||
<< hashfull << " tbhits " << tbHits << " time " << time << " pv";
|
||||
|
||||
for (Move m : rootMoves[i].pv)
|
||||
ss << " " << move(m, pos.is_chess960());
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The win rate model returns the probability of winning (in per mille units) given an
|
||||
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
|
||||
int win_rate_model(Value v, int ply) {
|
||||
|
@ -236,7 +431,7 @@ int win_rate_model(Value v, int ply) {
|
|||
constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330};
|
||||
|
||||
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
|
||||
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
|
||||
static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
|
||||
|
||||
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
||||
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||
|
@ -247,132 +442,9 @@ int win_rate_model(Value v, int ply) {
|
|||
// Return the win rate in per mille units, rounded to the nearest integer
|
||||
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Waits for a command from the stdin, parses it, and then calls the appropriate
|
||||
// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a
|
||||
// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments,
|
||||
// like running 'bench', the function returns immediately after the command is executed.
|
||||
// In addition to the UCI ones, some additional debug commands are also supported.
|
||||
void UCI::loop(int argc, char* argv[]) {
|
||||
|
||||
Position pos;
|
||||
std::string token, cmd;
|
||||
StateListPtr states(new std::deque<StateInfo>(1));
|
||||
|
||||
pos.set(StartFEN, false, &states->back(), Threads.main());
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
cmd += std::string(argv[i]) + " ";
|
||||
|
||||
do
|
||||
{
|
||||
if (argc == 1
|
||||
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
||||
cmd = "quit";
|
||||
|
||||
std::istringstream is(cmd);
|
||||
|
||||
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
||||
is >> std::skipws >> token;
|
||||
|
||||
if (token == "quit" || token == "stop")
|
||||
Threads.stop = true;
|
||||
|
||||
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
|
||||
// So, 'ponderhit' is sent if pondering was done on the same move that the user
|
||||
// has played. The search should continue, but should also switch from pondering
|
||||
// to the normal search.
|
||||
else if (token == "ponderhit")
|
||||
Threads.main()->ponder = false; // Switch to the normal search
|
||||
|
||||
else if (token == "uci")
|
||||
sync_cout << "id name " << engine_info(true) << "\n"
|
||||
<< Options << "\nuciok" << sync_endl;
|
||||
|
||||
else if (token == "setoption")
|
||||
setoption(is);
|
||||
else if (token == "go")
|
||||
go(pos, is, states);
|
||||
else if (token == "position")
|
||||
position(pos, is, states);
|
||||
else if (token == "ucinewgame")
|
||||
Search::clear();
|
||||
else if (token == "isready")
|
||||
sync_cout << "readyok" << sync_endl;
|
||||
|
||||
// Add custom non-UCI commands, mainly for debugging purposes.
|
||||
// These commands must not be used during a search!
|
||||
else if (token == "flip")
|
||||
pos.flip();
|
||||
else if (token == "bench")
|
||||
bench(pos, is, states);
|
||||
else if (token == "d")
|
||||
sync_cout << pos << sync_endl;
|
||||
else if (token == "eval")
|
||||
trace_eval(pos);
|
||||
else if (token == "compiler")
|
||||
sync_cout << compiler_info() << sync_endl;
|
||||
else if (token == "export_net")
|
||||
{
|
||||
std::optional<std::string> filename;
|
||||
std::string f;
|
||||
if (is >> std::skipws >> f)
|
||||
filename = f;
|
||||
Eval::NNUE::save_eval(filename, Eval::NNUE::Big);
|
||||
}
|
||||
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
||||
sync_cout
|
||||
<< "\nStockfish is a powerful chess engine for playing and analyzing."
|
||||
"\nIt is released as free software licensed under the GNU GPLv3 License."
|
||||
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
|
||||
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
|
||||
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
|
||||
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
|
||||
<< sync_endl;
|
||||
else if (!token.empty() && token[0] != '#')
|
||||
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
|
||||
<< sync_endl;
|
||||
|
||||
} while (token != "quit" && argc == 1); // The command-line arguments are one-shot
|
||||
}
|
||||
|
||||
|
||||
// Turns a Value to an integer centipawn number,
|
||||
// without treatment of mate and similar special scores.
|
||||
int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; }
|
||||
|
||||
// Converts a Value to a string by adhering to the UCI protocol specification:
|
||||
//
|
||||
// cp <x> The score from the engine's point of view in centipawns.
|
||||
// mate <y> Mate in 'y' moves (not plies). If the engine is getting mated,
|
||||
// uses negative values for 'y'.
|
||||
std::string UCI::value(Value v) {
|
||||
|
||||
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||
ss << "cp " << UCI::to_cp(v);
|
||||
else if (std::abs(v) <= VALUE_TB)
|
||||
{
|
||||
const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
|
||||
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
||||
}
|
||||
else
|
||||
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
// Reports the win-draw-loss (WDL) statistics given an evaluation
|
||||
// and a game ply based on the data gathered for fishtest LTC games.
|
||||
std::string UCI::wdl(Value v, int ply) {
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
int wdl_w = win_rate_model(v, ply);
|
||||
|
@ -383,49 +455,12 @@ std::string UCI::wdl(Value v, int ply) {
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
// Converts a Square to a string in algebraic notation (g1, a7, etc.)
|
||||
std::string UCI::square(Square s) {
|
||||
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
||||
}
|
||||
|
||||
|
||||
// Converts a Move to a string in coordinate notation (g1f3, a7a8q).
|
||||
// The only special case is castling where the e1g1 notation is printed in
|
||||
// standard chess mode and in e1h1 notation it is printed in Chess960 mode.
|
||||
// Internally, all castling moves are always encoded as 'king captures rook'.
|
||||
std::string UCI::move(Move m, bool chess960) {
|
||||
|
||||
if (m == Move::none())
|
||||
return "(none)";
|
||||
|
||||
if (m == Move::null())
|
||||
return "0000";
|
||||
|
||||
Square from = m.from_sq();
|
||||
Square to = m.to_sq();
|
||||
|
||||
if (m.type_of() == CASTLING && !chess960)
|
||||
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
||||
|
||||
std::string move = UCI::square(from) + UCI::square(to);
|
||||
|
||||
if (m.type_of() == PROMOTION)
|
||||
move += " pnbrqk"[m.promotion_type()];
|
||||
|
||||
return move;
|
||||
}
|
||||
|
||||
|
||||
// Converts a string representing a move in coordinate notation
|
||||
// (g1f3, a7a8q) to the corresponding legal Move, if any.
|
||||
Move UCI::to_move(const Position& pos, std::string& str) {
|
||||
|
||||
if (str.length() == 5)
|
||||
str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
|
||||
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
if (str == UCI::move(m, pos.is_chess960()))
|
||||
if (str == move(m, pos.is_chess960()))
|
||||
return m;
|
||||
|
||||
return Move::none();
|
||||
|
|
105
src/uci.h
105
src/uci.h
|
@ -19,77 +19,70 @@
|
|||
#ifndef UCI_H_INCLUDED
|
||||
#define UCI_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "types.h"
|
||||
#include "evaluate.h"
|
||||
#include "misc.h"
|
||||
#include "position.h"
|
||||
#include "thread.h"
|
||||
#include "tt.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class Position;
|
||||
namespace Eval::NNUE {
|
||||
enum NetSize : int;
|
||||
}
|
||||
|
||||
namespace UCI {
|
||||
namespace Search {
|
||||
class Worker;
|
||||
}
|
||||
|
||||
// Normalizes the internal value as reported by evaluate or search
|
||||
// to the UCI centipawn result used in output. This value is derived from
|
||||
// the win_rate_model() such that Stockfish outputs an advantage of
|
||||
// "100 centipawns" for a position if the engine has a 50% probability to win
|
||||
// from this position in self-play at fishtest LTC time control.
|
||||
const int NormalizeToPawnValue = 328;
|
||||
|
||||
class Option;
|
||||
|
||||
// Define a custom comparator, because the UCI options should be case-insensitive
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const std::string&, const std::string&) const;
|
||||
};
|
||||
|
||||
// The options container is defined as a std::map
|
||||
using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
|
||||
|
||||
// The Option class implements each option as specified by the UCI protocol
|
||||
class Option {
|
||||
|
||||
using OnChange = void (*)(const Option&);
|
||||
class Move;
|
||||
enum Square : int;
|
||||
using Value = int;
|
||||
|
||||
class UCI {
|
||||
public:
|
||||
Option(OnChange = nullptr);
|
||||
Option(bool v, OnChange = nullptr);
|
||||
Option(const char* v, OnChange = nullptr);
|
||||
Option(double v, int minv, int maxv, OnChange = nullptr);
|
||||
Option(const char* v, const char* cur, OnChange = nullptr);
|
||||
UCI(int argc, char** argv);
|
||||
|
||||
Option& operator=(const std::string&);
|
||||
void operator<<(const Option&);
|
||||
operator int() const;
|
||||
operator std::string() const;
|
||||
bool operator==(const char*) const;
|
||||
void loop();
|
||||
|
||||
static int to_cp(Value v);
|
||||
static std::string value(Value v);
|
||||
static std::string square(Square s);
|
||||
static std::string move(Move m, bool chess960);
|
||||
static std::string pv(const Search::Worker& workerThread,
|
||||
TimePoint elapsed,
|
||||
uint64_t nodesSearched,
|
||||
uint64_t tb_hits,
|
||||
int hashfull,
|
||||
bool rootInTB);
|
||||
static std::string wdl(Value v, int ply);
|
||||
static Move to_move(const Position& pos, std::string& str);
|
||||
|
||||
const std::string& workingDirectory() const { return cli.workingDirectory; }
|
||||
|
||||
OptionsMap options;
|
||||
|
||||
std::unordered_map<Eval::NNUE::NetSize, Eval::EvalFile> evalFiles;
|
||||
|
||||
private:
|
||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||
TranspositionTable tt;
|
||||
ThreadPool threads;
|
||||
CommandLine cli;
|
||||
|
||||
std::string defaultValue, currentValue, type;
|
||||
int min, max;
|
||||
size_t idx;
|
||||
OnChange on_change;
|
||||
void go(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||
void bench(Position& pos, std::istream& args, StateListPtr& states);
|
||||
void position(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||
void trace_eval(Position& pos);
|
||||
void search_clear();
|
||||
void setoption(std::istringstream& is);
|
||||
};
|
||||
|
||||
void init(OptionsMap&);
|
||||
void loop(int argc, char* argv[]);
|
||||
int to_cp(Value v);
|
||||
std::string value(Value v);
|
||||
std::string square(Square s);
|
||||
std::string move(Move m, bool chess960);
|
||||
std::string pv(const Position& pos, Depth depth);
|
||||
std::string wdl(Value v, int ply);
|
||||
Move to_move(const Position& pos, std::string& str);
|
||||
|
||||
} // namespace UCI
|
||||
|
||||
extern UCI::OptionsMap Options;
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef UCI_H_INCLUDED
|
||||
|
|
|
@ -16,104 +16,53 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ucioption.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <iosfwd>
|
||||
#include <istream>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "evaluate.h"
|
||||
#include "misc.h"
|
||||
#include "search.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "thread.h"
|
||||
#include "tt.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
UCI::OptionsMap Options; // Global object
|
||||
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const {
|
||||
|
||||
namespace UCI {
|
||||
|
||||
// 'On change' actions, triggered by an option's value change
|
||||
static void on_clear_hash(const Option&) { Search::clear(); }
|
||||
static void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
|
||||
static void on_logger(const Option& o) { start_logger(o); }
|
||||
static void on_threads(const Option& o) { Threads.set(size_t(o)); }
|
||||
static void on_tb_path(const Option& o) { Tablebases::init(o); }
|
||||
static void on_eval_file(const Option&) { Eval::NNUE::init(); }
|
||||
|
||||
// Our case insensitive less() function as required by UCI protocol
|
||||
bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const {
|
||||
|
||||
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
|
||||
[](char c1, char c2) { return tolower(c1) < tolower(c2); });
|
||||
return std::lexicographical_compare(
|
||||
s1.begin(), s1.end(), s2.begin(), s2.end(),
|
||||
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||
}
|
||||
|
||||
void OptionsMap::setoption(std::istringstream& is) {
|
||||
std::string token, name, value;
|
||||
|
||||
// Initializes the UCI options to their hard-coded default values
|
||||
void init(OptionsMap& o) {
|
||||
is >> token; // Consume the "name" token
|
||||
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
// Read the option name (can contain spaces)
|
||||
while (is >> token && token != "value")
|
||||
name += (name.empty() ? "" : " ") + token;
|
||||
|
||||
o["Debug Log File"] << Option("", on_logger);
|
||||
o["Threads"] << Option(1, 1, 1024, on_threads);
|
||||
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
||||
o["Clear Hash"] << Option(on_clear_hash);
|
||||
o["Ponder"] << Option(false);
|
||||
o["MultiPV"] << Option(1, 1, MAX_MOVES);
|
||||
o["Skill Level"] << Option(20, 0, 20);
|
||||
o["Move Overhead"] << Option(10, 0, 5000);
|
||||
o["nodestime"] << Option(0, 0, 10000);
|
||||
o["UCI_Chess960"] << Option(false);
|
||||
o["UCI_LimitStrength"] << Option(false);
|
||||
o["UCI_Elo"] << Option(1320, 1320, 3190);
|
||||
o["UCI_ShowWDL"] << Option(false);
|
||||
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
||||
o["SyzygyProbeDepth"] << Option(1, 1, 100);
|
||||
o["Syzygy50MoveRule"] << Option(true);
|
||||
o["SyzygyProbeLimit"] << Option(7, 0, 7);
|
||||
o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file);
|
||||
// Enable this after fishtest workers support EvalFileSmall
|
||||
// o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file);
|
||||
// Read the option value (can contain spaces)
|
||||
while (is >> token)
|
||||
value += (value.empty() ? "" : " ") + token;
|
||||
|
||||
if (options_map.count(name))
|
||||
options_map[name] = value;
|
||||
else
|
||||
sync_cout << "No such option: " << name << sync_endl;
|
||||
}
|
||||
|
||||
|
||||
// Used to print all the options default values in chronological
|
||||
// insertion order (the idx field) and in the format defined by the UCI protocol.
|
||||
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
||||
|
||||
for (size_t idx = 0; idx < om.size(); ++idx)
|
||||
for (const auto& it : om)
|
||||
if (it.second.idx == idx)
|
||||
{
|
||||
const Option& o = it.second;
|
||||
os << "\noption name " << it.first << " type " << o.type;
|
||||
|
||||
if (o.type == "string" || o.type == "check" || o.type == "combo")
|
||||
os << " default " << o.defaultValue;
|
||||
|
||||
if (o.type == "spin")
|
||||
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
||||
<< o.max;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
Option OptionsMap::operator[](const std::string& name) const {
|
||||
auto it = options_map.find(name);
|
||||
return it != options_map.end() ? it->second : Option();
|
||||
}
|
||||
|
||||
Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; }
|
||||
|
||||
// Option class constructors and conversion operators
|
||||
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
|
||||
|
||||
Option::Option(const char* v, OnChange f) :
|
||||
type("string"),
|
||||
|
@ -184,19 +133,19 @@ void Option::operator<<(const Option& o) {
|
|||
// Updates currentValue and triggers on_change() action. It's up to
|
||||
// the GUI to check for option's limits, but we could receive the new value
|
||||
// from the user by console window, so let's check the bounds anyway.
|
||||
Option& Option::operator=(const string& v) {
|
||||
Option& Option::operator=(const std::string& v) {
|
||||
|
||||
assert(!type.empty());
|
||||
|
||||
if ((type != "button" && type != "string" && v.empty())
|
||||
|| (type == "check" && v != "true" && v != "false")
|
||||
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
|
||||
|| (type == "spin" && (std::stof(v) < min || std::stof(v) > max)))
|
||||
return *this;
|
||||
|
||||
if (type == "combo")
|
||||
{
|
||||
OptionsMap comboMap; // To have case insensitive compare
|
||||
string token;
|
||||
std::string token;
|
||||
std::istringstream ss(defaultValue);
|
||||
while (ss >> token)
|
||||
comboMap[token] << Option();
|
||||
|
@ -213,6 +162,24 @@ Option& Option::operator=(const string& v) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
} // namespace UCI
|
||||
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
||||
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
|
||||
for (const auto& it : om.options_map)
|
||||
if (it.second.idx == idx)
|
||||
{
|
||||
const Option& o = it.second;
|
||||
os << "\noption name " << it.first << " type " << o.type;
|
||||
|
||||
} // namespace Stockfish
|
||||
if (o.type == "string" || o.type == "check" || o.type == "combo")
|
||||
os << " default " << o.defaultValue;
|
||||
|
||||
if (o.type == "spin")
|
||||
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
||||
<< o.max;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
}
|
||||
|
|
81
src/ucioption.h
Normal file
81
src/ucioption.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef UCIOPTION_H_INCLUDED
|
||||
#define UCIOPTION_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Stockfish {
|
||||
// Define a custom comparator, because the UCI options should be case-insensitive
|
||||
struct CaseInsensitiveLess {
|
||||
bool operator()(const std::string&, const std::string&) const;
|
||||
};
|
||||
|
||||
class Option;
|
||||
|
||||
class OptionsMap {
|
||||
public:
|
||||
void setoption(std::istringstream&);
|
||||
|
||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||
|
||||
Option operator[](const std::string&) const;
|
||||
Option& operator[](const std::string&);
|
||||
|
||||
std::size_t count(const std::string&) const;
|
||||
|
||||
private:
|
||||
// The options container is defined as a std::map
|
||||
using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;
|
||||
|
||||
OptionsStore options_map;
|
||||
};
|
||||
|
||||
// The Option class implements each option as specified by the UCI protocol
|
||||
class Option {
|
||||
public:
|
||||
using OnChange = std::function<void(const Option&)>;
|
||||
|
||||
Option(OnChange = nullptr);
|
||||
Option(bool v, OnChange = nullptr);
|
||||
Option(const char* v, OnChange = nullptr);
|
||||
Option(double v, int minv, int maxv, OnChange = nullptr);
|
||||
Option(const char* v, const char* cur, OnChange = nullptr);
|
||||
|
||||
Option& operator=(const std::string&);
|
||||
void operator<<(const Option&);
|
||||
operator int() const;
|
||||
operator std::string() const;
|
||||
bool operator==(const char*) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
||||
|
||||
private:
|
||||
std::string defaultValue, currentValue, type;
|
||||
int min, max;
|
||||
size_t idx;
|
||||
OnChange on_change;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // #ifndef UCIOPTION_H_INCLUDED
|
Loading…
Add table
Reference in a new issue