From cf9bb898148cfa16475740bf0238c682254f79ee Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Thu, 14 Nov 2019 10:37:59 +0100 Subject: [PATCH] Separate XBoard protocol code Split UCI and XBoard protocol code for better maintainability. No functional change. --- src/Makefile | 2 +- src/uci.cpp | 217 ++---------------------------------------------- src/xboard.cpp | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xboard.h | 51 +++++++++++ 4 files changed, 313 insertions(+), 209 deletions(-) create mode 100644 src/xboard.cpp create mode 100644 src/xboard.h diff --git a/src/Makefile b/src/Makefile index 766be62..6a57ac1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -38,7 +38,7 @@ PGOBENCH = ./$(EXE) bench ### Object files OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ material.o misc.o movegen.o movepick.o parser.o pawns.o piece.o position.o psqt.o \ - search.o thread.o timeman.o tt.o uci.o ucioption.o variant.o syzygy/tbprobe.o + search.o thread.o timeman.o tt.o uci.o ucioption.o variant.o xboard.o syzygy/tbprobe.o ### Establish the operating system name KERNEL = $(shell uname -s) diff --git a/src/uci.cpp b/src/uci.cpp index c9fbff9..dffec4f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -31,6 +31,7 @@ #include "timeman.h" #include "tt.h" #include "uci.h" +#include "xboard.h" #include "syzygy/tbprobe.h" using namespace std; @@ -145,13 +146,6 @@ namespace { Threads.start_thinking(pos, states, limits, ponderMode); } - void xboard_go(Position& pos, Search::LimitsType limits, StateListPtr& states) { - - limits.startTime = now(); // As early as possible! - - Threads.start_thinking(pos, states, limits, false); - } - // bench() is called when engine receives the "bench" command. Firstly // a list of UCI commands is setup according to bench parameters, then // it is run one by one printing a summary at the end. @@ -203,34 +197,6 @@ namespace { Options["VariantPath"] = token; } - // do_move() is called when engine needs to change position state in XBoard mode. - - void do_move(Position& pos, std::deque& moveList, StateListPtr& states, Move m) { - - // transfer states back - if (Threads.setupStates.get()) - states = std::move(Threads.setupStates); - - if (m == MOVE_NONE) - return; - moveList.push_back(m); - states->emplace_back(); - pos.do_move(m, states->back()); - } - - // undo_move() is called when engine needs to change position state in XBoard mode. - - void undo_move(Position& pos, std::deque& moveList, StateListPtr& states) { - - // transfer states back - if (Threads.setupStates.get()) - states = std::move(Threads.setupStates); - - pos.undo_move(moveList.back()); - states->pop_back(); - moveList.pop_back(); - } - } // namespace @@ -252,13 +218,10 @@ void UCI::loop(int argc, char* argv[]) { for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; - // XBoard states - Color playColor = COLOR_NB; - bool move_after_search = false; - Search::LimitsType limits; - Search::LimitsType analysis_limits; - analysis_limits.infinite = 1; - std::deque moveList = std::deque(); + // XBoard state machine + XBoard::StateMachine xboardStateMachine; + // UCCI banmoves state + std::vector banmoves = {}; do { if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF @@ -294,177 +257,15 @@ void UCI::loop(int argc, char* argv[]) { } else if (Options["Protocol"] == "xboard") - { - if (move_after_search) - { - Threads.stop = true; - Threads.main()->wait_for_search_finished(); - do_move(pos, moveList, states, Threads.main()->bestThread->rootMoves[0].pv[0]); - move_after_search = false; - } - if (token == "protover") - { - string vars = "chess"; - 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 << "\"" - << Options << sync_endl - << "feature done=1" << sync_endl; - } - else if (token == "accepted" || token == "rejected" || token == "result" || token == "?") {} - else if (token == "new") - { - is = istringstream("startpos"); - position(pos, is, states); - // play second by default - playColor = ~pos.side_to_move(); - } - else if (token == "variant") - { - if (is >> token) - Options["UCI_Variant"] = token; - is = istringstream("startpos"); - position(pos, is, states); - } - else if (token == "force") - playColor = COLOR_NB; - else if (token == "go") - { - playColor = pos.side_to_move(); - xboard_go(pos, limits, states); - move_after_search = true; - } - else if (token == "level" || token == "st" || token == "sd" || token == "time" || token == "otim") - { - int num; - if (token == "level") - { - // moves to go - is >> limits.movestogo; - // base time - is >> token; - size_t idx = token.find(":"); - if (idx != string::npos) - num = std::stoi(token.substr(0, idx)) * 60 + std::stoi(token.substr(idx + 1)); - else - num = std::stoi(token) * 60; - limits.time[WHITE] = num * 1000; - limits.time[BLACK] = num * 1000; - // increment - is >> num; - limits.inc[WHITE] = num * 1000; - limits.inc[BLACK] = num * 1000; - } - else if (token == "sd") - is >> limits.depth; - else if (token == "st") - is >> limits.movetime; - // Note: time/otim are in centi-, not milliseconds - else if (token == "time") - { - is >> num; - limits.time[playColor != COLOR_NB ? playColor : pos.side_to_move()] = num * 10; - } - else if (token == "otim") - { - is >> num; - limits.time[playColor != COLOR_NB ? ~playColor : ~pos.side_to_move()] = num * 10; - } - } - else if (token == "setboard") - { - std::getline(is, token); - is = istringstream("fen" + token); - position(pos, is, states); - } - else if (token == "cores") - { - if (is >> token) - Options["Threads"] = token; - } - else if (token == "memory") - { - if (is >> token) - Options["Hash"] = token; - } - else if (token == "hard" || token == "easy") - Options["Ponder"] = token == "hard"; - else if (token == "option") - { - string name, value; - is.get(); - std::getline(is, name, '='); - std::getline(is, value); - if (Options.count(name)) - Options[name] = value; - } - else if (token == "analyze") - { - Options["UCI_AnalyseMode"] = string("true"); - xboard_go(pos, analysis_limits, states); - } - else if (token == "exit") - { - Threads.stop = true; - Threads.main()->wait_for_search_finished(); - Options["UCI_AnalyseMode"] = string("false"); - } - else if (token == "undo") - { - if (moveList.size()) - { - if (Options["UCI_AnalyseMode"]) - { - Threads.stop = true; - Threads.main()->wait_for_search_finished(); - } - undo_move(pos, moveList, states); - if (Options["UCI_AnalyseMode"]) - xboard_go(pos, analysis_limits, states); - } - } - // Additional custom non-UCI commands, mainly for debugging. - else if (token == "perft") - { - Search::LimitsType perft_limits; - is >> perft_limits.perft; - xboard_go(pos, perft_limits, states); - } - // Move strings and unknown commands - else - { - // process move string - if (token == "usermove") - is >> token; - if (Options["UCI_AnalyseMode"]) - { - Threads.stop = true; - Threads.main()->wait_for_search_finished(); - } - Move m; - if ((m = UCI::to_move(pos, token)) != MOVE_NONE) - do_move(pos, moveList, states, m); - else - sync_cout << "Error (unkown command): " << token << sync_endl; - if (Options["UCI_AnalyseMode"]) - xboard_go(pos, analysis_limits, states); - else if (pos.side_to_move() == playColor) - { - xboard_go(pos, limits, states); - move_after_search = true; - } - } - } + xboardStateMachine.process_command(pos, token, is, states); else if (token == "setoption") setoption(is); // 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(); + banmoves.push_back(UCI::to_move(pos, token)); + else if (token == "go") go(pos, is, states, banmoves); + else if (token == "position") position(pos, is, states), banmoves.clear(); else if (token == "ucinewgame" || token == "usinewgame" || token == "uccinewgame") Search::clear(); else if (token == "isready") sync_cout << "readyok" << sync_endl; diff --git a/src/xboard.cpp b/src/xboard.cpp new file mode 100644 index 0000000..b8c88f7 --- /dev/null +++ b/src/xboard.cpp @@ -0,0 +1,252 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2019 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "search.h" +#include "thread.h" +#include "types.h" +#include "uci.h" +#include "xboard.h" + +namespace { + + const Search::LimitsType analysisLimits = []{ + Search::LimitsType limits; + limits.infinite = 1; + return limits; + }(); + + // go() starts the search for game play, analysis, or perft. + + void go(Position& pos, Search::LimitsType limits, StateListPtr& states) { + + limits.startTime = now(); // As early as possible! + + Threads.start_thinking(pos, states, limits, false); + } + + // setboard() is called when engine receives the "setboard" XBoard command. + + void setboard(Position& pos, StateListPtr& states, std::string fen = "") { + + if (fen.empty()) + fen = variants.find(Options["UCI_Variant"])->second->startFen; + + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + } + + // do_move() is called when engine needs to apply a move when using XBoard protocol. + + void do_move(Position& pos, std::deque& moveList, StateListPtr& states, Move m) { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + if (m == MOVE_NONE) + return; + moveList.push_back(m); + states->emplace_back(); + pos.do_move(m, states->back()); + } + + // undo_move() is called when the engine receives the undo command in XBoard protocol. + + void undo_move(Position& pos, std::deque& moveList, StateListPtr& states) { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + pos.undo_move(moveList.back()); + states->pop_back(); + moveList.pop_back(); + } + +} // namespace + +namespace XBoard { + +/// StateMachine::process_command() processes commands of the XBoard protocol. + +void StateMachine::process_command(Position& pos, std::string token, std::istringstream& is, StateListPtr& states) { + if (moveAfterSearch) + { + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + do_move(pos, moveList, states, Threads.main()->bestThread->rootMoves[0].pv[0]); + moveAfterSearch = false; + } + if (token == "protover") + { + std::string vars = "chess"; + for (std::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 << "\"" + << Options << sync_endl + << "feature done=1" << sync_endl; + } + else if (token == "accepted" || token == "rejected" || token == "result" || token == "?") {} + else if (token == "new") + { + setboard(pos, states); + // play second by default + playColor = ~pos.side_to_move(); + } + else if (token == "variant") + { + if (is >> token) + Options["UCI_Variant"] = token; + setboard(pos, states); + } + else if (token == "force") + playColor = COLOR_NB; + else if (token == "go") + { + playColor = pos.side_to_move(); + go(pos, limits, states); + moveAfterSearch = true; + } + else if (token == "level" || token == "st" || token == "sd" || token == "time" || token == "otim") + { + int num; + if (token == "level") + { + // moves to go + is >> limits.movestogo; + // base time + is >> token; + size_t idx = token.find(":"); + if (idx != std::string::npos) + num = std::stoi(token.substr(0, idx)) * 60 + std::stoi(token.substr(idx + 1)); + else + num = std::stoi(token) * 60; + limits.time[WHITE] = num * 1000; + limits.time[BLACK] = num * 1000; + // increment + is >> num; + limits.inc[WHITE] = num * 1000; + limits.inc[BLACK] = num * 1000; + } + else if (token == "sd") + is >> limits.depth; + else if (token == "st") + is >> limits.movetime; + // Note: time/otim are in centi-, not milliseconds + else if (token == "time") + { + is >> num; + limits.time[playColor != COLOR_NB ? playColor : pos.side_to_move()] = num * 10; + } + else if (token == "otim") + { + is >> num; + limits.time[playColor != COLOR_NB ? ~playColor : ~pos.side_to_move()] = num * 10; + } + } + else if (token == "setboard") + { + std::string fen; + std::getline(is >> std::ws, fen); + setboard(pos, states, fen); + } + else if (token == "cores") + { + if (is >> token) + Options["Threads"] = token; + } + else if (token == "memory") + { + if (is >> token) + Options["Hash"] = token; + } + else if (token == "hard" || token == "easy") + Options["Ponder"] = token == "hard"; + else if (token == "option") + { + std::string name, value; + is.get(); + std::getline(is, name, '='); + std::getline(is, value); + if (Options.count(name)) + Options[name] = value; + } + else if (token == "analyze") + { + Options["UCI_AnalyseMode"] = std::string("true"); + go(pos, analysisLimits, states); + } + else if (token == "exit") + { + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + Options["UCI_AnalyseMode"] = std::string("false"); + } + else if (token == "undo") + { + if (moveList.size()) + { + if (Options["UCI_AnalyseMode"]) + { + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + } + undo_move(pos, moveList, states); + if (Options["UCI_AnalyseMode"]) + go(pos, analysisLimits, states); + } + } + // Additional custom non-XBoard commands + else if (token == "perft") + { + Search::LimitsType perft_limits; + is >> perft_limits.perft; + go(pos, perft_limits, states); + } + // Move strings and unknown commands + else + { + // process move string + if (token == "usermove") + is >> token; + if (Options["UCI_AnalyseMode"]) + { + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + } + Move m; + if ((m = UCI::to_move(pos, token)) != MOVE_NONE) + do_move(pos, moveList, states, m); + else + sync_cout << "Error (unkown command): " << token << sync_endl; + if (Options["UCI_AnalyseMode"]) + go(pos, analysisLimits, states); + else if (pos.side_to_move() == playColor) + { + go(pos, limits, states); + moveAfterSearch = true; + } + } +} + +} // namespace XBoard diff --git a/src/xboard.h b/src/xboard.h new file mode 100644 index 0000000..ab3a3d6 --- /dev/null +++ b/src/xboard.h @@ -0,0 +1,51 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2019 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef XBOARD_H_INCLUDED +#define XBOARD_H_INCLUDED + +#include +#include + +#include "types.h" + +class Position; + +namespace XBoard { + +/// StateMachine class maintains the states required by XBoard protocol + +class StateMachine { +public: + StateMachine() { + moveList = std::deque(); + moveAfterSearch = false; + playColor = COLOR_NB; + } + void process_command(Position& pos, std::string token, std::istringstream& is, StateListPtr& states); + +private: + std::deque moveList; + Search::LimitsType limits; + bool moveAfterSearch; + Color playColor; +}; + +} // namespace XBoard + +#endif // #ifndef XBOARD_H_INCLUDED -- 1.7.0.4