Skip to content

Commit

Permalink
improved uci commands, minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Nonlinear2 committed Jun 8, 2024
1 parent 6f42610 commit 84b45a5
Show file tree
Hide file tree
Showing 15 changed files with 65 additions and 78 deletions.
27 changes: 8 additions & 19 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(bread_engine)
project(bread_engine VERSION 0.0.2)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
Expand All @@ -9,20 +9,9 @@ set(bread_UTILS ${CMAKE_CURRENT_SOURCE_DIR}/utils)
set(bread_DEPENDENCIES ${CMAKE_CURRENT_SOURCE_DIR}/dependencies)
set(bread_NEURAL_NETWORKS ${CMAKE_CURRENT_SOURCE_DIR}/neural_networks)

option(bread_USE_TB "use endgame tablebase" ON)
option(bread_EMBED_NN "embed neural network in the executable" ON)

message(STATUS "use endgame tablebase: ${bread_USE_TB}")
message(STATUS "embed neural network in executable: ${bread_EMBED_NN}")

# this path is relative to the executable. It can be changed if needed
set(bread_NNUE_MODEL_PATH
"${bread_NEURAL_NETWORKS}/model_NNUE_2-40960_2x256_8_8_1_quant_layers"
)

# this path is relative to the executable. It can be changed if needed
set(bread_TB_PATH
"3-4-5_pieces_Syzygy/3-4-5"
"${bread_NEURAL_NETWORKS}/model_NNUE_2-40960_2x256_32_32_1_quant_layers"
)

