summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLoic Guegan <manzerbredes@mailbox.org>2022-01-30 20:19:02 +0100
committerLoic Guegan <manzerbredes@mailbox.org>2022-02-01 19:58:14 +0100
commitf5d9fe6211bd397deb0ac19b634f551edfa0f6b8 (patch)
tree3568c011bdd4dcd201db8ca6ee098da70a1fd53f /src
Initialize project
Diffstat (limited to 'src')
-rw-r--r--src/Process.hpp28
-rw-r--r--src/ProcessLinux.cpp72
-rw-r--r--src/ProcessLinux.hpp26
-rw-r--r--src/UCI.cpp281
-rw-r--r--src/UCI.hpp125
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