diff options
| author | Loic Guegan <manzerbredes@mailbox.org> | 2022-01-30 20:19:02 +0100 |
|---|---|---|
| committer | Loic Guegan <manzerbredes@mailbox.org> | 2022-02-01 19:58:14 +0100 |
| commit | f5d9fe6211bd397deb0ac19b634f551edfa0f6b8 (patch) | |
| tree | 3568c011bdd4dcd201db8ca6ee098da70a1fd53f /src | |
Initialize project
Diffstat (limited to 'src')
| -rw-r--r-- | src/Process.hpp | 28 | ||||
| -rw-r--r-- | src/ProcessLinux.cpp | 72 | ||||
| -rw-r--r-- | src/ProcessLinux.hpp | 26 | ||||
| -rw-r--r-- | src/UCI.cpp | 281 | ||||
| -rw-r--r-- | src/UCI.hpp | 125 |
5 files changed, 532 insertions, 0 deletions
diff --git a/src/Process.hpp b/src/Process.hpp new file mode 100644 index 0000000..3265cc9 --- /dev/null +++ b/src/Process.hpp @@ -0,0 +1,28 @@ +#include <string> + +#define ENGINE_TIMEOUT 5 // In seconds +#define BUFFER_SIZE 1024 + +namespace uciadapter { + +class Process { + +public: + /// @brief Kill the engine + virtual void Kill() = 0; + /// @brief Start the engine from file path + virtual void Start(std::string) = 0; + /// @brief Read one line from the stdout of the engine (could raise a ReadTimeoutExpire) + virtual std::string ReadLine() = 0; + /// @brief Write to engine stdin + virtual void Write(std::string) = 0; +}; + +struct FailedToStartEngine : public std::exception { + const char *what() const throw() { return ("Could not start the engine"); } +}; +struct ReadTimeoutExpire : public std::exception { + const char *what() const throw() { return ("Engine is not responding"); } +}; + +} // namespace uciadapter
\ No newline at end of file diff --git a/src/ProcessLinux.cpp b/src/ProcessLinux.cpp new file mode 100644 index 0000000..ac54179 --- /dev/null +++ b/src/ProcessLinux.cpp @@ -0,0 +1,72 @@ +#include "ProcessLinux.hpp" + +namespace uciadapter { + +ProcessLinux::ProcessLinux() { + + pipe(in_fd); + pipe(out_fd); +} + +void ProcessLinux::Kill() { kill(pid, SIGTERM); } + +void ProcessLinux::Start(std::string path) { + pid = fork(); + if (pid == 0) { + // Connect output of child process + close(out_fd[0]); + dup2(out_fd[1], STDOUT_FILENO); + close(out_fd[1]); + // Connect input of child process + close(in_fd[1]); + dup2(in_fd[0], STDIN_FILENO); + close(in_fd[0]); + + const char *args[] = {path.c_str(), NULL}; + execvp(args[0], const_cast<char *const *>(args)); + + _exit(1); + } else if (pid < 0) { + throw FailedToStartEngine(); + } + + // Parent do not read the in of the child + close(in_fd[0]); + // The parent do not write on the out of the child + close(out_fd[1]); + // Set descriptor to non-blocking (for the read syscall) + int flags = fcntl(out_fd[0], F_GETFL, 0); + fcntl(out_fd[0], F_SETFL, flags | O_NONBLOCK); +} + +std::string ProcessLinux::ReadLine() { + std::string line; + auto start = std::chrono::system_clock::now(); + // Read char by char + while (true) { + char c; + int status = read(out_fd[0], &c, 1); + if (status > 0) { + line += c; + if (c == '\n') + break; + } else { + // Check for timeout + auto end = std::chrono::system_clock::now(); + auto elapsed = + std::chrono::duration_cast<std::chrono::seconds>(end - start); + if (elapsed.count() > ENGINE_TIMEOUT) { + throw ReadTimeoutExpire(); + } + } + } + return (std::string(line)); +} + +void ProcessLinux::Write(std::string data) { + for (unsigned int i = 0; i < data.size(); i++) { + buffer[i] = data[i]; + } + write(in_fd[1], buffer, data.size()); +} +} // namespace uciadapter
\ No newline at end of file diff --git a/src/ProcessLinux.hpp b/src/ProcessLinux.hpp new file mode 100644 index 0000000..533054e --- /dev/null +++ b/src/ProcessLinux.hpp @@ -0,0 +1,26 @@ +#include "Process.hpp" +#include <chrono> +#include <fcntl.h> +#include <sys/wait.h> +#include <unistd.h> + +namespace uciadapter { + +class ProcessLinux : public Process { + /// @brief Create pipe to engine stdin + int in_fd[2]; + /// @brief Create pipe to the engine stdout + int out_fd[2]; + /// @brief Writing buffer + char buffer[BUFFER_SIZE]; + /// @brief Contains engine pid + pid_t pid; + +public: + ProcessLinux(); + void Kill(); + void Start(std::string); + std::string ReadLine(); + void Write(std::string); +}; +}; // namespace uciadapter
\ No newline at end of file diff --git a/src/UCI.cpp b/src/UCI.cpp new file mode 100644 index 0000000..7318f81 --- /dev/null +++ b/src/UCI.cpp @@ -0,0 +1,281 @@ +#include "UCI.hpp" + +namespace uciadapter { + +Info::Info() + : depth(-1), seldepth(-1), multipv(-1), score_cp(-1), score_mate(-1), + score_lowerbound(-1), score_upperbound(-1), cp(-1), nodes(-1), nps(-1), + tbhits(-1), time(-1), hashfull(-1), cpuload(-1), currmovenumber(-1) + +{} + +Go::Go() + : wtime(-1), btime(-1), winc(-1), binc(-1), movestogo(-1), depth(-1), + nodes(-1), mate(-1), movetime(-1), infinite(false) {} + +UCI::UCI(std::string engine_path) { + INIT_PROCESS(p); + p->Start(engine_path); + // Init UCI + p->Write("uci\n"); + uciok = false; + registration_required = false; + copyprotection_failed = false; + registered = false; + Sync(); + if (!uciok) { + throw EngineError("failed to start engine in uci mode"); + } + Sync(); // Check copy protection + if (copyprotection_failed) { + throw EngineError("Copy protection check failed"); + } +} + +UCI::~UCI() { + p->Kill(); + delete p; +} + +void UCI::Sync() { + p->Write("isready\n"); + bool readyok = false; + std::string token; + while (!readyok) { + std::istringstream iss(p->ReadLine()); + buffer += iss.str(); + while (iss >> token) { + if (token == "readyok") { + readyok = true; + } else if (token == "uciok") { + uciok = true; + } else if (token == "id") { + ParseId(iss.str()); + } else if (token == "option") { + ParseOption(iss.str()); + } else if (token == "copyprotection") { + iss >> token; + if (token == "error") { + copyprotection_failed = true; + } else if (token == "ok") { + registered = true; + } + } else if (token == "registration") { + if (iss.str() == "registration error\n") + registration_required = true; + } else if (token == "bestmove") { + iss >> token; + bestmove = token; + iss >> token; + if (token == "ponder") { + iss >> token; + ponder = token; + } + } else if (token == "info") { + ParseInfo(iss.str()); + } + } + } +} + +void UCI::Command(std::string cmd) { + p->Write(cmd + "\n"); + Sync(); +} + +bool UCI::IsRegistered() { return (registered); } + +void UCI::SyncAfter(int ms) { + if (ms <= 0) { + Sync(); + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); + Sync(); + } +} + +void UCI::ParseInfo(std::string line) { + Info info; + std::istringstream iss(line); + std::string token; + while (iss >> token) { + if (token == "depth") { + iss >> info.depth; + } else if (token == "string") { + std::string line; + while (iss >> token) { + line += token + " "; + } + line = line.substr(0, line.size() - 1); // Remove trailing space + infostrings.push_back(line); + return; + } else if (token == "seldepth") { + iss >> info.seldepth; + } else if (token == "refutation") { + while (iss >> token) { + refutations.push_back(token); + } + return; + } else if (token == "nps") { + iss >> info.nps; + } else if (token == "multipv") { + iss >> info.multipv; + } else if (token == "nodes") { + iss >> info.nodes; + } else if (token == "time") { + iss >> info.time; + } else if (token == "tbhits") { + iss >> info.tbhits; + } else if (token == "hashfull") { + iss >> info.hashfull; + } else if (token == "cpuload") { + iss >> info.cpuload; + } else if (token == "currmove") { + iss >> info.currmove; + } else if (token == "currmovenumber") { + iss >> info.currmovenumber; + } else if (token == "score") { + iss >> token; + if (token == "cp") { + iss >> info.score_cp; + } else if (token == "mate") { + iss >> info.score_mate; + } else if (token == "lowerbound") { + iss >> info.score_lowerbound; + } else if (token == "upperbound") { + iss >> info.score_upperbound; + } + } else if (token == "pv") { + while (iss >> token) { + info.pv.push_back(token); + } + } + } + + // Save line + if (info.pv.size() > 0) { + lines[info.multipv] = info; + } +} + +std::vector<std::string> UCI::GetInfoStrings() { return (infostrings); } +void UCI::ParseOption(std::string line) { + std::istringstream iss(line); + std::string token; + Option opt; + iss >> token; // option token + std::string *entry = NULL; + while (iss) { + if (token == "name") { + entry = &opt.name; + } else if (token == "type") { + entry = &opt.type; + } else if (token == "default") { + entry = &opt.default_value; + } else if (token == "min") { + entry = &opt.min; + } else if (token == "max") { + entry = &opt.max; + } else if (token == "var") { + entry = &opt.var; + } else { + iss >> token; + } + if (entry != NULL) { + iss >> token; + while (!IS_OPT_PARAM(token) && iss) { + (*entry) += token + " "; + iss >> token; + } + *entry = entry->substr(0, entry->size() - 1); // Remove trailing space + entry = NULL; + } + } + options.push_back(opt); +} + +void UCI::ParseId(std::string line) { + std::istringstream iss(line); + std::string token; + iss >> token; // id + iss >> token; // name or author + if (token == "name") { + while (iss >> token) { + name += token + " "; + } + name = name.substr(0, name.size() - 1); + } else { + while (iss >> token) { + author += token + " "; + } + author = author.substr(0, author.size() - 1); + } +} + +std::unordered_map<int, Info> UCI::GetLines() { return (lines); } +std::vector<Option> UCI::GetOptions() { return (options); } +std::string UCI::GetName() { return (name); } +std::string UCI::GetAuthor() { return (author); } +std::string UCI::GetBuffer() { return (buffer); } +std::string UCI::GetBestMove() { return (bestmove); } + +bool UCI::IsRegistrationRequired() { return (registration_required); } + +void UCI::register_now(std::string name, std::string code) { + Command("register name " + name + " code " + code); +} +void UCI::register_later() { Command("register later"); } +void UCI::stop() { Command("stop"); } +void UCI::setoption(std::string name, std::string value) { + Command("setoption name " + name + " value " + value); +} +void UCI::debug(bool d) { + if (d) { + Command("debug on"); + } else { + Command("debug off"); + } +} +void UCI::ponderhit() { Command("ponderhit"); } +void UCI::quit() { Command("quit"); } +void UCI::ucinewgame() { Command("ucinewgame"); } +void UCI::position(std::string fen, std::string moves) { + position(fen + " moves " + moves); +} +void UCI::position(std::string fen) { Command("position fen " + fen); } + +void UCI::go(Go go) { + // Flush data + bestmove = ""; + ponder = ""; + infostrings.clear(); + lines.clear(); + refutations.clear(); + + std::string cmd = "go"; + if (go.searchmoves.size() > 0) + cmd += " searchmoves " + go.searchmoves; + if (go.ponder.size() > 0) + cmd += " ponder " + go.ponder; + if (go.wtime >= 0) + cmd += " wtime " + std::to_string(go.wtime); + if (go.btime >= 0) + cmd += " btime " + std::to_string(go.btime); + if (go.winc >= 0) + cmd += " winc " + std::to_string(go.winc); + if (go.binc >= 0) + cmd += " binc " + std::to_string(go.binc); + if (go.movestogo >= 0) + cmd += " movestogo " + std::to_string(go.movestogo); + if (go.depth >= 0) + cmd += " depth " + std::to_string(go.depth); + if (go.mate >= 0) + cmd += " mate " + std::to_string(go.mate); + if (go.movetime >= 0) + cmd += " movetime " + std::to_string(go.movetime); + if (go.infinite) { + cmd += " infinite"; + } + Command(cmd); +} +} // namespace uciadapter
\ No newline at end of file diff --git a/src/UCI.hpp b/src/UCI.hpp new file mode 100644 index 0000000..19cc1e9 --- /dev/null +++ b/src/UCI.hpp @@ -0,0 +1,125 @@ +#ifdef UNIX +#include "ProcessLinux.hpp" +#define INIT_PROCESS(p) \ + { p = static_cast<Process *>(new ProcessLinux()); } +#else +#include "ProcessWindows.hpp" +#endif +#include <chrono> +#include <sstream> +#include <thread> +#include <unordered_map> +#include <vector> + +#define IS_OPT_PARAM(str) \ + ((str) == "name" || (str) == "type" | (str) == "default" || \ + (str) == "min" || (str) == "max" || (str) == "var") + +namespace uciadapter { + +/// @brief Empty string and option if not specified +typedef struct Option { + std::string name; + std::string type; + std::string default_value; + std::string min; + std::string max; + std::string var; +} Option; + +/// @brief All long are initiated to -1 if not set +class Info { +public: + long depth; + long seldepth; + long multipv; + long score_cp; + long score_mate; + long score_lowerbound; + long score_upperbound; + long cp; + long nodes; + long nps; + long tbhits; + long time; + long hashfull; + long cpuload; + long currmovenumber; + std::vector<std::string> pv; + std::string currmove; + Info(); +}; + +/// @brief Calling go(Go()) will perform a Command("go"). Just leave unused +/// parameters untouched +class Go { +public: + std::string searchmoves; + std::string ponder; + int wtime, btime, winc, binc, movestogo, depth, nodes, mate, movetime; + bool infinite; + Go(); +}; + +class UCI { + Process *p; + /// @brief Reset on each call to go() + std::string buffer; + /// @brief Reset on each call to go() + std::string bestmove, ponder; + /// @brief Setup on engine startup + std::string name, author; + /// @brief Setup on engine startup + std::vector<Option> options; + /// @brief Setup on engine startup + bool uciok; + bool registration_required; + bool copyprotection_failed; + bool registered; + void ParseId(std::string); + void ParseOption(std::string); + void ParseInfo(std::string); + /// @brief Reset on each call to go() + std::unordered_map<int, Info> lines; + /// @brief Reset on each call to go() + std::vector<std::string> infostrings; + /// @brief Reset on each call to go() + std::vector<std::string> refutations; + void Sync(); + +public: + UCI(std::string); + ~UCI(); + std::string GetBuffer(); + std::string GetName(); + std::string GetAuthor(); + std::string GetBestMove(); + std::vector<Option> GetOptions(); + std::unordered_map<int, Info> GetLines(); + std::vector<std::string> GetInfoStrings(); + bool IsRegistrationRequired(); + bool IsRegistered(); + void Command(std::string); + void SyncAfter(int); + + // UCI API (all in lower case) + void ucinewgame(); + void stop(); + void position(std::string, std::string); + void setoption(std::string, std::string); + void position(std::string); + void register_now(std::string, std::string); + void register_later(); + void debug(bool); + void ponderhit(); + void quit(); + void go(Go); +}; + +struct EngineError : public std::exception { + std::string msg; + EngineError(std::string reason) { msg = "Engine error: " + reason; } + const char *what() const throw() { return msg.c_str(); } +}; + +} // namespace uciadapter
\ No newline at end of file |
