Skip to content

Commit

Permalink
Support XBoard/CECP protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
ianfab committed Mar 27, 2020
1 parent c4bfae8 commit 0cd871b
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 pawns.o position.o psqt.o \
search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o
search.o thread.o timeman.o tt.o uci.o ucioption.o xboard.o syzygy/tbprobe.o

### Establish the operating system name
KERNEL = $(shell uname -s)
Expand Down
45 changes: 40 additions & 5 deletions src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,26 +259,61 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th

ss >> std::noskipws;

// Detect FEN format
bool xboard = ss.str().find('*') != std::string::npos;

// 1. Piece placement
while ((ss >> token) && !isspace(token))
// Black gating pieces in XBoard format
if (xboard)
{
for (Square s = SQ_A8; (ss >> token) && token != '/' && s <= SQ_H8; s++)
{
if ((idx = PieceToChar.find(token)) != string::npos)
{
set_gating_type(type_of(Piece(idx)));
put_gating_piece(BLACK, s);
}
}
}

while ((ss >> token) && !isspace(token) && token != '[')
{
if (isdigit(token))
sq += (token - '0') * EAST; // Advance the given number of files

else if (token == '/')
{
sq += 2 * SOUTH;
if (sq < SQ_A1)
break;
}

else if ((idx = PieceToChar.find(token)) != string::npos)
{
put_piece(Piece(idx), sq);
++sq;
}
}

else if (token == '[')
break; // Stop before gating pieces
// White gating pieces in XBoard format
if (xboard)
{
Square s1 = SQ_A1, s2 = SQ_A1;
for (Square s = SQ_A1; (ss >> token) && !isspace(token) && s <= SQ_H1; s++)
{
if ((idx = PieceToChar.find(token)) != string::npos)
{
if (type_of(Piece(idx)) == gating_piece(GATE_1))
s1 = s;
else
s2 = s;
}
}
put_gating_piece(WHITE, s1);
put_gating_piece(WHITE, s2);
}
// Gating pieces
if (!isspace(token))
else if (token == '[')
while ((ss >> token) && !isspace(token))
{
// Allow various formats here. Rank and slash are optional.
Expand Down Expand Up @@ -331,7 +366,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
else if (token == 'Q')
for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}

else if (token >= 'A' && token <= 'H')
else if (isChess960 && token >= 'A' && token <= 'H')
rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));

else
Expand Down
43 changes: 36 additions & 7 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ namespace {
pos.undo_move(m);
}
if (Root)
sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
sync_cout << UCI::move(m, pos) << ": " << cnt << sync_endl;
}
return nodes;
}
Expand Down Expand Up @@ -199,6 +199,12 @@ void MainThread::search() {
if (rootMoves.empty())
{
rootMoves.emplace_back(MOVE_NONE);
if (Options["Protocol"] == "xboard")
sync_cout << ( !rootPos.checkers() ? "1/2-1/2 {Draw}"
: rootPos.side_to_move() == BLACK ? "1-0 {White mates}"
: "0-1 {Black mates}")
<< sync_endl;
else
sync_cout << "info depth 0 score "
<< UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)
<< sync_endl;
Expand Down Expand Up @@ -237,7 +243,7 @@ void MainThread::search() {
Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();

// Check if there are threads with a better score than main thread
Thread* bestThread = this;
bestThread = this;
if ( Options["MultiPV"] == 1
&& !Limits.depth
&& !Skill(Options["Skill Level"]).enabled()
Expand All @@ -261,10 +267,17 @@ void MainThread::search() {
if (bestThread != this)
sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;

sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
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(bestThread->rootMoves[0].pv[0], rootPos) << sync_endl;
return;
}
sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos);

if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos);

std::cout << sync_endl;
}
Expand Down Expand Up @@ -866,9 +879,9 @@ namespace {

ss->moveCount = ++moveCount;

if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000 && Options["Protocol"] == "uci")
sync_cout << "info depth " << depth / ONE_PLY
<< " currmove " << UCI::move(move, pos.is_chess960())
<< " currmove " << UCI::move(move, pos)
<< " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
if (PvNode)
(ss+1)->pv = nullptr;
Expand Down Expand Up @@ -1585,6 +1598,21 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
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(m, pos);
}
else
{
ss << "info"
<< " depth " << d / ONE_PLY
<< " seldepth " << rootMoves[i].selDepth
Expand All @@ -1605,7 +1633,8 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
<< " pv";

for (Move m : rootMoves[i].pv)
ss << " " << UCI::move(m, pos.is_chess960());
ss << " " << UCI::move(m, pos);
}
}

