UCILoader
C++ 17 library for working with chess engines. Effortlessly open, configure and query UCI engines from your C++ codebase. Easily modify UCI client to support even the most exotic chess variants.
About the Project
UCILoader is a cross platform library implementing an UCI protocol client. It was designed to provide an easily customizable entry point for creating tools and application for both regular and variant chess.
Getting Started
Prerequisites
This project uses cmake build system, so in order to build the library make sure you have cmake installed.
Building from source
In order to build the library from source code you should clone this repository then use cmake to generate build files for you:
git clone https://github.com/Pawwz85/UCILoader.git
cd UCILoader
cmake -B build
Depending on your OS and your build system the next step will differ. On windows, if you are using visual studio, enter generated build folder and open .sln file in visual studio then compile the solution (you can use ctrl+shift+b shortcut). On Linux simply execute following commands:
Running Tests
To run tests, run the following command (in build directory)
Linking library (in cmake)
If you are using cmake, the fastest way to add this library to your project is to simply add it to your project source folder. Then in your CMakeLists.txt simply add the following line:
add_subdirectory(UCILoader)
Alternatively, you can use FetchContent module to download and build the library for you:
include(FetchContent)
FetchContent_Declare(
UCILoader
URL https://github.com/Pawwz85/UCILoader/archive/refs/tags/v1.1.2.zip
)
Then link it like this:
target_link_libraries(MyProject PUBLIC uciloader)
Usage
Opening Engine
#include <iostream>
#include <UCILoader/EngineConnection.h>
#include <UCILoader/StandardChess.h>
using namespace std;
using namespace UCILoader;
int main() {
auto proces = openProcess({"Path_To_Engine_Executable.exe"}, "/");
auto instance = StandardChess::ChessEngineInstanceBuilder->build(proces);
instance->sync();
cout << "Engine: " << instance->getName() << "\n";
cout << "Author: " << instance->getAuthor() << "\n";
cout << "Latency: " << instance->ping().count() << " ms\n";
Logging UCI Protocol Communication
UCILoader provides a flexible logging system to capture and analyze the UCI protocol messages exchanged with engines. Loggers can be created from various sources and customized with traits.
Basic Logging:
auto instance = StandardChess::ChessEngineInstanceBuilder->build(
proces,
Loggers::toFile("engine.log") | LoggerTraits::Pretty
);
Available Loggers:
Loggers::toNoting() - Discards all messages (no-op logger)
Loggers::toStd() - Outputs to standard output (stdout)
Loggers::toStderr() - Outputs to standard error
Loggers::toFile(filename) - Logs to a file
Loggers::toBuffer(buffer) - Stores messages in an in-memory buffer
Loggers::toCallback(callback) - Forwards messages to a custom callback function
Loggers::toOstream(stream) - Logs to any std::ostream
Available Traits:
Traits customize logger behavior using the pipe operator |:
LoggerTraits::Pretty - Adds direction prefixes (>> for sent, << for received, Parser: for parsed)
LoggerTraits::Timestamp - Prepends timestamps to each message
LoggerTraits::IgnoreParser - Filters out parser-generated messages
LoggerTraits::IgnoreEngine - Filters out messages from the engine
LoggerTraits::IgnoreApplication - Filters out messages sent to the engine
Logging Examples:
Note on Move Semantics: LoggerBuilder uses move semantics and has no copy constructor. When binding a logger composition to a variable, use std::move() to transfer ownership. Inline compositions (used directly in function arguments) automatically apply move semantics.
Method Comparison:
Both of these approaches are equivalent so choose one based on your code style:
auto instance1 = StandardChess::ChessEngineInstanceBuilder->build(
proces,
Loggers::toFile("engine.log") | LoggerTraits::Pretty | LoggerTraits::Timestamp
);
auto logger = Loggers::toFile("engine.log") | LoggerTraits::Pretty | LoggerTraits::Timestamp;
auto instance2 = StandardChess::ChessEngineInstanceBuilder->build(proces, std::move(logger));
Full Examples:
auto logger1 = Loggers::toFile("engine.log")
| LoggerTraits::Pretty
| LoggerTraits::Timestamp;
auto instance1 = StandardChess::ChessEngineInstanceBuilder->build(proces, std::move(logger1));
auto logger2 = Loggers::toFile("responses.log")
| LoggerTraits::IgnoreParser
| LoggerTraits::IgnoreApplication
| LoggerTraits::Timestamp;
auto instance2 = StandardChess::ChessEngineInstanceBuilder->build(proces, std::move(logger2));
auto logger3 = Loggers::toBuffer(buffer) | LoggerTraits::Pretty;
auto instance3 = StandardChess::ChessEngineInstanceBuilder->build(proces, std::move(logger3));
for (const auto& msg : messages) {
cout << msg << endl;
}
if (dir == Logger::FromEngine) {
cout << "Engine: " << msg;
}
});
auto instance4 = StandardChess::ChessEngineInstanceBuilder->build(proces, std::move(logger4));
Thread-safe buffer for storing log messages.
Definition: Logger.h:449
std::vector< std::string > snapshot() const
Get a snapshot of all messages currently in the buffer.
Definition: Logger.cpp:236
MessageDirection
Enumeration of message directions for logging purposes.
Definition: Logger.h:37
Custom Logger:
For specialized logging needs, implement a custom logger and use Loggers::from<T>():
private:
std::ofstream file;
public:
MyCustomLogger(const std::string& filename) : file(filename) {}
void log(MessageDirection dir,
const std::string& msg)
override {
std::string prefix = (dir ==
ToEngine) ?
"[CMD] " :
"[RSP] ";
file << prefix << msg << std::flush;
}
};
auto instance = StandardChess::ChessEngineInstanceBuilder->build(
proces,
Loggers::from<MyCustomLogger>("engine.log") | LoggerTraits::Timestamp
);
Base interface for logging UCI protocol messages.
Definition: Logger.h:32
@ ToEngine
Message sent from the application to the chess engine.
Definition: Logger.h:38
virtual void log(MessageDirection dir, const std::string &msg)=0
Log a message with a specified direction.
Configure Engine Options
if (instance->options.contains("UCI_ShowWDL")) {
instance->options["UCI_ShowWDL"] = true;
}
if (instance->options.contains("Hash")) {
int defaultHash = instance->options["Hash"];
cout << "Engine uses by default hash table of size " << defaultHash << "\n";
instance->options["Hash"] = 16;
}
Query Engine for best move
GoParamsBuilder<StandardChessMove> paramsBuilder;
searchConnection->waitFor(std::chrono::milliseconds(550));
auto status = searchConnection->getStatus();
if (status == UCILoader::ResultReady) {
cout << "Best starting move is " << StandardChess::stringValueOf(*searchConnection->getResult().bestMove) << "\n";
if (searchConnection->getResult().ponderMove)
cout << "Engine thinks " << StandardChess::stringValueOf(*searchConnection->getResult().ponderMove) << " is best response for black\n";
}
else {
cout << "Search status code timed out with a status code: " << status<<'\n';
}
Position formatter for the standard chess starting position.
Definition: StandardChess.h:250
Defining a callback for capturing engine info
Info<StandardChessMove> info = *(Info<StandardChessMove>*)e->
getPayload();
if (info.getType() == Pv) {
cout << "Pv: ";
for (auto& m : info.getMoveArray()) {
cout << StandardChess::stringValueOf(m) << " ";
}
cout << "\n";
}
}
Base class for events emitted by an EngineInstance.
Definition: EngineEvent.h:42
virtual const void * getPayload() const =0
Get the payload data associated with this event.
Registering a callback capturing engine info
instance->connect(displayPv, UCILoader::NamedEngineEvents::InfoReceived);
Closing Engine
Documentation
The library documentation is in its source. You can either view it in your favorite code editor, generate it using doxygen or read it here: https://pawwz85.github.io/UCILoader/