{
rootMoves.emplace_back(MOVE_NONE);
Value variantResult;
+ Value result = rootPos.is_game_end(variantResult) ? variantResult
+ : rootPos.checkers() ? rootPos.checkmate_value()
+ : rootPos.stalemate_value();
+ if (Options["Protocol"] == "xboard")
+ sync_cout << ( result == VALUE_DRAW ? "1/2-1/2 {Draw}"
+ : (rootPos.side_to_move() == BLACK ? -result : result) == VALUE_MATE ? "1-0 {White wins}"
+ : "0-1 {Black wins}")
+ << sync_endl;
+ else
sync_cout << "info depth 0 score "
- << UCI::value( rootPos.is_game_end(variantResult) ? variantResult
- : rootPos.checkers() ? rootPos.checkmate_value() : rootPos.stalemate_value())
+ << UCI::value(result)
<< sync_endl;
}
else
if (Limits.npmsec)
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
- Thread* bestThread = this;
+ bestThread = this;
// Check if there are threads with a better score than main thread
if ( Options["MultiPV"] == 1
if (bestThread != this)
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
+ if (Options["Protocol"] == "xboard")
+ {
+ // Send move only when not in analyze mode and not at game end
+ if (!Options["UCI_AnalyseMode"] && rootMoves[0].pv[0] != MOVE_NONE)
+ sync_cout << "move " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]) << sync_endl;
+ return;
+ }
+
sync_cout << "bestmove " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]);
if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
ss->moveCount = ++moveCount;
- if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
+ if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000 && Options["Protocol"] != "xboard")
sync_cout << "info depth " << depth
<< " currmove " << UCI::move(pos, move)
<< " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
if (ss.rdbuf()->in_avail()) // Not at first line
ss << "\n";
+ if (Options["Protocol"] == "xboard")
+ {
+ ss << d << " "
+ << UCI::value(v) << " "
+ << elapsed / 10 << " "
+ << nodesSearched << " "
+ << rootMoves[i].selDepth << " "
+ << nodesSearched * 1000 / elapsed << " "
+ << tbHits << "\t";
+
+ for (Move m : rootMoves[i].pv)
+ ss << " " << UCI::move(pos, m);
+ }
+ else
+ {
ss << "info"
<< " depth " << d
<< " seldepth " << rootMoves[i].selDepth
for (Move m : rootMoves[i].pv)
ss << " " << UCI::move(pos, m);
+ }
}
return ss.str();
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
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
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>();
+
do {
if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF
cmd = "quit";
else if (token == "ponderhit")
Threads.main()->ponder = false; // Switch to normal search
- else if (token == "uci" || token == "usi")
+ else if (token == "uci" || token == "usi" || token == "xboard")
{
Options["Protocol"] = token;
- sync_cout << "id name " << engine_info(true)
- << "\n" << Options
- << "\n" << token << "ok" << sync_endl;
+ if (token != "xboard")
+ sync_cout << "id name " << engine_info(true)
+ << "\n" << Options
+ << "\n" << token << "ok" << sync_endl;
+ }
+
+ 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 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);
+ }
+ }
+ 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;
+ }
+ }
}
else if (token == "setoption") setoption(is);
stringstream ss;
+ if (Options["Protocol"] == "xboard")
+ {
+ if (abs(v) < VALUE_MATE - MAX_PLY)
+ ss << v * 100 / PawnValueEg;
+ else
+ ss << (v > 0 ? XBOARD_VALUE_MATE + VALUE_MATE - v + 1 : -XBOARD_VALUE_MATE - VALUE_MATE - v - 1) / 2;
+ } else
+
if (abs(v) < VALUE_MATE - MAX_PLY)
ss << "cp " << v * 100 / PawnValueEg;
else if (Options["Protocol"] == "usi")
: 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)
+ 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)) }
: std::string{ char('a' + file_of(s)), char('0' + ((rank_of(s) + 1) / 10)),
namespace UCI {
+// standard variants of XBoard/WinBoard
+std::set<string> standard_variants = {
+ "normal", "fischerandom", "3check", "makruk", "shatranj",
+ "asean", "seirawan", "crazyhouse", "suicide", "giveaway", "losers",
+ "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi"
+};
+
/// 'On change' actions, triggered by an option's value change
void on_clear_hash(const Option&) { Search::clear(); }
void on_hash_size(const Option& o) { TT.resize(o); }
void on_variant_change(const Option &o) {
const Variant* v = variants.find(o)->second;
PSQT::init(v);
- sync_cout << "info string variant " << (std::string)o
- << " files " << v->maxFile + 1
- << " ranks " << v->maxRank + 1
- << " pocket " << (v->pieceDrops ? (v->pocketSize ? v->pocketSize : v->pieceTypes.size()) : 0)
- << " template " << v->variantTemplate
- << " startpos " << v->startFen
- << sync_endl;
+ // Do not send setup command for known variants
+ if (standard_variants.find(o) != standard_variants.end())
+ return;
+ int pocketsize = v->pieceDrops ? (v->pocketSize ? v->pocketSize : v->pieceTypes.size()) : 0;
+ if (Options["Protocol"] == "xboard")
+ sync_cout << "setup (-) "
+ << v->maxFile + 1 << "x" << v->maxRank + 1
+ << "+" << pocketsize << "_" << v->variantTemplate
+ << " " << v->startFen
+ << sync_endl;
+ else
+ sync_cout << "info string variant " << (std::string)o
+ << " files " << v->maxFile + 1
+ << " ranks " << v->maxRank + 1
+ << " pocket " << pocketsize
+ << " template " << v->variantTemplate
+ << " startpos " << v->startFen
+ << sync_endl;
}
// at most 2^32 clusters.
constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
- o["Protocol"] << Option("uci", {"uci", "usi"});
+ o["Protocol"] << Option("uci", {"uci", "usi", "xboard"});
o["Debug Log File"] << Option("", on_logger);
o["Contempt"] << Option(24, -100, 100);
o["Analysis Contempt"] << Option("Both", {"Both", "Off", "White", "Black"});
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
+ if (Options["Protocol"] == "xboard")
+ {
+ for (size_t idx = 0; idx < om.size(); ++idx)
+ for (const auto& it : om)
+ if (it.second.idx == idx && it.first != "Protocol")
+ {
+ const Option& o = it.second;
+ os << "\nfeature option=\"" << it.first << " -" << o.type;
+
+ if (o.type == "string" || o.type == "check" || o.type == "combo")
+ os << " " << o.defaultValue;
+
+ if (o.type == "combo")
+ for (string value : o.comboValues)
+ if (value != o.defaultValue)
+ os << " /// " << value;
+
+ if (o.type == "spin")
+ os << " " << int(stof(o.defaultValue))
+ << " " << o.min
+ << " " << o.max;
+
+ os << "\"";
+
+ break;
+ }
+ }
+ else
+
for (size_t idx = 0; idx < om.size(); ++idx)
for (const auto& it : om)
if (it.second.idx == idx)
&& !CaseInsensitiveLess()(s, currentValue);
}
+bool Option::operator!=(const char* s) const {
+ assert(type == "combo");
+ return !(*this == s);
+}
+
/// operator<<() inits options and assigns idx in the correct printing order