return ss.str();
Expand Down
3 changes: 2 additions & 1 deletion src/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Thread {
CapturePieceToHistory captureHistory;
ContinuationHistory contHistory;
Score contempt;
Thread* bestThread; // to fetch best move when in XBoard mode
};


Expand Down Expand Up @@ -107,9 +108,9 @@ struct ThreadPool : public std::vector<Thread*> {

std::atomic_bool stop, ponder, stopOnPonderhit;

private:
StateListPtr setupStates;

private:
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {

uint64_t sum = 0;
Expand Down
1 change: 1 addition & 0 deletions src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ enum Value : int {
VALUE_DRAW = 0,
VALUE_KNOWN_WIN = 10000,
VALUE_MATE = 32000,
XBOARD_VALUE_MATE = 200000,
VALUE_INFINITE = 32001,
VALUE_NONE = 32002,

Expand Down
35 changes: 32 additions & 3 deletions src/uci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "timeman.h"
#include "tt.h"
#include "uci.h"
#include "xboard.h"
#include "syzygy/tbprobe.h"

using namespace std;
Expand Down Expand Up @@ -198,6 +199,9 @@ void UCI::loop(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " ";

// XBoard state machine
XBoard::StateMachine xboardStateMachine;

do {
if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF
cmd = "quit";
Expand All @@ -220,6 +224,18 @@ void UCI::loop(int argc, char* argv[]) {
else if (token == "ponderhit")
Threads.ponder = false; // Switch to normal search

else if (token == "uci" || token == "xboard")
{
Options["Protocol"] = token;
if (token == "uci")
sync_cout << "id name " << engine_info(true)
<< "\n" << Options
<< "\n" << token << "ok" << sync_endl;
}

else if (Options["Protocol"] == "xboard")
xboardStateMachine.process_command(pos, token, is, states);

else if (token == "uci")
sync_cout << "id name " << engine_info(true)
<< "\n" << Options
Expand Down Expand Up @@ -256,6 +272,14 @@ string UCI::value(Value v) {

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
Expand All @@ -277,7 +301,7 @@ std::string UCI::square(Square s) {
/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
/// castling moves are always encoded as 'king captures rook'.

string UCI::move(Move m, bool chess960) {
string UCI::move(Move m, const Position& pos) {

Square from = from_sq(m);
Square to = to_sq(m);
Expand All @@ -288,7 +312,7 @@ string UCI::move(Move m, bool chess960) {
if (m == MOVE_NULL)
return "0000";

if (type_of(m) == CASTLING && !chess960)
if (type_of(m) == CASTLING && !pos.is_chess960())
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));

if (type_of(m) == SET_GATING_TYPE)
Expand All @@ -302,6 +326,8 @@ string UCI::move(Move m, bool chess960) {

if (type_of(m) == PROMOTION)
move += PieceToChar[make_piece(BLACK, promotion_type(m))];
else if (Options["Protocol"] == "xboard" && (pos.gates() & from))
move += PieceToChar[make_piece(BLACK, pos.gating_piece(from))];

return move;
}
Expand All @@ -315,8 +341,11 @@ Move UCI::to_move(const Position& pos, string& str) {
if (str.length() == 5) // Junior could send promotion piece in uppercase
str[4] = char(tolower(str[4]));

// allow optional gating suffix
string strNoSuffix = str.substr(0, 4);

for (const auto& m : MoveList<LEGAL>(pos))
if (str == UCI::move(m, pos.is_chess960()))
if (str == UCI::move(m, pos) || strNoSuffix == UCI::move(m, pos))
return m;

return MOVE_NONE;
Expand Down
7 changes: 5 additions & 2 deletions src/uci.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <map>
#include <string>
#include <vector>

#include "types.h"

Expand All @@ -49,20 +50,22 @@ class Option {
Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr);
Option(const char* v, const std::vector<std::string>& combo, OnChange = nullptr);
Option(double v, int minv, int maxv, OnChange = nullptr);
Option(const char* v, const char *cur, OnChange = nullptr);

Option& operator=(const std::string&);
void operator<<(const Option&);
operator double() const;
operator std::string() const;
bool operator==(const char*);
const std::string get_type() const;

private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);

std::string defaultValue, currentValue, type;
int min, max;
std::vector<std::string> comboValues;
size_t idx;
OnChange on_change;
};
Expand All @@ -71,7 +74,7 @@ void init(OptionsMap&);
void loop(int argc, char* argv[]);
std::string value(Value v);
std::string square(Square s);
std::string move(Move m, bool chess960);
std::string move(Move m, const Position& pos);
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
Move to_move(const Position& pos, std::string& str);

Expand Down
Loading

0 comments on commit 0cd871b

Please sign in to comment.