set(bread_NN_HEADER_PATH
Expand All @@ -33,23 +22,19 @@ set(bread_INCLUDE_DIRS
${bread_DEPENDENCIES}
${bread_DEPENDENCIES}/extern
${bread_DEPENDENCIES}/unit_tests
${bread_DEPENDENCIES}/extern/Fathom/src
)

if(bread_USE_TB)
set(bread_INCLUDE_DIRS ${bread_INCLUDE_DIRS} ${bread_DEPENDENCIES}/extern/Fathom/src)
endif(bread_USE_TB)

set(bread_SOURCE
${bread_SRC}/search_board.cpp
${bread_SRC}/nnue/nnue.cpp
${bread_SRC}/nnue/nnue_board.cpp
${bread_SRC}/transposition_table.cpp
${bread_SRC}/bread_engine_core.cpp
${bread_DEPENDENCIES}/extern/Fathom/src/tbprobe.cpp
)

if(bread_USE_TB)
set(bread_SOURCE ${bread_SOURCE} ${bread_DEPENDENCIES}/extern/Fathom/src/tbprobe.cpp)
endif(bread_USE_TB)


function(bread_configure bread_TARGET)
Expand All @@ -59,12 +44,16 @@ function(bread_configure bread_TARGET)

target_include_directories(${bread_TARGET} PRIVATE ${bread_INCLUDE_DIRS})

target_compile_definitions(${bread_TARGET} PRIVATE bread_VERSION="${CMAKE_PROJECT_VERSION}")

target_compile_definitions(${bread_TARGET} PRIVATE bread_USE_TB=$<BOOL:${bread_USE_TB}>)

target_compile_definitions(${bread_TARGET} PRIVATE bread_EMBED_NN=$<BOOL:${bread_EMBED_NN}>)

if (TARGET generate_neural_net_header)
target_compile_definitions(${bread_TARGET} PRIVATE bread_EMBED_NN=false)
else()
target_compile_definitions(${bread_TARGET} PRIVATE bread_EMBED_NN=true)
endif()

target_compile_definitions(${bread_TARGET} PRIVATE bread_TB_PATH="${bread_TB_PATH}")
Expand Down
5 changes: 0 additions & 5 deletions dependencies/search_board.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@

#include "chess.hpp"
#include "piece_square_tables.hpp"

#if bread_USE_TB
#include "tbprobe.hpp"
#endif

#include <string>

class SearchBoard: public chess::Board {
public:
SearchBoard();
SearchBoard(std::string_view fen);

virtual void synchronize() = 0;

Expand Down
2 changes: 2 additions & 0 deletions dependencies/uci.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class UCIAgent {

std::vector<std::string> split_string(std::string str);

void process_setoption(std::vector<std::string> command);

void process_position(std::vector<std::string> command);

void process_go(std::vector<std::string> command);
Expand Down
18 changes: 5 additions & 13 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@
Bread engine is a chess engine written in c++. I started working on it in 2021, and only just finished. There is still a lot of room for improvement, but the engine is quite strong (for humans, at least). It uses NNUE (efficiently updatable neural network) to evaluate positions, as well as minimax search.
Bread engine does not have a GUI built in, however it supports the uci protocol, you can therefore run it on any chess GUI.

The engine has two build options:

- Option 1: use endgame tablebase
If this option is on, the engine will search for a folder next to the executable, named `3-4-5_pieces_Syzygy` containing the tablebase. You can download the folder [here](https://chess.massimilianogoi.com/download/tablebases/Syzygy3-4-5/). Thanks to Massimiliano Goi for sharing this tablebase.

- Option 2: embed neural network in executable.
If this option is off, the engine will search for the folder `model_NNUE_2-40960_2x256_8_8_1_quant_layers` next to the executable.

# Installation
For windows, you can find precompiled binaries in the release section. There are 4 of them, one for each option combination.
For windows, you can find the precompiled binary in the release section.
Please note that the engine requires a cpu with AVX2 support.

If you want to change the tablebase or neural network path, you can build the engine yourself using cmake.

Once you have the engine installed, you can run the executable, and write the following commands:
Once you have the engine installed, you can run the executable, and write the following commands one after the other:

```console
uci
position startpos
go wtime 10000 btime 10000 winc 1000 binc 1000
go movetime 5000
```

and the engine will return the best move. To set a position with a specific fen, you can use `position fen 5rkq/3prp1p/5RpP/p1p5/5Q2/1B4P1/P4PK1/8 w - a6 0 51` for instance. You can find more details on the [uci protocol](https://www.wbec-ridderkerk.nl/html/UCIProtocol.html) webpage. Please note that only the main commands are supported.

Besides the main engine build, you can also build some utility code to:
Expand Down
11 changes: 5 additions & 6 deletions src/Bread_engine_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,11 @@ chess::Move Engine::iterative_deepening(int time_limit_, int min_depth_=4, int m

engine_color = (inner_board.sideToMove() == chess::Color::WHITE) ? 1: -1;

#if bread_USE_TB
chess::Move tb_move;
if (inner_board.probe_dtz(tb_move)){
std::cout << "bestmove " << chess::uci::moveToUci(tb_move) << std::endl;
return tb_move;
};
#endif

while (true){
search_depth = current_depth;
Expand Down Expand Up @@ -438,13 +436,11 @@ float Engine::negamax(int depth, int color, float alpha, float beta){
float Engine::qsearch(float alpha, float beta, int color, int depth){
nodes++;

#if bread_USE_TB
// tablebase probe
float wdl_eval;
if (inner_board.probe_wdl(wdl_eval)){
return wdl_eval;
}
#endif

// this is recomputed when qsearch is called the first time. Performance loss is probably low.
uint64_t zobrist_hash = inner_board.hash();
Expand All @@ -461,7 +457,8 @@ float Engine::qsearch(float alpha, float beta, int color, int depth){

// we can already check for cutoff
if (stand_pat >= beta){
return stand_pat;
// return stand_pat;
return beta;
}
} else if (transposition->flag == TFlag::EXACT){
// this means that it is either a higher depth search, in which case we can return,
Expand Down Expand Up @@ -493,7 +490,8 @@ float Engine::qsearch(float alpha, float beta, int color, int depth){
stand_pat = inner_board.evaluate();
transposition_table.store(zobrist_hash, stand_pat, 0, NO_MOVE, TFlag::EXACT, static_cast<uint8_t>(inner_board.fullMoveNumber()));
if (stand_pat >= beta){
return stand_pat;
// return stand_pat;
return beta;
}

if (depth == 0){
Expand Down Expand Up @@ -523,6 +521,7 @@ float Engine::qsearch(float alpha, float beta, int color, int depth){
if (pos_eval > alpha){
alpha = pos_eval;
if (alpha >= beta){ // only check for cutoffs when alpha gets updated.
// return alpha;
return beta;
}
}
Expand Down
20 changes: 0 additions & 20 deletions src/search_board.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
#include "search_board.hpp"

SearchBoard::SearchBoard(): chess::Board::Board(){
#if bread_USE_TB
tb_init(bread_TB_PATH);
#endif
};

SearchBoard::SearchBoard(std::string_view fen): chess::Board::Board(fen) {
#if bread_USE_TB
tb_init(bread_TB_PATH);
#endif
};

bool SearchBoard::probe_wdl(float& eval){
#if bread_USE_TB
if (occ().count() > 5){
return false;
}
Expand All @@ -39,13 +26,9 @@ bool SearchBoard::probe_wdl(float& eval){
return true;
}
return false;
#else
return false;
#endif
}

bool SearchBoard::probe_dtz(chess::Move& move){
#if bread_USE_TB
if (occ().count() > TB_LARGEST){
return false;
}
Expand Down Expand Up @@ -101,7 +84,4 @@ bool SearchBoard::probe_dtz(chess::Move& move){
move.setScore(0);
}
return true;
#else
return false;
#endif
}
60 changes: 45 additions & 15 deletions src/uci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ bool UCIAgent::process_uci_command(std::string command){
std::string first = parsed_command[0];

if (first == "uci"){
std::cout << "id name Bread Engine\n";
std::cout << "id author Nonlinear\n";
std::cout << "uciok\n";
std::cout << "id name Bread Engine " << bread_VERSION << std::endl;
std::cout << "id author Nonlinear" << std::endl;
std::cout << std::endl;
std::cout << "option name SyzygyPath type string default <empty>" << std::endl;
std::cout << "uciok" << std::endl;
} else if (first == "setoption"){
process_setoption(parsed_command);

} else if (first == "isready"){
std::cout << "readyok\n";
std::cout << "readyok" << std::endl;

} else if (first == "ucinewgame"){
engine.transposition_table.clear();
Expand All @@ -30,9 +34,7 @@ bool UCIAgent::process_uci_command(std::string command){

} else if (first == "quit"){
interrupt_if_searching();
#if bread_USE_TB
tb_free();
#endif
return 0;

} else {
Expand All @@ -54,6 +56,25 @@ std::vector<std::string> UCIAgent::split_string(std::string str){
return split;
}

void UCIAgent::process_setoption(std::vector<std::string> command){
std::string option_name = command[2];
std::transform(option_name.begin(), option_name.end(), option_name.begin(), [](unsigned char c){ return std::tolower(c); });
if (option_name == "syzygypath"){
std::string path = command[4];
for (int i = 5; i < command.size(); i++){
path += " " + command[i];
}
bool tb_success = tb_init(path.c_str());
if (!tb_success){
std::cout << "info string tablebase initialisation failed" << std::endl;
} else if (TB_LARGEST == 0){
std::cout << "info string no tablebase loaded" << std::endl;
} else {
std::cout << "info string tablebase loaded" << std::endl;
}
}
}

void UCIAgent::process_position(std::vector<std::string> command){
interrupt_if_searching();

Expand Down Expand Up @@ -81,6 +102,7 @@ void UCIAgent::process_position(std::vector<std::string> command){
}

void UCIAgent::process_go(std::vector<std::string> command){
bool exact_movetime = false;
int wtime = -1;
int btime = -1;
int inc = 0;
Expand All @@ -96,18 +118,26 @@ void UCIAgent::process_go(std::vector<std::string> command){
} else if (token == "movestogo"){
movestogo = std::stoi(command[i+1]);
// normally, handle depth, node, mate, movetime command now
} else if (token == "movetime"){
exact_movetime = true;
think_time = std::stoi(command[i+1]);
break;
} else if (token == "infinite"){
wtime = btime = INT32_MAX;
exact_movetime = true;
think_time = INT32_MAX;
break;
}
}
if ((wtime == -1) || (btime == -1)){
std::cout << "no time specified\n";
return;
};

int engine_time_left = (engine.inner_board.sideToMove() == chess::Color::WHITE) ? wtime: btime;

think_time = static_cast<int>(engine.get_think_time(engine_time_left, num_moves_out_of_book, movestogo, inc));
if (!exact_movetime){
if ((wtime == -1) || (btime == -1)){
std::cout << "no time specified\n";
return;
};
int engine_time_left = (engine.inner_board.sideToMove() == chess::Color::WHITE) ? wtime: btime;

think_time = static_cast<int>(engine.get_think_time(engine_time_left, num_moves_out_of_book, movestogo, inc));
}

if (command[1] == "ponder"){
main_search_thread = std::thread(&Engine::iterative_deepening,
&engine, INT32_MAX, 0, 25); // infinite search time
Expand Down

0 comments on commit 84b45a5

Please sign in to comment.