Line data Source code
1 : #include "xboard.hpp"
2 :
3 : #include "com.hpp"
4 : #include "distributed.h"
5 : #include "dynamicConfig.hpp"
6 : #include "logging.hpp"
7 : #include "option.hpp"
8 : #include "position.hpp"
9 : #include "searcher.hpp"
10 : #include "timeMan.hpp"
11 : #include "tools.hpp"
12 :
13 : namespace XBoard {
14 :
15 : bool display;
16 :
17 : enum Ponder : uint8_t { p_off = 0, p_on = 1 };
18 : Ponder ponder; // easy / hard
19 :
20 : enum Mode : uint8_t { m_play_white = 0, m_play_black = 1, m_force = 2, m_analyze = 3 };
21 : Mode mode;
22 :
23 : enum SideToMove : uint8_t { stm_white = 0, stm_black = 1 };
24 : SideToMove stm; ///@todo isn't this redundant with position.c ??
25 :
26 2 : SideToMove opponent(SideToMove& s) { return s == stm_white ? stm_black : stm_white; }
27 :
28 4 : bool sideToMoveFromFEN(const std::string& fen) {
29 4 : const bool b = readFEN(fen, COM::position, false, true);
30 4 : if (!b) Logging::LogIt(Logging::logFatal) << "Illegal FEN " << fen;
31 4 : stm = COM::position.c == Co_White ? stm_white : stm_black;
32 4 : return b;
33 : }
34 :
35 0 : void newGame() {
36 2 : mode = m_force;
37 1 : stm = stm_white;
38 0 : }
39 :
40 1 : void init() {
41 1 : Logging::ct = Logging::CT_xboard;
42 1 : Logging::LogIt(Logging::logInfo) << "Init xboard";
43 1 : display = false;
44 1 : ponder = p_off;
45 1 : COM::init(COM::p_xboard);
46 : newGame();
47 1 : }
48 :
49 1 : void setFeature() {
50 : ///@todo more feature disable !!
51 : ///@todo use otim ?
52 : std::string version = MinicVersion;
53 : if (Distributed::moreThanOneProcess()) version += "_" + std::to_string(Distributed::worldSize) + "proc";
54 1 : Logging::LogIt(Logging::logGUI) << "feature ping=1 setboard=1 edit=0 Colors=0 usermove=1 memory=0 sigint=0 sigterm=0 otim=0 time=1 nps=0 draw=0 "
55 : "playother=0 variants=\"normal,fischerandom\" myname=\"Minic "
56 : << version << "\"";
57 1 : Options::displayOptionsXBoard();
58 1 : Logging::LogIt(Logging::logGUI) << "feature done=1";
59 1 : }
60 :
61 : // here we apply a move from the opponent
62 3 : bool receiveOppMove(const std::string& command) {
63 : std::string mstr(command);
64 3 : COM::stop();
65 6 : if (const size_t p = command.find("usermove"); p != std::string::npos) mstr = mstr.substr(p + 8);
66 3 : const Move m = COM::moveFromCOM(mstr);
67 3 : if (m == INVALIDMOVE) return false;
68 6 : Logging::LogIt(Logging::logInfo) << "XBOARD applying move " << ToString(m);
69 6 : if (!COM::makeMove(m, false, "")) { // make move
70 0 : Logging::LogIt(Logging::logInfo) << "Bad opponent move ! " << mstr << ToString(COM::position);
71 0 : mode = m_force;
72 0 : return false;
73 : }
74 : else {
75 : // backup game history
76 6 : COM::GetGameInfo().append({COM::position, m});
77 : // switch stm
78 3 : stm = opponent(stm);
79 : }
80 3 : return true;
81 3 : }
82 :
83 : // here we apply an own move
84 2 : void moveApplied(const bool success, const Move & m) {
85 2 : if (success) {
86 : // backup game history
87 0 : COM::GetGameInfo().append({COM::position, m});
88 : // switch stm
89 0 : stm = opponent(stm);
90 : }
91 : else {
92 2 : mode = m_force;
93 : }
94 2 : }
95 :
96 2 : bool replay(size_t nbmoves) {
97 : // backup moves
98 2 : std::vector<Move> vm = COM::GetGameInfo().getMoves();
99 2 : assert(nbmoves < vm.size());
100 2 : COM::State previousState = COM::state;
101 2 : COM::stop();
102 2 : mode = m_force;
103 : // verify initial position
104 4 : if (!sideToMoveFromFEN(GetFEN(COM::GetGameInfo().initialPos))) return false;
105 : // reset game information
106 2 : COM::GetGameInfo().clear(COM::position);
107 : // replay
108 4 : for (size_t k = 0; k < nbmoves; ++k) {
109 4 : if (!COM::makeMove(vm[k], false, "")) { // make move
110 0 : Logging::LogIt(Logging::logInfo) << "Bad move ! " << ToString(vm[k]);
111 0 : Logging::LogIt(Logging::logInfo) << ToString(COM::position);
112 0 : mode = m_force;
113 0 : return false;
114 : }
115 : else {
116 2 : stm = opponent(stm);
117 4 : COM::GetGameInfo().append({COM::position, vm[k]});
118 : }
119 : }
120 2 : if (previousState == COM::st_analyzing) { mode = m_analyze; }
121 : return true;
122 2 : }
123 :
124 1 : void xboard() {
125 1 : Logging::LogIt(Logging::logInfo) << "Starting XBoard main loop";
126 :
127 : bool iterate = true;
128 25 : while (iterate) {
129 48 : Logging::LogIt(Logging::logInfo) << "XBoard: mode (before command)" << static_cast<int>(mode);
130 48 : Logging::LogIt(Logging::logInfo) << "XBoard: stm (before command)" << static_cast<int>(stm);
131 48 : Logging::LogIt(Logging::logInfo) << "XBoard: state (before command)" << static_cast<int>(COM::state);
132 : bool commandOK = true;
133 : int once = 0;
134 50 : while (once++ == 0 || !commandOK) { // loop until a good command is found
135 : commandOK = true;
136 26 : COM::readLine(); // read next command !
137 26 : if(COM::command.empty()){
138 0 : Logging::LogIt(Logging::logFatal) << "Empty command";
139 : }
140 : std::lock_guard lock(COM::mutexGUI); // cannot treat GUI bestmove while receiving new position
141 26 : if (COM::command == "force") mode = m_force;
142 26 : else if (COM::command == "xboard")
143 1 : Logging::LogIt(Logging::logInfo) << "This is minic!";
144 25 : else if (COM::command == "post")
145 1 : display = true;
146 24 : else if (COM::command == "nopost")
147 1 : display = false;
148 23 : else if (COM::command == "computer") {
149 : }
150 23 : else if (strncmp(COM::command.c_str(), "protover", 8) == 0) {
151 1 : setFeature();
152 : }
153 22 : else if (strncmp(COM::command.c_str(), "accepted", 8) == 0) {
154 : }
155 22 : else if (strncmp(COM::command.c_str(), "rejected", 8) == 0) {
156 : }
157 22 : else if (strncmp(COM::command.c_str(), "ping", 4) == 0) {
158 : std::string str(COM::command);
159 : const size_t p = COM::command.find("ping");
160 1 : str = str.substr(p + 4);
161 2 : str = trim(str);
162 1 : Logging::LogIt(Logging::logGUI) << "pong " << str;
163 : }
164 21 : else if (COM::command == "new") { // not following protocol, should set infinite depth search
165 1 : COM::stop();
166 1 : COM::init(COM::p_xboard);
167 : newGame();
168 1 : if (!sideToMoveFromFEN(std::string(startPosition))) { commandOK = false; }
169 1 : COM::GetGameInfo().clear(COM::position);
170 1 : DynamicConfig::FRC = false;
171 1 : mode = static_cast<Mode>(static_cast<int>(stm)); ///@todo this is so wrong !
172 1 : if (mode != m_analyze) {
173 1 : mode = m_play_black;
174 1 : stm = stm_white;
175 : }
176 : }
177 20 : else if (strncmp(COM::command.c_str(), "variant", 7) == 0) {
178 : const size_t v = COM::command.find("variant");
179 2 : const std::string var = trim(COM::command.substr(v + 7));
180 1 : if (var == "fischerandom") DynamicConfig::FRC = true;
181 : ///@todo other variants
182 : else {
183 0 : Logging::LogIt(Logging::logWarn) << "Unsupported variant : " << var;
184 : }
185 : }
186 19 : else if (COM::command == "white") { // deprecated
187 0 : COM::stop();
188 0 : mode = m_play_black;
189 0 : stm = stm_white;
190 : }
191 19 : else if (COM::command == "black") { // deprecated
192 0 : COM::stop();
193 0 : mode = m_play_white;
194 0 : stm = stm_black;
195 : }
196 19 : else if (COM::command == "go") {
197 1 : COM::stop();
198 1 : mode = static_cast<Mode>(static_cast<int>(stm));
199 : }
200 18 : else if (COM::command == "playother") {
201 0 : COM::stop();
202 0 : mode = static_cast<Mode>(static_cast<int>(opponent(stm)));
203 : }
204 18 : else if (strncmp(COM::command.c_str(), "usermove", 8) == 0) {
205 3 : if (!receiveOppMove(COM::command)) commandOK = false;
206 : }
207 15 : else if (strncmp(COM::command.c_str(), "setboard", 8) == 0) {
208 1 : COM::stop();
209 : std::string fen(COM::command);
210 : const size_t p = COM::command.find("setboard");
211 1 : fen = fen.substr(p + 8);
212 1 : if (!sideToMoveFromFEN(fen)) { commandOK = false; }
213 1 : COM::GetGameInfo().clear(COM::position);
214 : }
215 14 : else if (strncmp(COM::command.c_str(), "result", 6) == 0) {
216 0 : COM::stop();
217 0 : mode = m_force;
218 : }
219 14 : else if (COM::command == "analyze") {
220 1 : COM::stop();
221 1 : mode = m_analyze;
222 : }
223 13 : else if (COM::command == "exit") {
224 1 : COM::stop();
225 1 : mode = m_force;
226 : }
227 12 : else if (COM::command == "easy") {
228 1 : COM::stopPonder();
229 1 : ponder = p_off;
230 : }
231 11 : else if (COM::command == "hard") {
232 1 : ponder = p_on;
233 : }
234 10 : else if (COM::command == "quit") {
235 : iterate = false;
236 : }
237 9 : else if (COM::command == "wait") { // only used for testing purpose
238 : using namespace std::chrono_literals;
239 1 : while(!ThreadPool::instance().main().stopFlag){
240 0 : std::this_thread::sleep_for(10ms);
241 : }
242 1 : std::this_thread::sleep_for(200ms);
243 : }
244 8 : else if (COM::command == "pause") {
245 0 : COM::stopPonder();
246 0 : COM::readLine();
247 0 : while (COM::command != "resume") {
248 0 : Logging::LogIt(Logging::logInfo) << "Error (paused): " << COM::command;
249 0 : COM::readLine();
250 : }
251 : }
252 8 : else if (strncmp(COM::command.c_str(), "time", 4) == 0) {
253 1 : COM::stopPonder();
254 1 : int centisec = 0;
255 1 : sscanf(COM::command.c_str(), "time %d", ¢isec);
256 : // just updating remaining time in curren TC (shall be classic TC or sudden death)
257 1 : TimeMan::isDynamic = true;
258 1 : TimeMan::msecUntilNextTC = centisec * 10;
259 : commandOK = false; // waiting for usermove to be sent !!!! ///@todo this is not pretty !
260 : }
261 7 : else if (strncmp(COM::command.c_str(), "st", 2) == 0) { // not following protocol, will update search time
262 0 : int msecPerMove = 0;
263 0 : sscanf(COM::command.c_str(), "st %d", &msecPerMove);
264 : // forced move time
265 0 : TimeMan::isDynamic = false;
266 0 : TimeMan::nbMoveInTC = -1;
267 0 : TimeMan::msecPerMove = msecPerMove * 1000;
268 0 : TimeMan::msecInTC = -1;
269 0 : TimeMan::msecInc = -1;
270 0 : TimeMan::msecUntilNextTC = -1;
271 0 : TimeMan::moveToGo = -1;
272 0 : COM::depth = MAX_DEPTH; // infinity
273 : }
274 7 : else if (strncmp(COM::command.c_str(), "sd", 2) == 0) { // not following protocol, will update search depth
275 1 : int d = 0;
276 1 : sscanf(COM::command.c_str(), "sd %d", &d);
277 1 : COM::depth = clampDepth(d);
278 : // forced move depth
279 1 : TimeMan::isDynamic = false;
280 1 : TimeMan::nbMoveInTC = -1;
281 1 : TimeMan::msecPerMove = INFINITETIME;
282 1 : TimeMan::msecInTC = -1;
283 1 : TimeMan::msecInc = -1;
284 1 : TimeMan::msecUntilNextTC = -1;
285 1 : TimeMan::moveToGo = -1;
286 : }
287 6 : else if (strncmp(COM::command.c_str(), "level", 5) == 0) {
288 1 : int timeTC = 0;
289 1 : int secTC = 0;
290 1 : int inc = 0;
291 1 : int mps = 0;
292 : // WARNING here, level command only supports integer ! timeTC is in minutes, and secTC and inc in secondes
293 1 : if (sscanf(COM::command.c_str(), "level %d %d %d", &mps, &timeTC, &inc) != 3)
294 0 : sscanf(COM::command.c_str(), "level %d %d:%d %d", &mps, &timeTC, &secTC, &inc);
295 : // classic TC is mps>0, else sudden death
296 1 : TimeMan::isDynamic = false;
297 1 : TimeMan::nbMoveInTC = mps;
298 1 : TimeMan::msecPerMove = -1;
299 1 : TimeMan::msecInTC = timeTC * 60000 + secTC * 1000;
300 1 : TimeMan::msecInc = inc * 1000;
301 1 : TimeMan::msecUntilNextTC = timeTC; // just an init here, will be managed using "time" command later
302 1 : TimeMan::moveToGo = -1;
303 1 : COM::depth = MAX_DEPTH; // infinity
304 : }
305 5 : else if (COM::command == "edit") {
306 : }
307 5 : else if (COM::command == "?") {
308 1 : if (COM::state == COM::st_searching) COM::stop();
309 : }
310 4 : else if (COM::command == "draw") {
311 : }
312 4 : else if (COM::command == "undo") {
313 1 : replay(COM::GetGameInfo().size() - 1);
314 : }
315 3 : else if (COM::command == "remove") {
316 1 : replay(COM::GetGameInfo().size() - 2);
317 : }
318 2 : else if (COM::command == "hint") {
319 : }
320 2 : else if (COM::command == "bk") {
321 : }
322 2 : else if (COM::command == "random") {
323 : }
324 2 : else if (strncmp(COM::command.c_str(), "otim", 4) == 0) {
325 : commandOK = false;
326 : }
327 1 : else if (COM::command == ".") {
328 : }
329 1 : else if (strncmp(COM::command.c_str(), "option", 6) == 0) {
330 1 : std::stringstream str(COM::command);
331 : std::string tmpstr;
332 1 : str >> tmpstr; // option
333 1 : str >> tmpstr; // key[=value] in fact
334 1 : std::vector<std::string> kv;
335 1 : tokenize(tmpstr, kv, "=");
336 1 : kv.resize(2); // hacky ...
337 2 : if (!Options::SetValue(kv[0], kv[1])) Logging::LogIt(Logging::logError) << "Unable to set value " << kv[0] << " = " << kv[1];
338 2 : }
339 : //************ end of Xboard command ********//
340 : // let's try to read the unknown command as a move ... trying to fix a scid versus PC issue ...
341 : ///@todo try to be safer here
342 0 : else if (!receiveOppMove(COM::command))
343 0 : Logging::LogIt(Logging::logInfo) << "Xboard does not know this command \"" << COM::command << "\"";
344 : } // readline
345 :
346 48 : Logging::LogIt(Logging::logInfo) << "XBoard: mode (after command)" << static_cast<int>(mode);
347 48 : Logging::LogIt(Logging::logInfo) << "XBoard: stm (after command)" << static_cast<int>(stm);
348 48 : Logging::LogIt(Logging::logInfo) << "XBoard: state (after command)" << static_cast<int>(COM::state);
349 :
350 : // move as computer if mode is equal to stm
351 24 : if (static_cast<int>(mode) == static_cast<int>(stm) && COM::state == COM::st_none) {
352 1 : Logging::LogIt(Logging::logInfo) << "xboard search launched";
353 1 : COM::thinkAsync(COM::st_searching);
354 1 : Logging::LogIt(Logging::logInfo) << "xboard async started";
355 : }
356 :
357 : /*
358 : ///@todo Ponder won't work here because after this, as searchDriver() is async, we are probably stuck in readLine() ...
359 : // if not our turn, and ponder is on, let's think ...
360 : if (static_cast<int>(COM::mode) == static_cast<int>(COM::opponent(COM::stm)) && COM::ponder == COM::p_on && COM::state == COM::st_none) {
361 : Logging::LogIt(Logging::logInfo) << "xboard search launched (pondering)";
362 : COM::thinkAsync(COM::st_pondering);
363 : Logging::LogIt(Logging::logInfo) << "xboard async started (pondering)";
364 : }
365 : */
366 :
367 24 : if (mode == m_analyze && COM::state == COM::st_none) {
368 1 : Logging::LogIt(Logging::logInfo) << "xboard search launched (analysis)";
369 1 : COM::thinkAsync(COM::st_analyzing);
370 1 : Logging::LogIt(Logging::logInfo) << "xboard async started (analysis)";
371 : }
372 :
373 48 : Logging::LogIt(Logging::logInfo) << "XBoard: mode (after search launch)" << static_cast<int>(mode);
374 48 : Logging::LogIt(Logging::logInfo) << "XBoard: stm (after search launch)" << static_cast<int>(stm);
375 48 : Logging::LogIt(Logging::logInfo) << "XBoard: state (after search launch)" << static_cast<int>(COM::state);
376 :
377 : } // while true
378 1 : Logging::LogIt(Logging::logInfo) << "Leaving Xboard loop";
379 :
380 : // write last game
381 1 : if (DynamicConfig::pgnOut){
382 0 : std::ofstream os("games_" + std::to_string(GETPID()) + "_" + std::to_string(0) + ".pgn", std::ofstream::app);
383 0 : COM::GetGameInfo().write(os);
384 0 : os.close();
385 0 : }
386 1 : }
387 : } // namespace XBoard
|