1 /*
2     This file is part of Leela Zero.
3     Copyright (C) 2017-2019 Gian-Carlo Pascutto and contributors
4 
5     Leela Zero is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     Leela Zero is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with Leela Zero.  If not, see <http://www.gnu.org/licenses/>.
17 
18     Additional permission under GNU GPL version 3 section 7
19 
20     If you modify this Program, or any covered work, by linking or
21     combining it with NVIDIA Corporation's libraries from the
22     NVIDIA CUDA Toolkit and/or the NVIDIA CUDA Deep Neural
23     Network library and/or the NVIDIA TensorRT inference library
24     (or a modified version of those libraries), containing parts covered
25     by the terms of the respective license agreement, the licensors of
26     this Program grant you additional permission to convey the resulting
27     work.
28 */
29 
30 #include "config.h"
31 
32 #include <algorithm>
33 #include <cctype>
34 #include <chrono>
35 #include <cmath>
36 #include <cstdlib>
37 #include <exception>
38 #include <fstream>
39 #include <limits>
40 #include <memory>
41 #include <random>
42 #include <string>
43 #include <vector>
44 #include <boost/algorithm/string.hpp>
45 
46 #include "GTP.h"
47 #include "FastBoard.h"
48 #include "FullBoard.h"
49 #include "GameState.h"
50 #include "Network.h"
51 #include "SGFTree.h"
52 #include "SMP.h"
53 #include "Training.h"
54 #include "UCTSearch.h"
55 #include "Utils.h"
56 
57 using namespace Utils;
58 
59 // Configuration flags
60 bool cfg_gtp_mode;
61 bool cfg_allow_pondering;
62 unsigned int cfg_num_threads;
63 unsigned int cfg_batch_size;
64 int cfg_max_playouts;
65 int cfg_max_visits;
66 size_t cfg_max_memory;
67 size_t cfg_max_tree_size;
68 int cfg_max_cache_ratio_percent;
69 TimeManagement::enabled_t cfg_timemanage;
70 int cfg_lagbuffer_cs;
71 int cfg_resignpct;
72 int cfg_noise;
73 int cfg_random_cnt;
74 int cfg_random_min_visits;
75 float cfg_random_temp;
76 std::uint64_t cfg_rng_seed;
77 bool cfg_dumbpass;
78 #ifdef USE_OPENCL
79 std::vector<int> cfg_gpus;
80 bool cfg_sgemm_exhaustive;
81 bool cfg_tune_only;
82 #ifdef USE_HALF
83 precision_t cfg_precision;
84 #endif
85 #endif
86 float cfg_puct;
87 float cfg_logpuct;
88 float cfg_logconst;
89 float cfg_softmax_temp;
90 float cfg_fpu_reduction;
91 float cfg_fpu_root_reduction;
92 float cfg_ci_alpha;
93 float cfg_lcb_min_visit_ratio;
94 std::string cfg_weightsfile;
95 std::string cfg_logfile;
96 FILE* cfg_logfile_handle;
97 bool cfg_quiet;
98 std::string cfg_options_str;
99 bool cfg_benchmark;
100 bool cfg_cpu_only;
101 AnalyzeTags cfg_analyze_tags;
102 
103 /* Parses tags for the lz-analyze GTP command and friends */
AnalyzeTags(std::istringstream & cmdstream,const GameState & game)104 AnalyzeTags::AnalyzeTags(std::istringstream& cmdstream, const GameState& game) {
105     std::string tag;
106 
107     /* Default color is the current one */
108     m_who = game.board.get_to_move();
109 
110     auto avoid_not_pass_resign_b = false, avoid_not_pass_resign_w = false;
111     auto allow_b = false, allow_w = false;
112 
113     while (true) {
114         cmdstream >> std::ws;
115         if (isdigit(cmdstream.peek())) {
116             tag = "interval";
117         } else {
118             cmdstream >> tag;
119             if (cmdstream.fail() && cmdstream.eof()) {
120                 /* Parsing complete */
121                 m_invalid = false;
122                 return;
123             }
124         }
125 
126         if (tag == "avoid" || tag == "allow") {
127             std::string textcolor, textmoves;
128             size_t until_movenum;
129             cmdstream >> textcolor;
130             cmdstream >> textmoves;
131             cmdstream >> until_movenum;
132             if (cmdstream.fail()) {
133                 return;
134             }
135 
136             std::vector<int> moves;
137             std::istringstream movestream(textmoves);
138             while (!movestream.eof()) {
139                 std::string textmove;
140                 getline(movestream, textmove, ',');
141                 auto sepidx = textmove.find_first_of(':');
142                 if (sepidx != std::string::npos) {
143                     if (!(sepidx == 2 || sepidx == 3)) {
144                         moves.clear();
145                         break;
146                     }
147                     auto move1_compressed = game.board.text_to_move(
148                         textmove.substr(0, sepidx)
149                     );
150                     auto move2_compressed = game.board.text_to_move(
151                         textmove.substr(sepidx + 1)
152                     );
153                     if (move1_compressed == FastBoard::NO_VERTEX ||
154                         move1_compressed == FastBoard::PASS ||
155                         move1_compressed == FastBoard::RESIGN ||
156                         move2_compressed == FastBoard::NO_VERTEX ||
157                         move2_compressed == FastBoard::PASS ||
158                         move2_compressed == FastBoard::RESIGN)
159                     {
160                         moves.clear();
161                         break;
162                     }
163                     auto move1_xy = game.board.get_xy(move1_compressed);
164                     auto move2_xy = game.board.get_xy(move2_compressed);
165                     auto xmin = std::min(move1_xy.first, move2_xy.first);
166                     auto xmax = std::max(move1_xy.first, move2_xy.first);
167                     auto ymin = std::min(move1_xy.second, move2_xy.second);
168                     auto ymax = std::max(move1_xy.second, move2_xy.second);
169                     for (auto move_x = xmin; move_x <= xmax; move_x++) {
170                         for (auto move_y = ymin; move_y <= ymax; move_y++) {
171                             moves.push_back(game.board.get_vertex(move_x,move_y));
172                         }
173                     }
174                 } else {
175                     auto move = game.board.text_to_move(textmove);
176                     if (move == FastBoard::NO_VERTEX) {
177                         moves.clear();
178                         break;
179                     }
180                     moves.push_back(move);
181                 }
182             }
183             if (moves.empty()) {
184                 return;
185             }
186 
187             int color;
188             if (textcolor == "w" || textcolor == "white") {
189                 color = FastBoard::WHITE;
190             } else if (textcolor == "b" || textcolor == "black") {
191                 color = FastBoard::BLACK;
192             } else {
193                 return;
194             }
195 
196             if (until_movenum < 1) {
197                 return;
198             }
199             until_movenum += game.get_movenum() - 1;
200 
201             for (const auto& move : moves) {
202                 if (tag == "avoid") {
203                     add_move_to_avoid(color, move, until_movenum);
204                     if (move != FastBoard::PASS && move != FastBoard::RESIGN) {
205                         if (color == FastBoard::BLACK) {
206                             avoid_not_pass_resign_b = true;
207                         } else {
208                             avoid_not_pass_resign_w = true;
209                         }
210                     }
211                 } else {
212                     add_move_to_allow(color, move, until_movenum);
213                     if (color == FastBoard::BLACK) {
214                         allow_b = true;
215                     } else {
216                         allow_w = true;
217                     }
218                 }
219             }
220             if ((allow_b && avoid_not_pass_resign_b) ||
221                 (allow_w && avoid_not_pass_resign_w)) {
222                 /* If "allow" is in use, it is illegal to use "avoid" with any
223                  * move that is not "pass" or "resign". */
224                 return;
225             }
226         } else if (tag == "w" || tag == "white") {
227             m_who = FastBoard::WHITE;
228         } else if (tag == "b" || tag == "black") {
229             m_who = FastBoard::BLACK;
230         } else if (tag == "interval") {
231             cmdstream >> m_interval_centis;
232             if (cmdstream.fail()) {
233                 return;
234             }
235         } else if (tag == "minmoves") {
236             cmdstream >> m_min_moves;
237             if (cmdstream.fail()) {
238                 return;
239             }
240         } else {
241             return;
242         }
243     }
244 }
245 
add_move_to_avoid(int color,int vertex,size_t until_move)246 void AnalyzeTags::add_move_to_avoid(int color, int vertex, size_t until_move) {
247     m_moves_to_avoid.emplace_back(color, until_move, vertex);
248 }
249 
add_move_to_allow(int color,int vertex,size_t until_move)250 void AnalyzeTags::add_move_to_allow(int color, int vertex, size_t until_move) {
251     m_moves_to_allow.emplace_back(color, until_move, vertex);
252 }
253 
interval_centis() const254 int AnalyzeTags::interval_centis() const {
255     return m_interval_centis;
256 }
257 
invalid() const258 int AnalyzeTags::invalid() const {
259     return m_invalid;
260 }
261 
who() const262 int AnalyzeTags::who() const {
263     return m_who;
264 }
265 
post_move_count() const266 size_t AnalyzeTags::post_move_count() const {
267     return m_min_moves;
268 }
269 
is_to_avoid(int color,int vertex,size_t movenum) const270 bool AnalyzeTags::is_to_avoid(int color, int vertex, size_t movenum) const {
271     for (auto& move : m_moves_to_avoid) {
272         if (color == move.color && vertex == move.vertex && movenum <= move.until_move) {
273             return true;
274         }
275     }
276     if (vertex != FastBoard::PASS && vertex != FastBoard::RESIGN) {
277         auto active_allow = false;
278         for (auto& move : m_moves_to_allow) {
279             if (color == move.color && movenum <= move.until_move) {
280                 active_allow = true;
281                 if (vertex == move.vertex) {
282                     return false;
283                 }
284             }
285         }
286         if (active_allow) {
287             return true;
288         }
289     }
290     return false;
291 }
292 
has_move_restrictions() const293 bool AnalyzeTags::has_move_restrictions() const {
294     return !m_moves_to_avoid.empty() || !m_moves_to_allow.empty();
295 }
296 
297 std::unique_ptr<Network> GTP::s_network;
298 
initialize(std::unique_ptr<Network> && net)299 void GTP::initialize(std::unique_ptr<Network>&& net) {
300     s_network = std::move(net);
301 
302     bool result;
303     std::string message;
304     std::tie(result, message) =
305         set_max_memory(cfg_max_memory, cfg_max_cache_ratio_percent);
306     if (!result) {
307         // This should only ever happen with 60 block networks on 32bit machine.
308         myprintf("LOW MEMORY SETTINGS! Couldn't set default memory limits.\n");
309         myprintf("The network you are using might be too big\n");
310         myprintf("for the default settings on your system.\n");
311         throw std::runtime_error("Error setting memory requirements.");
312     }
313     myprintf("%s\n", message.c_str());
314 }
315 
setup_default_parameters()316 void GTP::setup_default_parameters() {
317     cfg_gtp_mode = false;
318     cfg_allow_pondering = true;
319 
320     // we will re-calculate this on Leela.cpp
321     cfg_num_threads = 1;
322     // we will re-calculate this on Leela.cpp
323     cfg_batch_size = 1;
324 
325     cfg_max_memory = UCTSearch::DEFAULT_MAX_MEMORY;
326     cfg_max_playouts = UCTSearch::UNLIMITED_PLAYOUTS;
327     cfg_max_visits = UCTSearch::UNLIMITED_PLAYOUTS;
328     // This will be overwriiten in initialize() after network size is known.
329     cfg_max_tree_size = UCTSearch::DEFAULT_MAX_MEMORY;
330     cfg_max_cache_ratio_percent = 10;
331     cfg_timemanage = TimeManagement::AUTO;
332     cfg_lagbuffer_cs = 100;
333     cfg_weightsfile = leelaz_file("best-network");
334 #ifdef USE_OPENCL
335     cfg_gpus = { };
336     cfg_sgemm_exhaustive = false;
337     cfg_tune_only = false;
338 
339 #ifdef USE_HALF
340     cfg_precision = precision_t::AUTO;
341 #endif
342 #endif
343     cfg_puct = 0.5f;
344     cfg_logpuct = 0.015f;
345     cfg_logconst = 1.7f;
346     cfg_softmax_temp = 1.0f;
347     cfg_fpu_reduction = 0.25f;
348     // see UCTSearch::should_resign
349     cfg_resignpct = -1;
350     cfg_noise = false;
351     cfg_fpu_root_reduction = cfg_fpu_reduction;
352     cfg_ci_alpha = 1e-5f;
353     cfg_lcb_min_visit_ratio = 0.10f;
354     cfg_random_cnt = 0;
355     cfg_random_min_visits = 1;
356     cfg_random_temp = 1.0f;
357     cfg_dumbpass = false;
358     cfg_logfile_handle = nullptr;
359     cfg_quiet = false;
360     cfg_benchmark = false;
361 #ifdef USE_CPU_ONLY
362     cfg_cpu_only = true;
363 #else
364     cfg_cpu_only = false;
365 #endif
366 
367     cfg_analyze_tags = AnalyzeTags{};
368 
369     // C++11 doesn't guarantee *anything* about how random this is,
370     // and in MinGW it isn't random at all. But we can mix it in, which
371     // helps when it *is* high quality (Linux, MSVC).
372     std::random_device rd;
373     std::ranlux48 gen(rd());
374     std::uint64_t seed1 = (gen() << 16) ^ gen();
375     // If the above fails, this is one of our best, portable, bets.
376     std::uint64_t seed2 = std::chrono::high_resolution_clock::
377         now().time_since_epoch().count();
378     cfg_rng_seed = seed1 ^ seed2;
379 }
380 
381 const std::string GTP::s_commands[] = {
382     "protocol_version",
383     "name",
384     "version",
385     "quit",
386     "known_command",
387     "list_commands",
388     "boardsize",
389     "clear_board",
390     "komi",
391     "play",
392     "genmove",
393     "showboard",
394     "undo",
395     "final_score",
396     "final_status_list",
397     "time_settings",
398     "time_left",
399     "fixed_handicap",
400     "last_move",
401     "move_history",
402     "place_free_handicap",
403     "set_free_handicap",
404     "loadsgf",
405     "printsgf",
406     "kgs-genmove_cleanup",
407     "kgs-time_settings",
408     "kgs-game_over",
409     "heatmap",
410     "lz-analyze",
411     "lz-genmove_analyze",
412     "lz-memory_report",
413     "lz-setoption",
414     "gomill-explain_last_move",
415     ""
416 };
417 
418 // Default/min/max could be moved into separate fields,
419 // but for now we assume that the GUI will not send us invalid info.
420 const std::string GTP::s_options[] = {
421     "option name Maximum Memory Use (MiB) type spin default 2048 min 128 max 131072",
422     "option name Percentage of memory for cache type spin default 10 min 1 max 99",
423     "option name Visits type spin default 0 min 0 max 1000000000",
424     "option name Playouts type spin default 0 min 0 max 1000000000",
425     "option name Lagbuffer type spin default 0 min 0 max 3000",
426     "option name Resign Percentage type spin default -1 min -1 max 30",
427     "option name Pondering type check default true",
428     ""
429 };
430 
get_life_list(const GameState & game,bool live)431 std::string GTP::get_life_list(const GameState & game, bool live) {
432     std::vector<std::string> stringlist;
433     std::string result;
434     const auto& board = game.board;
435 
436     if (live) {
437         for (int i = 0; i < board.get_boardsize(); i++) {
438             for (int j = 0; j < board.get_boardsize(); j++) {
439                 int vertex = board.get_vertex(i, j);
440 
441                 if (board.get_state(vertex) != FastBoard::EMPTY) {
442                     stringlist.push_back(board.get_string(vertex));
443                 }
444             }
445         }
446     }
447 
448     // remove multiple mentions of the same string
449     // unique reorders and returns new iterator, erase actually deletes
450     std::sort(begin(stringlist), end(stringlist));
451     stringlist.erase(std::unique(begin(stringlist), end(stringlist)),
452                      end(stringlist));
453 
454     for (size_t i = 0; i < stringlist.size(); i++) {
455         result += (i == 0 ? "" : "\n") + stringlist[i];
456     }
457 
458     return result;
459 }
460 
execute(GameState & game,const std::string & xinput)461 void GTP::execute(GameState & game, const std::string& xinput) {
462     std::string input;
463     static auto search = std::make_unique<UCTSearch>(game, *s_network);
464 
465     bool transform_lowercase = true;
466 
467     // Required on Unixy systems
468     if (xinput.find("loadsgf") != std::string::npos) {
469         transform_lowercase = false;
470     }
471 
472     /* eat empty lines, simple preprocessing, lower case */
473     for (unsigned int tmp = 0; tmp < xinput.size(); tmp++) {
474         if (xinput[tmp] == 9) {
475             input += " ";
476         } else if ((xinput[tmp] > 0 && xinput[tmp] <= 9)
477                 || (xinput[tmp] >= 11 && xinput[tmp] <= 31)
478                 || xinput[tmp] == 127) {
479                continue;
480         } else {
481             if (transform_lowercase) {
482                 input += std::tolower(xinput[tmp]);
483             } else {
484                 input += xinput[tmp];
485             }
486         }
487 
488         // eat multi whitespace
489         if (input.size() > 1) {
490             if (std::isspace(input[input.size() - 2]) &&
491                 std::isspace(input[input.size() - 1])) {
492                 input.resize(input.size() - 1);
493             }
494         }
495     }
496 
497     std::string command;
498     int id = -1;
499 
500     if (input == "") {
501         return;
502     } else if (input == "exit") {
503         exit(EXIT_SUCCESS);
504     } else if (input.find("#") == 0) {
505         return;
506     } else if (std::isdigit(input[0])) {
507         std::istringstream strm(input);
508         char spacer;
509         strm >> id;
510         strm >> std::noskipws >> spacer;
511         std::getline(strm, command);
512     } else {
513         command = input;
514     }
515 
516     /* process commands */
517     if (command == "protocol_version") {
518         gtp_printf(id, "%d", GTP_VERSION);
519         return;
520     } else if (command == "name") {
521         gtp_printf(id, PROGRAM_NAME);
522         return;
523     } else if (command == "version") {
524         gtp_printf(id, PROGRAM_VERSION);
525         return;
526     } else if (command == "quit") {
527         gtp_printf(id, "");
528         exit(EXIT_SUCCESS);
529     } else if (command.find("known_command") == 0) {
530         std::istringstream cmdstream(command);
531         std::string tmp;
532 
533         cmdstream >> tmp;     /* remove known_command */
534         cmdstream >> tmp;
535 
536         for (int i = 0; s_commands[i].size() > 0; i++) {
537             if (tmp == s_commands[i]) {
538                 gtp_printf(id, "true");
539                 return;
540             }
541         }
542 
543         gtp_printf(id, "false");
544         return;
545     } else if (command.find("list_commands") == 0) {
546         std::string outtmp(s_commands[0]);
547         for (int i = 1; s_commands[i].size() > 0; i++) {
548             outtmp = outtmp + "\n" + s_commands[i];
549         }
550         gtp_printf(id, outtmp.c_str());
551         return;
552     } else if (command.find("boardsize") == 0) {
553         std::istringstream cmdstream(command);
554         std::string stmp;
555         int tmp;
556 
557         cmdstream >> stmp;  // eat boardsize
558         cmdstream >> tmp;
559 
560         if (!cmdstream.fail()) {
561             if (tmp != BOARD_SIZE) {
562                 gtp_fail_printf(id, "unacceptable size");
563             } else {
564                 float old_komi = game.get_komi();
565                 Training::clear_training();
566                 game.init_game(tmp, old_komi);
567                 gtp_printf(id, "");
568             }
569         } else {
570             gtp_fail_printf(id, "syntax not understood");
571         }
572 
573         return;
574     } else if (command.find("clear_board") == 0) {
575         Training::clear_training();
576         game.reset_game();
577         search = std::make_unique<UCTSearch>(game, *s_network);
578         assert(UCTNodePointer::get_tree_size() == 0);
579         gtp_printf(id, "");
580         return;
581     } else if (command.find("komi") == 0) {
582         std::istringstream cmdstream(command);
583         std::string tmp;
584         float komi = KOMI;
585         float old_komi = game.get_komi();
586 
587         cmdstream >> tmp;  // eat komi
588         cmdstream >> komi;
589 
590         if (!cmdstream.fail()) {
591             if (komi != old_komi) {
592                 game.set_komi(komi);
593             }
594             gtp_printf(id, "");
595         } else {
596             gtp_fail_printf(id, "syntax not understood");
597         }
598 
599         return;
600     } else if (command.find("play") == 0) {
601         std::istringstream cmdstream(command);
602         std::string tmp;
603         std::string color, vertex;
604 
605         cmdstream >> tmp;   //eat play
606         cmdstream >> color;
607         cmdstream >> vertex;
608 
609         if (!cmdstream.fail()) {
610             if (!game.play_textmove(color, vertex)) {
611                 gtp_fail_printf(id, "illegal move");
612             } else {
613                 gtp_printf(id, "");
614             }
615         } else {
616             gtp_fail_printf(id, "syntax not understood");
617         }
618         return;
619     } else if (command.find("genmove") == 0
620                || command.find("lz-genmove_analyze") == 0) {
621         auto analysis_output = command.find("lz-genmove_analyze") == 0;
622 
623         std::istringstream cmdstream(command);
624         std::string tmp;
625         cmdstream >> tmp;  // eat genmove
626 
627         int who;
628         AnalyzeTags tags;
629 
630         if (analysis_output) {
631             tags = AnalyzeTags{cmdstream, game};
632             if (tags.invalid()) {
633                 gtp_fail_printf(id, "cannot parse analyze tags");
634                 return;
635             }
636             who = tags.who();
637         } else {
638             /* genmove command */
639             cmdstream >> tmp;
640             if (tmp == "w" || tmp == "white") {
641                 who = FastBoard::WHITE;
642             } else if (tmp == "b" || tmp == "black") {
643                 who = FastBoard::BLACK;
644             } else {
645                 gtp_fail_printf(id, "syntax error");
646                 return;
647             }
648         }
649 
650         if (analysis_output) {
651             // Start of multi-line response
652             cfg_analyze_tags = tags;
653             if (id != -1) gtp_printf_raw("=%d\n", id);
654             else gtp_printf_raw("=\n");
655         }
656         // start thinking
657         {
658             game.set_to_move(who);
659             // Outputs winrate and pvs for lz-genmove_analyze
660             int move = search->think(who);
661             game.play_move(move);
662 
663             std::string vertex = game.move_to_text(move);
664             if (!analysis_output) {
665                 gtp_printf(id, "%s", vertex.c_str());
666             } else {
667                 gtp_printf_raw("play %s\n", vertex.c_str());
668             }
669         }
670 
671         if (cfg_allow_pondering) {
672             // now start pondering
673             if (!game.has_resigned()) {
674                 // Outputs winrate and pvs through gtp for lz-genmove_analyze
675                 search->ponder();
676             }
677         }
678         if (analysis_output) {
679             // Terminate multi-line response
680             gtp_printf_raw("\n");
681         }
682         cfg_analyze_tags = {};
683         return;
684     } else if (command.find("lz-analyze") == 0) {
685         std::istringstream cmdstream(command);
686         std::string tmp;
687 
688         cmdstream >> tmp; // eat lz-analyze
689         AnalyzeTags tags{cmdstream, game};
690         if (tags.invalid()) {
691             gtp_fail_printf(id, "cannot parse analyze tags");
692             return;
693         }
694         // Start multi-line response.
695         if (id != -1) gtp_printf_raw("=%d\n", id);
696         else gtp_printf_raw("=\n");
697         // Now start pondering.
698         if (!game.has_resigned()) {
699             cfg_analyze_tags = tags;
700             // Outputs winrate and pvs through gtp
701             game.set_to_move(tags.who());
702             search->ponder();
703         }
704         cfg_analyze_tags = {};
705         // Terminate multi-line response
706         gtp_printf_raw("\n");
707         return;
708     } else if (command.find("kgs-genmove_cleanup") == 0) {
709         std::istringstream cmdstream(command);
710         std::string tmp;
711 
712         cmdstream >> tmp;  // eat kgs-genmove
713         cmdstream >> tmp;
714 
715         if (!cmdstream.fail()) {
716             int who;
717             if (tmp == "w" || tmp == "white") {
718                 who = FastBoard::WHITE;
719             } else if (tmp == "b" || tmp == "black") {
720                 who = FastBoard::BLACK;
721             } else {
722                 gtp_fail_printf(id, "syntax error");
723                 return;
724             }
725             game.set_passes(0);
726             {
727                 game.set_to_move(who);
728                 int move = search->think(who, UCTSearch::NOPASS);
729                 game.play_move(move);
730 
731                 std::string vertex = game.move_to_text(move);
732                 gtp_printf(id, "%s", vertex.c_str());
733             }
734             if (cfg_allow_pondering) {
735                 // now start pondering
736                 if (!game.has_resigned()) {
737                     search->ponder();
738                 }
739             }
740         } else {
741             gtp_fail_printf(id, "syntax not understood");
742         }
743         return;
744     } else if (command.find("undo") == 0) {
745         if (game.undo_move()) {
746             gtp_printf(id, "");
747         } else {
748             gtp_fail_printf(id, "cannot undo");
749         }
750         return;
751     } else if (command.find("showboard") == 0) {
752         gtp_printf(id, "");
753         game.display_state();
754         return;
755     } else if (command.find("final_score") == 0) {
756         float ftmp = game.final_score();
757         /* white wins */
758         if (ftmp < -0.1) {
759             gtp_printf(id, "W+%3.1f", float(fabs(ftmp)));
760         } else if (ftmp > 0.1) {
761             gtp_printf(id, "B+%3.1f", ftmp);
762         } else {
763             gtp_printf(id, "0");
764         }
765         return;
766     } else if (command.find("final_status_list") == 0) {
767         if (command.find("alive") != std::string::npos) {
768             std::string livelist = get_life_list(game, true);
769             gtp_printf(id, livelist.c_str());
770         } else if (command.find("dead") != std::string::npos) {
771             std::string deadlist = get_life_list(game, false);
772             gtp_printf(id, deadlist.c_str());
773         } else {
774             gtp_printf(id, "");
775         }
776         return;
777     } else if (command.find("time_settings") == 0) {
778         std::istringstream cmdstream(command);
779         std::string tmp;
780         int maintime, byotime, byostones;
781 
782         cmdstream >> tmp >> maintime >> byotime >> byostones;
783 
784         if (!cmdstream.fail()) {
785             // convert to centiseconds and set
786             game.set_timecontrol(maintime * 100, byotime * 100, byostones, 0);
787 
788             gtp_printf(id, "");
789         } else {
790             gtp_fail_printf(id, "syntax not understood");
791         }
792         return;
793     } else if (command.find("time_left") == 0) {
794         std::istringstream cmdstream(command);
795         std::string tmp, color;
796         int time, stones;
797 
798         cmdstream >> tmp >> color >> time >> stones;
799 
800         if (!cmdstream.fail()) {
801             int icolor;
802 
803             if (color == "w" || color == "white") {
804                 icolor = FastBoard::WHITE;
805             } else if (color == "b" || color == "black") {
806                 icolor = FastBoard::BLACK;
807             } else {
808                 gtp_fail_printf(id, "Color in time adjust not understood.\n");
809                 return;
810             }
811 
812             game.adjust_time(icolor, time * 100, stones);
813 
814             gtp_printf(id, "");
815 
816             if (cfg_allow_pondering) {
817                 // KGS sends this after our move
818                 // now start pondering
819                 if (!game.has_resigned()) {
820                     search->ponder();
821                 }
822             }
823         } else {
824             gtp_fail_printf(id, "syntax not understood");
825         }
826         return;
827     } else if (command.find("auto") == 0) {
828         do {
829             int move = search->think(game.get_to_move(), UCTSearch::NORMAL);
830             game.play_move(move);
831             game.display_state();
832 
833         } while (game.get_passes() < 2 && !game.has_resigned());
834 
835         return;
836     } else if (command.find("go") == 0 && command.size() < 6) {
837         int move = search->think(game.get_to_move());
838         game.play_move(move);
839 
840         std::string vertex = game.move_to_text(move);
841         myprintf("%s\n", vertex.c_str());
842         return;
843     } else if (command.find("heatmap") == 0) {
844         std::istringstream cmdstream(command);
845         std::string tmp;
846         std::string symmetry;
847 
848         cmdstream >> tmp;   // eat heatmap
849         cmdstream >> symmetry;
850 
851         Network::Netresult vec;
852         if (cmdstream.fail()) {
853             // Default = DIRECT with no symmetric change
854             vec = s_network->get_output(
855                 &game, Network::Ensemble::DIRECT,
856                 Network::IDENTITY_SYMMETRY, false);
857         } else if (symmetry == "all") {
858             for (auto s = 0; s < Network::NUM_SYMMETRIES; ++s) {
859                 vec = s_network->get_output(
860                     &game, Network::Ensemble::DIRECT, s, false);
861                 Network::show_heatmap(&game, vec, false);
862             }
863         } else if (symmetry == "average" || symmetry == "avg") {
864             vec = s_network->get_output(
865                 &game, Network::Ensemble::AVERAGE, -1, false);
866         } else {
867             vec = s_network->get_output(
868                 &game, Network::Ensemble::DIRECT, std::stoi(symmetry), false);
869         }
870 
871         if (symmetry != "all") {
872             Network::show_heatmap(&game, vec, false);
873         }
874 
875         gtp_printf(id, "");
876         return;
877     } else if (command.find("fixed_handicap") == 0) {
878         std::istringstream cmdstream(command);
879         std::string tmp;
880         int stones;
881 
882         cmdstream >> tmp;   // eat fixed_handicap
883         cmdstream >> stones;
884 
885         if (!cmdstream.fail() && game.set_fixed_handicap(stones)) {
886             auto stonestring = game.board.get_stone_list();
887             gtp_printf(id, "%s", stonestring.c_str());
888         } else {
889             gtp_fail_printf(id, "Not a valid number of handicap stones");
890         }
891         return;
892     } else if (command.find("last_move") == 0) {
893         auto last_move = game.get_last_move();
894         if (last_move == FastBoard::NO_VERTEX) {
895             gtp_fail_printf(id, "no previous move known");
896             return;
897         }
898         auto coordinate = game.move_to_text(last_move);
899         auto color = game.get_to_move() == FastBoard::WHITE ? "black" : "white";
900         gtp_printf(id, "%s %s", color, coordinate.c_str());
901         return;
902     } else if (command.find("move_history") == 0) {
903         if (game.get_movenum() == 0) {
904             gtp_printf_raw("= \n");
905         } else {
906             gtp_printf_raw("= ");
907         }
908         auto game_history = game.get_game_history();
909         // undone moves may still be present, so reverse the portion of the
910         // array we need and resize to trim it down for iteration.
911         std::reverse(begin(game_history),
912                      begin(game_history) + game.get_movenum() + 1);
913         game_history.resize(game.get_movenum());
914         for (const auto &state : game_history) {
915             auto coordinate = game.move_to_text(state->get_last_move());
916             auto color = state->get_to_move() == FastBoard::WHITE ? "black" : "white";
917             gtp_printf_raw("%s %s\n", color, coordinate.c_str());
918         }
919         gtp_printf_raw("\n");
920         return;
921     } else if (command.find("place_free_handicap") == 0) {
922         std::istringstream cmdstream(command);
923         std::string tmp;
924         int stones;
925 
926         cmdstream >> tmp;   // eat place_free_handicap
927         cmdstream >> stones;
928 
929         if (!cmdstream.fail()) {
930             game.place_free_handicap(stones, *s_network);
931             auto stonestring = game.board.get_stone_list();
932             gtp_printf(id, "%s", stonestring.c_str());
933         } else {
934             gtp_fail_printf(id, "Not a valid number of handicap stones");
935         }
936 
937         return;
938     } else if (command.find("set_free_handicap") == 0) {
939         std::istringstream cmdstream(command);
940         std::string tmp;
941 
942         cmdstream >> tmp;   // eat set_free_handicap
943 
944         do {
945             std::string vertex;
946 
947             cmdstream >> vertex;
948 
949             if (!cmdstream.fail()) {
950                 if (!game.play_textmove("black", vertex)) {
951                     gtp_fail_printf(id, "illegal move");
952                 } else {
953                     game.set_handicap(game.get_handicap() + 1);
954                 }
955             }
956         } while (!cmdstream.fail());
957 
958         std::string stonestring = game.board.get_stone_list();
959         gtp_printf(id, "%s", stonestring.c_str());
960 
961         return;
962     } else if (command.find("loadsgf") == 0) {
963         std::istringstream cmdstream(command);
964         std::string tmp, filename;
965         int movenum;
966 
967         cmdstream >> tmp;   // eat loadsgf
968         cmdstream >> filename;
969 
970         if (!cmdstream.fail()) {
971             cmdstream >> movenum;
972 
973             if (cmdstream.fail()) {
974                 movenum = 999;
975             }
976         } else {
977             gtp_fail_printf(id, "Missing filename.");
978             return;
979         }
980 
981         auto sgftree = std::make_unique<SGFTree>();
982 
983         try {
984             sgftree->load_from_file(filename);
985             game = sgftree->follow_mainline_state(movenum - 1);
986             gtp_printf(id, "");
987         } catch (const std::exception&) {
988             gtp_fail_printf(id, "cannot load file");
989         }
990         return;
991     } else if (command.find("kgs-chat") == 0) {
992         // kgs-chat (game|private) Name Message
993         std::istringstream cmdstream(command);
994         std::string tmp;
995 
996         cmdstream >> tmp; // eat kgs-chat
997         cmdstream >> tmp; // eat game|private
998         cmdstream >> tmp; // eat player name
999         do {
1000             cmdstream >> tmp; // eat message
1001         } while (!cmdstream.fail());
1002 
1003         gtp_fail_printf(id, "I'm a go bot, not a chat bot.");
1004         return;
1005     } else if (command.find("kgs-game_over") == 0) {
1006         // Do nothing. Particularly, don't ponder.
1007         gtp_printf(id, "");
1008         return;
1009     } else if (command.find("kgs-time_settings") == 0) {
1010         // none, absolute, byoyomi, or canadian
1011         std::istringstream cmdstream(command);
1012         std::string tmp;
1013         std::string tc_type;
1014         int maintime, byotime, byostones, byoperiods;
1015 
1016         cmdstream >> tmp >> tc_type;
1017 
1018         if (tc_type.find("none") != std::string::npos) {
1019             // 30 mins
1020             game.set_timecontrol(30 * 60 * 100, 0, 0, 0);
1021         } else if (tc_type.find("absolute") != std::string::npos) {
1022             cmdstream >> maintime;
1023             game.set_timecontrol(maintime * 100, 0, 0, 0);
1024         } else if (tc_type.find("canadian") != std::string::npos) {
1025             cmdstream >> maintime >> byotime >> byostones;
1026             // convert to centiseconds and set
1027             game.set_timecontrol(maintime * 100, byotime * 100, byostones, 0);
1028         } else if (tc_type.find("byoyomi") != std::string::npos) {
1029             // KGS style Fischer clock
1030             cmdstream >> maintime >> byotime >> byoperiods;
1031             game.set_timecontrol(maintime * 100, byotime * 100, 0, byoperiods);
1032         } else {
1033             gtp_fail_printf(id, "syntax not understood");
1034             return;
1035         }
1036 
1037         if (!cmdstream.fail()) {
1038             gtp_printf(id, "");
1039         } else {
1040             gtp_fail_printf(id, "syntax not understood");
1041         }
1042         return;
1043     } else if (command.find("netbench") == 0) {
1044         std::istringstream cmdstream(command);
1045         std::string tmp;
1046         int iterations;
1047 
1048         cmdstream >> tmp;  // eat netbench
1049         cmdstream >> iterations;
1050 
1051         if (!cmdstream.fail()) {
1052             s_network->benchmark(&game, iterations);
1053         } else {
1054             s_network->benchmark(&game);
1055         }
1056         gtp_printf(id, "");
1057         return;
1058 
1059     } else if (command.find("printsgf") == 0) {
1060         std::istringstream cmdstream(command);
1061         std::string tmp, filename;
1062 
1063         cmdstream >> tmp;   // eat printsgf
1064         cmdstream >> filename;
1065 
1066         auto sgf_text = SGFTree::state_to_string(game, 0);
1067         // GTP says consecutive newlines terminate the output,
1068         // so we must filter those.
1069         boost::replace_all(sgf_text, "\n\n", "\n");
1070 
1071         if (cmdstream.fail()) {
1072             gtp_printf(id, "%s\n", sgf_text.c_str());
1073         } else {
1074             std::ofstream out(filename);
1075             out << sgf_text;
1076             out.close();
1077             gtp_printf(id, "");
1078         }
1079 
1080         return;
1081     } else if (command.find("load_training") == 0) {
1082         std::istringstream cmdstream(command);
1083         std::string tmp, filename;
1084 
1085         // tmp will eat "load_training"
1086         cmdstream >> tmp >> filename;
1087 
1088         Training::load_training(filename);
1089 
1090         if (!cmdstream.fail()) {
1091             gtp_printf(id, "");
1092         } else {
1093             gtp_fail_printf(id, "syntax not understood");
1094         }
1095 
1096         return;
1097     } else if (command.find("save_training") == 0) {
1098         std::istringstream cmdstream(command);
1099         std::string tmp, filename;
1100 
1101         // tmp will eat "save_training"
1102         cmdstream >> tmp >>  filename;
1103 
1104         Training::save_training(filename);
1105 
1106         if (!cmdstream.fail()) {
1107             gtp_printf(id, "");
1108         } else {
1109             gtp_fail_printf(id, "syntax not understood");
1110         }
1111 
1112         return;
1113     } else if (command.find("dump_training") == 0) {
1114         std::istringstream cmdstream(command);
1115         std::string tmp, winner_color, filename;
1116         int who_won;
1117 
1118         // tmp will eat "dump_training"
1119         cmdstream >> tmp >> winner_color >> filename;
1120 
1121         if (winner_color == "w" || winner_color == "white") {
1122             who_won = FullBoard::WHITE;
1123         } else if (winner_color == "b" || winner_color == "black") {
1124             who_won = FullBoard::BLACK;
1125         } else {
1126             gtp_fail_printf(id, "syntax not understood");
1127             return;
1128         }
1129 
1130         Training::dump_training(who_won, filename);
1131 
1132         if (!cmdstream.fail()) {
1133             gtp_printf(id, "");
1134         } else {
1135             gtp_fail_printf(id, "syntax not understood");
1136         }
1137 
1138         return;
1139     } else if (command.find("dump_debug") == 0) {
1140         std::istringstream cmdstream(command);
1141         std::string tmp, filename;
1142 
1143         // tmp will eat "dump_debug"
1144         cmdstream >> tmp >> filename;
1145 
1146         Training::dump_debug(filename);
1147 
1148         if (!cmdstream.fail()) {
1149             gtp_printf(id, "");
1150         } else {
1151             gtp_fail_printf(id, "syntax not understood");
1152         }
1153 
1154         return;
1155     } else if (command.find("dump_supervised") == 0) {
1156         std::istringstream cmdstream(command);
1157         std::string tmp, sgfname, outname;
1158 
1159         // tmp will eat dump_supervised
1160         cmdstream >> tmp >> sgfname >> outname;
1161 
1162         Training::dump_supervised(sgfname, outname);
1163 
1164         if (!cmdstream.fail()) {
1165             gtp_printf(id, "");
1166         } else {
1167             gtp_fail_printf(id, "syntax not understood");
1168         }
1169         return;
1170     } else if (command.find("lz-memory_report") == 0) {
1171         auto base_memory = get_base_memory();
1172         auto tree_size = add_overhead(UCTNodePointer::get_tree_size());
1173         auto cache_size = add_overhead(s_network->get_estimated_cache_size());
1174 
1175         auto total = base_memory + tree_size + cache_size;
1176         gtp_printf(id,
1177             "Estimated total memory consumption: %d MiB.\n"
1178             "Network with overhead: %d MiB / Search tree: %d MiB / Network cache: %d\n",
1179             total / MiB, base_memory / MiB, tree_size / MiB, cache_size / MiB);
1180         return;
1181     } else if (command.find("lz-setoption") == 0) {
1182         return execute_setoption(*search.get(), id, command);
1183     } else if (command.find("gomill-explain_last_move") == 0) {
1184         gtp_printf(id, "%s\n", search->explain_last_think().c_str());
1185         return;
1186     }
1187     gtp_fail_printf(id, "unknown command");
1188     return;
1189 }
1190 
parse_option(std::istringstream & is)1191 std::pair<std::string, std::string> GTP::parse_option(std::istringstream& is) {
1192     std::string token, name, value;
1193 
1194     // Read option name (can contain spaces)
1195     while (is >> token && token != "value")
1196         name += std::string(" ", name.empty() ? 0 : 1) + token;
1197 
1198     // Read option value (can contain spaces)
1199     while (is >> token)
1200         value += std::string(" ", value.empty() ? 0 : 1) + token;
1201 
1202     return std::make_pair(name, value);
1203 }
1204 
get_base_memory()1205 size_t GTP::get_base_memory() {
1206     // At the moment of writing the memory consumption is
1207     // roughly network size + 85 for one GPU and + 160 for two GPUs.
1208 #ifdef USE_OPENCL
1209     auto gpus = std::max(cfg_gpus.size(), size_t{1});
1210     return s_network->get_estimated_size() + 85 * MiB * gpus;
1211 #else
1212     return s_network->get_estimated_size();
1213 #endif
1214 }
1215 
set_max_memory(size_t max_memory,int cache_size_ratio_percent)1216 std::pair<bool, std::string> GTP::set_max_memory(size_t max_memory,
1217     int cache_size_ratio_percent) {
1218     if (max_memory == 0) {
1219         max_memory = UCTSearch::DEFAULT_MAX_MEMORY;
1220     }
1221 
1222     // Calculate amount of memory available for the search tree +
1223     // NNCache by estimating a constant memory overhead first.
1224     auto base_memory = get_base_memory();
1225 
1226     if (max_memory < base_memory) {
1227         return std::make_pair(false, "Not enough memory for network. " +
1228             std::to_string(base_memory / MiB) + " MiB required.");
1229     }
1230 
1231     auto max_memory_for_search = max_memory - base_memory;
1232 
1233     assert(cache_size_ratio_percent >= 1);
1234     assert(cache_size_ratio_percent <= 99);
1235     auto max_cache_size = max_memory_for_search *
1236         cache_size_ratio_percent / 100;
1237 
1238     auto max_cache_count =
1239         (int)(remove_overhead(max_cache_size) / NNCache::ENTRY_SIZE);
1240 
1241     // Verify if the setting would not result in too little cache.
1242     if (max_cache_count < NNCache::MIN_CACHE_COUNT) {
1243         return std::make_pair(false, "Not enough memory for cache.");
1244     }
1245     auto max_tree_size = max_memory_for_search - max_cache_size;
1246 
1247     if (max_tree_size < UCTSearch::MIN_TREE_SPACE) {
1248         return std::make_pair(false, "Not enough memory for search tree.");
1249     }
1250 
1251     // Only if settings are ok we store the values in config.
1252     cfg_max_memory = max_memory;
1253     cfg_max_cache_ratio_percent = cache_size_ratio_percent;
1254     // Set max_tree_size.
1255     cfg_max_tree_size = remove_overhead(max_tree_size);
1256     // Resize cache.
1257     s_network->nncache_resize(max_cache_count);
1258 
1259     return std::make_pair(true, "Setting max tree size to " +
1260         std::to_string(max_tree_size / MiB) + " MiB and cache size to " +
1261         std::to_string(max_cache_size / MiB) +
1262         " MiB.");
1263 }
1264 
execute_setoption(UCTSearch & search,int id,const std::string & command)1265 void GTP::execute_setoption(UCTSearch & search,
1266                             int id, const std::string &command) {
1267     std::istringstream cmdstream(command);
1268     std::string tmp, name_token;
1269 
1270     // Consume lz_setoption, name.
1271     cmdstream >> tmp >> name_token;
1272 
1273     // Print available options if called without an argument.
1274     if (cmdstream.fail()) {
1275         std::string options_out_tmp("");
1276         for (int i = 0; s_options[i].size() > 0; i++) {
1277             options_out_tmp = options_out_tmp + "\n" + s_options[i];
1278         }
1279         gtp_printf(id, options_out_tmp.c_str());
1280         return;
1281     }
1282 
1283     if (name_token.find("name") != 0) {
1284         gtp_fail_printf(id, "incorrect syntax for lz-setoption");
1285         return;
1286     }
1287 
1288     std::string name, value;
1289     std::tie(name, value) = parse_option(cmdstream);
1290 
1291     if (name == "maximum memory use (mib)") {
1292         std::istringstream valuestream(value);
1293         int max_memory_in_mib;
1294         valuestream >> max_memory_in_mib;
1295         if (!valuestream.fail()) {
1296             if (max_memory_in_mib < 128 || max_memory_in_mib > 131072) {
1297                 gtp_fail_printf(id, "incorrect value");
1298                 return;
1299             }
1300             bool result;
1301             std::string reason;
1302             std::tie(result, reason) = set_max_memory(max_memory_in_mib * MiB,
1303                 cfg_max_cache_ratio_percent);
1304             if (result) {
1305                 gtp_printf(id, reason.c_str());
1306             } else {
1307                 gtp_fail_printf(id, reason.c_str());
1308             }
1309             return;
1310         } else {
1311             gtp_fail_printf(id, "incorrect value");
1312             return;
1313         }
1314     } else if (name == "percentage of memory for cache") {
1315         std::istringstream valuestream(value);
1316         int cache_size_ratio_percent;
1317         valuestream >> cache_size_ratio_percent;
1318         if (cache_size_ratio_percent < 1 || cache_size_ratio_percent > 99) {
1319             gtp_fail_printf(id, "incorrect value");
1320             return;
1321         }
1322         bool result;
1323         std::string reason;
1324         std::tie(result, reason) = set_max_memory(cfg_max_memory,
1325             cache_size_ratio_percent);
1326         if (result) {
1327             gtp_printf(id, reason.c_str());
1328         } else {
1329             gtp_fail_printf(id, reason.c_str());
1330         }
1331         return;
1332     } else if (name == "visits") {
1333         std::istringstream valuestream(value);
1334         int visits;
1335         valuestream >> visits;
1336         cfg_max_visits = visits;
1337 
1338         // 0 may be specified to mean "no limit"
1339         if (cfg_max_visits == 0) {
1340             cfg_max_visits = UCTSearch::UNLIMITED_PLAYOUTS;
1341         }
1342         // Note that if the visits are changed but no
1343         // explicit command to set memory usage is given,
1344         // we will stick with the initial guess we made on startup.
1345         search.set_visit_limit(cfg_max_visits);
1346 
1347         gtp_printf(id, "");
1348     } else if (name == "playouts") {
1349         std::istringstream valuestream(value);
1350         int playouts;
1351         valuestream >> playouts;
1352         cfg_max_playouts = playouts;
1353 
1354         // 0 may be specified to mean "no limit"
1355         if (cfg_max_playouts == 0) {
1356             cfg_max_playouts = UCTSearch::UNLIMITED_PLAYOUTS;
1357         } else if (cfg_allow_pondering) {
1358             // Limiting playouts while pondering is still enabled
1359             // makes no sense.
1360             gtp_fail_printf(id, "incorrect value");
1361             return;
1362         }
1363 
1364         // Note that if the playouts are changed but no
1365         // explicit command to set memory usage is given,
1366         // we will stick with the initial guess we made on startup.
1367         search.set_playout_limit(cfg_max_playouts);
1368 
1369         gtp_printf(id, "");
1370     } else if (name == "lagbuffer") {
1371         std::istringstream valuestream(value);
1372         int lagbuffer;
1373         valuestream >> lagbuffer;
1374         cfg_lagbuffer_cs = lagbuffer;
1375         gtp_printf(id, "");
1376     } else if (name == "pondering") {
1377         std::istringstream valuestream(value);
1378         std::string toggle;
1379         valuestream >> toggle;
1380         if (toggle == "true") {
1381             if (cfg_max_playouts != UCTSearch::UNLIMITED_PLAYOUTS) {
1382                 gtp_fail_printf(id, "incorrect value");
1383                 return;
1384             }
1385             cfg_allow_pondering = true;
1386         } else if (toggle == "false") {
1387             cfg_allow_pondering = false;
1388         } else {
1389             gtp_fail_printf(id, "incorrect value");
1390             return;
1391         }
1392         gtp_printf(id, "");
1393     } else if (name == "resign percentage") {
1394         std::istringstream valuestream(value);
1395         int resignpct;
1396         valuestream >> resignpct;
1397         cfg_resignpct = resignpct;
1398         gtp_printf(id, "");
1399     } else {
1400         gtp_fail_printf(id, "Unknown option");
1401     }
1402     return;
1403 }
1404