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