Separate XBoard protocol code
authorFabian Fichter <ianfab@users.noreply.github.com>
Thu, 14 Nov 2019 09:37:59 +0000 (10:37 +0100)
committerFabian Fichter <ianfab@users.noreply.github.com>
Thu, 14 Nov 2019 09:58:26 +0000 (10:58 +0100)
Split UCI and XBoard protocol code for better maintainability.

No functional change.

src/Makefile
src/uci.cpp
src/xboard.cpp [new file with mode: 0644]
src/xboard.h [new file with mode: 0644]

index 766be62..6a57ac1 100644 (file)
@@ -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)
index c9fbff9..dffec4f 100644 (file)
@@ -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<Move>& 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<Move>& 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<Move> moveList = std::deque<Move>();
+  // XBoard state machine
+  XBoard::StateMachine xboardStateMachine;
+  // UCCI banmoves state
+  std::vector<Move> 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 (file)
index 0000000..b8c88f7
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <iostream>
+#include <string>
+
+#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<StateInfo>(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<Move>& 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<Move>& 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 (file)
index 0000000..ab3a3d6
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef XBOARD_H_INCLUDED
+#define XBOARD_H_INCLUDED
+
+#include <sstream>
+#include <string>
+
+#include "types.h"
+
+class Position;
+
+namespace XBoard {
+
+/// StateMachine class maintains the states required by XBoard protocol
+
+class StateMachine {
+public:
+  StateMachine() {
+    moveList = std::deque<Move>();
+    moveAfterSearch = false;
+    playColor = COLOR_NB;
+  }
+  void process_command(Position& pos, std::string token, std::istringstream& is, StateListPtr& states);
+
+private:
+  std::deque<Move> moveList;
+  Search::LimitsType limits;
+  bool moveAfterSearch;
+  Color playColor;
+};
+
+} // namespace XBoard
+
+#endif // #ifndef XBOARD_H_INCLUDED