From: Fabian Fichter Date: Sat, 9 Nov 2019 14:54:04 +0000 (+0100) Subject: Support UCCI protocol X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=4889b1dee66b80ac3292d167baf4c543788a29d6;p=fairystockfish.git Support UCCI protocol http://www.xqbase.com/protocol/cchess_ucci.htm Closes #46. --- diff --git a/Readme.md b/Readme.md index 4e6d993..04011af 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ [![Build Status](https://travis-ci.org/ianfab/Fairy-Stockfish.svg?branch=master)](https://travis-ci.org/ianfab/Fairy-Stockfish) [![Build Status](https://ci.appveyor.com/api/projects/status/github/ianfab/Fairy-Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/ianfab/Fairy-Stockfish/branch/master) -Fairy-Stockfish is a UCI/USI/XBoard chess variant engine derived from [Stockfish](https://github.com/official-stockfish/Stockfish/) designed for the support of fairy chess variants and easy extensibility with more games. It can play various historical, regional, and modern chess variants as well [games with user-defined rules](https://github.com/ianfab/Fairy-Stockfish/wiki/Variant-configuration). +Fairy-Stockfish is a UCI/UCCI/USI/XBoard chess variant engine derived from [Stockfish](https://github.com/official-stockfish/Stockfish/) designed for the support of fairy chess variants and easy extensibility with more games. It can play various historical, regional, and modern chess variants as well [games with user-defined rules](https://github.com/ianfab/Fairy-Stockfish/wiki/Variant-configuration). The goal of the project is to create an engine supporting a large variety of chess-like games, equipped with the powerful search of Stockfish. It is complementary to Stockfish forks more specialized for certain chess variants, such as [multi-variant Stockfish](https://github.com/ddugovic/Stockfish), [Seirawan-Stockfish](https://github.com/ianfab/Seirawan-Stockfish), [Makruk-Stockfish](https://github.com/ianfab/Makruk-Stockfish), etc., supporting many more variants with the tradeoff of slightly lower performance compared to a specialized implementation. diff --git a/src/search.h b/src/search.h index c77ca3a..10c04e1 100644 --- a/src/search.h +++ b/src/search.h @@ -93,7 +93,7 @@ struct LimitsType { return !(mate | movetime | depth | nodes | perft | infinite); } - std::vector searchmoves; + std::vector searchmoves, banmoves; TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; int64_t nodes; diff --git a/src/thread.cpp b/src/thread.cpp index 5b4decd..a8c18a6 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -184,8 +184,8 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) - if ( limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + if ( (limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + && (limits.banmoves.empty() || !std::count(limits.banmoves.begin(), limits.banmoves.end(), m))) rootMoves.emplace_back(m); if (!rootMoves.empty()) diff --git a/src/uci.cpp b/src/uci.cpp index bad9884..d6177e9 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -85,6 +85,9 @@ namespace { is >> token; // Consume "name" token + if (Options["Protocol"] == "ucci") + name = token; + else // Read option name (can contain spaces) while (is >> token && token != "value") name += (name.empty() ? "" : " ") + token; @@ -95,6 +98,8 @@ namespace { if (Options.count(name)) Options[name] = value; + else if (Options["Protocol"] == "ucci" && (std::replace(name.begin(), name.end(), '_', ' '), Options.count(name))) + Options[name] = value; else sync_cout << "No such option: " << name << sync_endl; } @@ -104,7 +109,7 @@ namespace { // the thinking time and other parameters from the input string, then starts // the search. - void go(Position& pos, istringstream& is, StateListPtr& states) { + void go(Position& pos, istringstream& is, StateListPtr& states, const std::vector& banmoves = {}) { Search::LimitsType limits; string token; @@ -112,6 +117,8 @@ namespace { limits.startTime = now(); // As early as possible! + limits.banmoves = banmoves; + while (is >> token) if (token == "searchmoves") while (is >> token) @@ -129,6 +136,11 @@ namespace { else if (token == "perft") is >> limits.perft; else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") ponderMode = true; + // UCCI commands + else if (token == "time") is >> limits.time[pos.side_to_move()]; + else if (token == "opptime") is >> limits.time[~pos.side_to_move()]; + else if (token == "increment") is >> limits.inc[pos.side_to_move()]; + else if (token == "oppinc") is >> limits.inc[~pos.side_to_move()]; Threads.start_thinking(pos, states, limits, ponderMode); } @@ -268,12 +280,14 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "ponderhit") Threads.main()->ponder = false; // Switch to normal search - else if (token == "uci" || token == "usi" || token == "xboard") + else if (token == "uci" || token == "usi" || token == "ucci" || token == "xboard") { Options["Protocol"] = token; + if (token == "ucci") + Options["UCI_Variant"] = string("xiangqi"); if (token != "xboard") sync_cout << "id name " << engine_info(true) - << "\n" << Options + << "\n" << Options << "\n" << token << "ok" << sync_endl; } @@ -292,7 +306,8 @@ void UCI::loop(int argc, char* argv[]) { for (string v : variants.get_keys()) if (v != "chess") vars += "," + v; - sync_cout << "feature setboard=1 usermove=1 time=1 memory=1 smp=1 colors=0 draw=0 name=0 sigint=0 myname=Fairy-Stockfish variants=\"" << vars << "\"" + sync_cout << "feature setboard=1 usermove=1 time=1 memory=1 smp=1 colors=0 draw=0 name=0 sigint=0 myname=Fairy-Stockfish variants=\"" + << vars << "\"" << Options << sync_endl << "feature done=1" << sync_endl; } @@ -434,9 +449,13 @@ void UCI::loop(int argc, char* argv[]) { } 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" || token == "usinewgame") Search::clear(); + // UCCI-specific banmoves command + else if (token == "banmoves") + while (is >> token) + limits.banmoves.push_back(UCI::to_move(pos, token)); + else if (token == "go") go(pos, is, states, limits.banmoves); + else if (token == "position") position(pos, is, states), limits.banmoves.clear(); + else if (token == "ucinewgame" || token == "usinewgame" || token == "uccinewgame") Search::clear(); else if (token == "isready") sync_cout << "readyok" << sync_endl; // Additional custom non-UCI commands, mainly for debugging. @@ -495,7 +514,7 @@ std::string UCI::square(const Position& pos, Square s) { : std::string{ char('0' + (pos.max_file() - file_of(s) + 1) / 10), char('0' + (pos.max_file() - file_of(s) + 1) % 10), char('a' + pos.max_rank() - rank_of(s)) }; - else if (Options["Protocol"] == "xboard" && pos.max_rank() == RANK_10) + else if ((Options["Protocol"] == "xboard" || Options["Protocol"] == "ucci") && pos.max_rank() == RANK_10) return std::string{ char('a' + file_of(s)), char('0' + rank_of(s)) }; else return rank_of(s) < RANK_10 ? std::string{ char('a' + file_of(s)), char('1' + (rank_of(s) % 10)) } diff --git a/src/ucioption.cpp b/src/ucioption.cpp index fbffb90..d4f653b 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -95,7 +95,7 @@ void init(OptionsMap& o) { // at most 2^32 clusters. constexpr int MaxHashMB = Is64Bit ? 131072 : 2048; - o["Protocol"] << Option("uci", {"uci", "usi", "xboard"}); + o["Protocol"] << Option("uci", {"uci", "usi", "ucci", "xboard"}); o["Debug Log File"] << Option("", on_logger); o["Contempt"] << Option(24, -100, 100); o["Analysis Contempt"] << Option("Both", {"Both", "Off", "White", "Black"}); @@ -161,7 +161,14 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { if (it.second.idx == idx) { const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; + if (Options["Protocol"] == "ucci") + { + string name = it.first; + std::replace(name.begin(), name.end(), ' ', '_'); + os << "\noption " << name << " type " << o.type; + } + else + os << "\noption name " << it.first << " type " << o.type; if (o.type == "string" || o.type == "check" || o.type == "combo") os << " default " << o.defaultValue;