Commit | Line | Data |
---|---|---|
7eeb782e AT |
1 | #! /usr/bin/env pike |
2 | ||
3 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ | |
4 | * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * | |
5 | * http://www.gnu.org/software/gnugo/ for more information. * | |
6 | * * | |
7 | * Copyright 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 and * | |
8 | * 2009 by the Free Software Foundation. * | |
9 | * * | |
10 | * This program is free software; you can redistribute it and/or * | |
11 | * modify it under the terms of the GNU General Public License as * | |
12 | * published by the Free Software Foundation - version 3 or * | |
13 | * (at your option) any later version. * | |
14 | * * | |
15 | * This program is distributed in the hope that it will be useful, * | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
18 | * GNU General Public License in file COPYING for more details. * | |
19 | * * | |
20 | * You should have received a copy of the GNU General Public * | |
21 | * License along with this program; if not, write to the Free * | |
22 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * | |
23 | * Boston, MA 02111, USA. * | |
24 | \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | |
25 | ||
26 | #define DUMP_GTP_PIPES 0 | |
27 | ||
28 | ||
29 | int | |
30 | maximal_fixed_handicap(int board_size) | |
31 | { | |
32 | if (board_size < 7) | |
33 | return 0; | |
34 | if (board_size % 2 == 1 && board_size >= 9) | |
35 | return 9; | |
36 | return 4; | |
37 | } | |
38 | ||
39 | ||
40 | float | |
41 | result_to_float(string result) | |
42 | { | |
43 | if (result[..1] == "W+") | |
44 | return (float) result[2..]; | |
45 | if (result[..1] == "B+") | |
46 | return - (float) result[2..]; | |
47 | return (float) result; | |
48 | } | |
49 | ||
50 | ||
51 | string | |
52 | arrange_values_nicely(array(string) values, string delimiter) | |
53 | { | |
54 | string result = ""; | |
55 | for (int k = 0; k < sizeof(values); k++) { | |
56 | if (k > 0 && k % 12 == 0) | |
57 | result += "\n"; | |
58 | result += delimiter + values[k]; | |
59 | } | |
60 | ||
61 | return result + "\n"; | |
62 | } | |
63 | ||
64 | ||
65 | string | |
66 | list_sgf_positions(array(array(string)) positions, array(string) sgf_properties) | |
67 | { | |
68 | string result = ""; | |
69 | for (int k = 0; k < sizeof(positions); k++) { | |
70 | if (sizeof(positions[k])) | |
71 | result += sgf_properties[k] + arrange_values_nicely(positions[k], ""); | |
72 | } | |
73 | ||
74 | return result; | |
75 | } | |
76 | ||
77 | ||
78 | string | |
79 | nice_time(float _time) | |
80 | { | |
81 | int time = (int) (_time * 10); | |
82 | #ifdef __AUTO_BIGNUM__ | |
83 | return sprintf("%d:%02d.%d", time / 600, (time % 600) / 10, time % 10); | |
84 | #else | |
85 | return sprintf("%d:%02d", time / 600, (time % 600) / 10); | |
86 | #endif | |
87 | } | |
88 | ||
89 | ||
90 | int | |
91 | is_numeric(string str) | |
92 | { | |
93 | str = String.trim_all_whites(str); | |
94 | if (str[0] == '+') | |
95 | str = str[1..]; | |
96 | ||
97 | return ((string) ((int) str)) == str; | |
98 | } | |
99 | ||
100 | ||
101 | class GtpServer { | |
102 | int server_is_up; | |
103 | int board_size; | |
104 | private Stdio.File file_out; | |
105 | private Stdio.FILE file_in; | |
106 | int protocol_version; | |
107 | string color; | |
108 | string capitalized_color; | |
109 | string command_line; | |
110 | string full_engine_name; | |
111 | string random_seed; | |
112 | private float main_time; | |
113 | private float byo_yomi_time; | |
114 | private int byo_yomi_stones; | |
115 | private float time_left; | |
116 | private int stones_left; | |
117 | float total_used_time; | |
118 | private array(string) statistics; | |
119 | private array(string) reset; | |
120 | private array totals; | |
121 | ||
122 | ||
123 | void | |
124 | create(string _command_line, array(string) _statistics, array(string) _reset, | |
125 | string _color, | |
126 | float _main_time, float _byo_yomi_time, int _byo_yomi_stones) | |
127 | { | |
128 | file_out = Stdio.File(); | |
129 | file_in = Stdio.FILE(); | |
130 | set_color(_color); | |
131 | command_line = _command_line; | |
132 | ||
133 | array error = catch { | |
134 | Process.create_process(command_line / " ", | |
135 | ([ "stdin" : file_out->pipe(), | |
136 | "stdout" : file_in->pipe() ])); | |
137 | }; | |
138 | ||
139 | if (error) { | |
140 | werror(error[0]); | |
141 | werror("Command line was `%s'.\n", command_line); | |
142 | destruct(this_object()); | |
143 | } | |
144 | else { | |
145 | array error = catch { | |
146 | array answer = send_command("protocol_version"); | |
147 | protocol_version = answer[0] ? 1 : (int) answer[1]; | |
148 | full_engine_name = get_full_engine_name(); | |
149 | server_is_up = 1; | |
150 | }; | |
151 | if (error) { | |
152 | werror("Engine `%s' crashed at startup.\nPerhaps command line is wrong.\n", | |
153 | command_line); | |
154 | destruct(this_object()); | |
155 | } | |
156 | else { | |
157 | main_time = _main_time; | |
158 | byo_yomi_time = _byo_yomi_time; | |
159 | byo_yomi_stones = _byo_yomi_stones; | |
160 | total_used_time = 0.0; | |
161 | ||
162 | statistics = _statistics; | |
163 | reset = _reset; | |
164 | totals = ({ 0 }) * sizeof(statistics); | |
165 | } | |
166 | } | |
167 | } | |
168 | ||
169 | ||
170 | void | |
171 | set_color(string _color) | |
172 | { | |
173 | color = _color; | |
174 | capitalized_color = String.capitalize(color); | |
175 | } | |
176 | ||
177 | ||
178 | void | |
179 | restart_if_crashed() | |
180 | { | |
181 | if (!server_is_up) { | |
182 | werror("Restarting engine `%s' playing %s.\n", command_line, color); | |
183 | Process.create_process(command_line / " ", | |
184 | ([ "stdin" : file_out->pipe(), | |
185 | "stdout" : file_in->pipe() ])); | |
186 | server_is_up = 1; | |
187 | } | |
188 | } | |
189 | ||
190 | ||
191 | array | |
192 | send_command(string command) | |
193 | { | |
194 | #if DUMP_GTP_PIPES | |
195 | werror("[%s%s] %s\n", | |
196 | full_engine_name ? full_engine_name + ", " : "", color, command); | |
197 | #endif | |
198 | ||
199 | command = String.trim_all_whites(command); | |
200 | sscanf(command, "%[0-9]", string id); | |
201 | if (command[0] == '#' || command == id) | |
202 | return ({ 0, "" }); | |
203 | ||
204 | file_out->write("%s\n", command); | |
205 | string response = file_in->gets(); | |
206 | if (!response) { | |
207 | server_is_up = 0; | |
208 | error("Engine `%s' playing %s crashed!", command_line, color); | |
209 | } | |
210 | ||
211 | #if DUMP_GTP_PIPES | |
212 | werror("%s\n", response); | |
213 | #endif | |
214 | ||
215 | array result; | |
216 | int id_length = strlen(id); | |
217 | if (response && response[..id_length] == "=" + id) | |
218 | result = ({ 0, response[id_length + 1 ..] }); | |
219 | else if (response && response[..id_length] == "?" + id) | |
220 | result = ({ 1, response[id_length + 1 ..] }); | |
221 | else | |
222 | result = ({ -1, response }); | |
223 | ||
224 | result[1] = String.trim_all_whites(result[1]); | |
225 | while (1) { | |
226 | response = file_in->gets(); | |
227 | ||
228 | #if DUMP_GTP_PIPES | |
229 | werror("%s\n", response); | |
230 | #endif | |
231 | ||
232 | if (response == "") { | |
233 | if (result[0] < 0) { | |
234 | werror("Warning, unrecognized response to command `%s':\n", command); | |
235 | werror("%s\n", result[1]); | |
236 | } | |
237 | return result; | |
238 | } | |
239 | result[1] += "\n" + response; | |
240 | } | |
241 | } | |
242 | ||
243 | ||
244 | int | |
245 | is_known_command(string command) | |
246 | { | |
247 | array answer = send_command("known_command " + command); | |
248 | return !answer[0] && answer[1] == "true"; | |
249 | } | |
250 | ||
251 | ||
252 | string | |
253 | get_full_engine_name() | |
254 | { | |
255 | return send_command("name")[1] + " " + send_command("version")[1]; | |
256 | } | |
257 | ||
258 | ||
259 | void | |
260 | set_board_size(int _board_size) | |
261 | { | |
262 | board_size = _board_size; | |
263 | send_command("boardsize " + board_size); | |
264 | if (protocol_version >= 2) | |
265 | send_command("clear_board"); | |
266 | random_seed = get_random_seed(); | |
267 | } | |
268 | ||
269 | ||
270 | array(string) | |
271 | fixed_handicap(int handicap) | |
272 | { | |
273 | array answer = send_command("fixed_handicap " + handicap); | |
274 | return answer[0] ? ({}) : answer[1] / " "; | |
275 | } | |
276 | ||
277 | ||
278 | array(string) | |
279 | place_free_handicap(int handicap) | |
280 | { | |
281 | array answer = send_command("place_free_handicap " + handicap); | |
282 | return answer[0] ? ({}) : answer[1] / " "; | |
283 | } | |
284 | ||
285 | ||
286 | void | |
287 | set_free_handicap(array(string) stones) | |
288 | { | |
289 | send_command("set_free_handicap " + (stones * " ")); | |
290 | } | |
291 | ||
292 | ||
293 | array(string) | |
294 | set_handicap(int handicap, string handicap_mode, | |
295 | GtpServer opponent, GtpServer arbiter) | |
296 | { | |
297 | if (handicap == 0) | |
298 | return ({}); | |
299 | ||
300 | if (handicap_mode == "free") { | |
301 | array(string) stones = place_free_handicap(handicap); | |
302 | opponent->set_free_handicap(stones); | |
303 | if (arbiter) | |
304 | arbiter->set_free_handicap(stones); | |
305 | ||
306 | return stones; | |
307 | } | |
308 | else { | |
309 | opponent->fixed_handicap(handicap); | |
310 | if (arbiter) | |
311 | arbiter->fixed_handicap(handicap); | |
312 | ||
313 | return fixed_handicap(handicap); | |
314 | } | |
315 | } | |
316 | ||
317 | ||
318 | float | |
319 | get_komi() | |
320 | { | |
321 | return (float) (send_command("get_komi")[1]); | |
322 | } | |
323 | ||
324 | ||
325 | void | |
326 | set_komi(float komi) | |
327 | { | |
328 | send_command("komi " + komi); | |
329 | } | |
330 | ||
331 | ||
332 | array | |
333 | load_sgf(string sgf_file_name, int|string load_up_to) | |
334 | { | |
335 | array answer = send_command(sprintf("loadsgf %s %s", sgf_file_name, | |
336 | (string) load_up_to)); | |
337 | board_size = (int) send_command("query_boardsize")[1]; | |
338 | return answer; | |
339 | } | |
340 | ||
341 | ||
342 | void | |
343 | reset_engine() | |
344 | { | |
345 | foreach (reset, string reset_command) | |
346 | send_command(reset_command); | |
347 | ||
348 | initialize_time_control(); | |
349 | } | |
350 | ||
351 | ||
352 | void | |
353 | initialize_time_control() | |
354 | { | |
355 | if (main_time < 0.0) { | |
356 | time_left = 0.0; | |
357 | return; | |
358 | } | |
359 | ||
360 | if (main_time > 0.0) { | |
361 | time_left = main_time; | |
362 | stones_left = 0; | |
363 | } | |
364 | else { | |
365 | time_left = byo_yomi_time; | |
366 | stones_left = byo_yomi_stones; | |
367 | } | |
368 | ||
369 | send_command(sprintf("time_settings %d %d %d", (int) main_time, | |
370 | (int) byo_yomi_time, byo_yomi_stones)); | |
371 | } | |
372 | ||
373 | ||
374 | string | |
375 | generate_move(int use_time_control) | |
376 | { | |
377 | string result; | |
378 | int time_notch = 0; | |
379 | ||
380 | if (use_time_control) { | |
381 | if (main_time >= 0.0) { | |
382 | send_command(sprintf("time_left %s %d %d", color, | |
383 | (int) time_left, stones_left)); | |
384 | } | |
385 | ||
386 | #ifdef __AUTO_BIGNUM__ | |
387 | time_notch = gethrtime(); | |
388 | #else | |
389 | time_notch = time(); | |
390 | #endif | |
391 | } | |
392 | ||
393 | if (protocol_version >= 2) | |
394 | result = send_command("genmove " + color)[1]; | |
395 | else | |
396 | result = send_command("genmove_" + color)[1]; | |
397 | ||
398 | if (use_time_control) { | |
399 | #ifdef __AUTO_BIGNUM__ | |
400 | time_left -= (gethrtime() - time_notch) / 1.0e6; | |
401 | #else | |
402 | time_left -= time() - time_notch; | |
403 | #endif | |
404 | if (main_time >= 0.0) { | |
405 | if (time_left < 0.0) { | |
406 | if (stones_left > 0) | |
407 | return "time"; | |
408 | else { | |
409 | total_used_time += main_time; | |
410 | time_left += byo_yomi_time; | |
411 | stones_left = byo_yomi_stones; | |
412 | if (time_left < 0.0) | |
413 | return "time"; | |
414 | } | |
415 | } | |
416 | ||
417 | if (stones_left > 0 && --stones_left == 0) { | |
418 | total_used_time += byo_yomi_time - time_left; | |
419 | time_left = byo_yomi_time; | |
420 | stones_left = byo_yomi_stones; | |
421 | } | |
422 | } | |
423 | } | |
424 | ||
425 | return result; | |
426 | } | |
427 | ||
428 | ||
429 | string | |
430 | get_time_left() | |
431 | { | |
432 | if (main_time < 0.0) | |
433 | return ""; | |
434 | ||
435 | if (time_left < 0) | |
436 | return "lost on time"; | |
437 | ||
438 | if (stones_left <= 0) { | |
439 | return sprintf("main time: %s / %s", | |
440 | nice_time(time_left), nice_time(main_time)); | |
441 | } | |
442 | ||
443 | return sprintf("byo-yomi time: %s / %s, stones: %d / %d", | |
444 | nice_time(time_left), nice_time(byo_yomi_time), | |
445 | stones_left, byo_yomi_stones); | |
446 | } | |
447 | ||
448 | ||
449 | void | |
450 | finalize_time_control() | |
451 | { | |
452 | if (main_time < 0.0) | |
453 | total_used_time += -time_left; | |
454 | else if (stones_left > 0) | |
455 | total_used_time += byo_yomi_time - time_left; | |
456 | else | |
457 | total_used_time += main_time - time_left; | |
458 | } | |
459 | ||
460 | ||
461 | void | |
462 | play(string color, string move) | |
463 | { | |
464 | if (protocol_version >= 2) | |
465 | send_command(sprintf("play %s %s", color, move)); | |
466 | else | |
467 | send_command(sprintf("%s %s", color, move)); | |
468 | } | |
469 | ||
470 | ||
471 | string | |
472 | get_random_seed() | |
473 | { | |
474 | array answer = send_command("get_random_seed"); | |
475 | return answer[0] ? "unknown" : answer[1]; | |
476 | } | |
477 | ||
478 | ||
479 | void | |
480 | set_random_seed(int|string seed) | |
481 | { | |
482 | random_seed = (string) seed; | |
483 | send_command("set_random_seed " + random_seed); | |
484 | } | |
485 | ||
486 | ||
487 | array(string) | |
488 | list_stones(string color) | |
489 | { | |
490 | return send_command("list_stones " + color)[1] / " "; | |
491 | } | |
492 | ||
493 | ||
494 | array(array(string)) | |
495 | get_position_as_sgf() | |
496 | { | |
497 | if (!is_known_command("list_stones")) | |
498 | return ({}); | |
499 | return ({ map(list_stones("white"), move_to_sgf_notation), | |
500 | map(list_stones("black"), move_to_sgf_notation) }); | |
501 | } | |
502 | ||
503 | ||
504 | array(string) | |
505 | final_status_list(string status) | |
506 | { | |
507 | array result = send_command("final_status_list " + status); | |
508 | return result[0] ? ({}) : ((result[1] / "\n") * " ") / " " - ({""}); | |
509 | } | |
510 | ||
511 | ||
512 | array(array(string)) | |
513 | get_territory_as_sgf() | |
514 | { | |
515 | if (!is_known_command("final_status_list")) | |
516 | return ({}); | |
517 | ||
518 | array(array(string)) result | |
519 | = ({ map(final_status_list("white_territory"), move_to_sgf_notation), | |
520 | map(final_status_list("black_territory"), move_to_sgf_notation) }); | |
521 | if (result[0] == ({}) && result[1] == ({})) | |
522 | return ({}); | |
523 | ||
524 | if (is_known_command("color")) { | |
525 | array(string) dead_stones = final_status_list("dead"); | |
526 | foreach (dead_stones, string stone) { | |
527 | switch (send_command("color " + stone)[1]) { | |
528 | case "black": result[0] += ({ move_to_sgf_notation(stone) }); break; | |
529 | case "white": result[1] += ({ move_to_sgf_notation(stone) }); break; | |
530 | } | |
531 | } | |
532 | } | |
533 | ||
534 | return result; | |
535 | } | |
536 | ||
537 | ||
538 | string | |
539 | show_board() | |
540 | { | |
541 | array answer = send_command("showboard"); | |
542 | if (answer[0]) | |
543 | return "\n"; | |
544 | if (answer[1] != "" && answer[1][0] == '\n') | |
545 | return answer[1][1..] + "\n"; | |
546 | return answer[1] + "\n"; | |
547 | } | |
548 | ||
549 | ||
550 | void | |
551 | print_statistics(int dont_print_numerics) | |
552 | { | |
553 | for (int k = 0; k < sizeof(statistics); k++) { | |
554 | array command_result = send_command(statistics[k]); | |
555 | if (!command_result[0]) { | |
556 | if (is_numeric(command_result[1])) { | |
557 | if (totals[k] != "") | |
558 | totals[k] += (int) command_result[1]; | |
559 | ||
560 | if (dont_print_numerics) | |
561 | continue; | |
562 | } | |
563 | else | |
564 | totals[k] = ""; | |
565 | ||
566 | write("%s (%s) statistic `%s': %s\n", full_engine_name, color, | |
567 | statistics[k], command_result[1]); | |
568 | } | |
569 | else { | |
570 | werror("Couldn't acquire statistic `%s': engine failed with message \"%s\"\n", | |
571 | statistics[k], command_result[1]); | |
572 | } | |
573 | } | |
574 | } | |
575 | ||
576 | ||
577 | void | |
578 | print_statistic_totals() | |
579 | { | |
580 | int first_total = 1; | |
581 | for (int k = 0; k < sizeof(statistics); k++) { | |
582 | if (totals[k] != "") { | |
583 | if (first_total) { | |
584 | write("\n%s (%s) statistics totals:\n", full_engine_name, color); | |
585 | first_total = 0; | |
586 | } | |
587 | ||
588 | write("`%s' total: %d\n", statistics[k], totals[k]); | |
589 | } | |
590 | } | |
591 | } | |
592 | ||
593 | ||
594 | string | |
595 | final_score() | |
596 | { | |
597 | array answer = send_command("final_score"); | |
598 | return answer[0] ? "?" : answer[1]; | |
599 | } | |
600 | ||
601 | ||
602 | string | |
603 | cpu_time() | |
604 | { | |
605 | if (is_known_command("cputime")) | |
606 | return send_command("cputime")[1]; | |
607 | return ""; | |
608 | } | |
609 | ||
610 | ||
611 | void | |
612 | quit() | |
613 | { | |
614 | send_command("quit"); | |
615 | } | |
616 | ||
617 | ||
618 | string | |
619 | move_to_sgf_notation(string coordinates) | |
620 | { | |
621 | coordinates = lower_case(coordinates); | |
622 | if (coordinates == "pass") | |
623 | return "[]"; | |
624 | ||
625 | int y = board_size - ((int) coordinates[1..]); | |
626 | int x = coordinates[0] - 'a'; | |
627 | if (x > 'i' - 'a') | |
628 | x--; | |
629 | ||
630 | return sprintf("[%c%c]", 'a' + x, 'a' + y); | |
631 | } | |
632 | }; | |
633 | ||
634 | ||
635 | class GtpGame { | |
636 | private GtpServer white; | |
637 | private GtpServer black; | |
638 | private GtpServer arbiter; | |
639 | private int verbose; | |
640 | private int black_to_play; | |
641 | private string sgf_header; | |
642 | private array(array(string)) territory; | |
643 | private int totals_only; | |
644 | ||
645 | ||
646 | void | |
647 | create(string command_line_white, string command_line_black, | |
648 | string command_line_arbiter, | |
649 | array(string) statistics_white, array(string) statistics_black, | |
650 | array(string) reset_white, array(string) reset_black, | |
651 | int _totals_only, int _verbose, | |
652 | float main_time, float byo_yomi_time, int byo_yomi_stones) | |
653 | { | |
654 | verbose = _verbose; | |
655 | totals_only = _totals_only; | |
656 | white = GtpServer(command_line_white, statistics_white, reset_white, | |
657 | "white", main_time, byo_yomi_time, byo_yomi_stones); | |
658 | if (white) { | |
659 | black = GtpServer(command_line_black, statistics_black, reset_black, | |
660 | "black", main_time, byo_yomi_time, byo_yomi_stones); | |
661 | ||
662 | if (black && command_line_arbiter != "") { | |
663 | arbiter = GtpServer(command_line_arbiter, ({}), ({}), "arbiter", | |
664 | -1.0, 0.0, 0); | |
665 | } | |
666 | else | |
667 | arbiter = UNDEFINED; | |
668 | } | |
669 | ||
670 | if (!white || !black || (command_line_arbiter != "" && !arbiter)) | |
671 | destruct(this_object()); | |
672 | } | |
673 | ||
674 | ||
675 | void | |
676 | swap_engines() | |
677 | { | |
678 | GtpServer temp = white; | |
679 | white = black; | |
680 | black = temp; | |
681 | white->set_color("white"); | |
682 | black->set_color("black"); | |
683 | } | |
684 | ||
685 | ||
686 | void | |
687 | start_new_game(int board_size, int handicap, string handicap_mode, | |
688 | float komi) | |
689 | { | |
690 | white->set_board_size(board_size); | |
691 | black->set_board_size(board_size); | |
692 | if (arbiter) | |
693 | arbiter->set_board_size(board_size); | |
694 | ||
695 | array(string) stones = black->set_handicap(handicap, handicap_mode, | |
696 | white, arbiter); | |
697 | stones = map(stones, black->move_to_sgf_notation); | |
698 | ||
699 | white->set_komi(komi); | |
700 | black->set_komi(komi); | |
701 | if (arbiter) | |
702 | arbiter->set_komi(komi); | |
703 | ||
704 | black_to_play = (handicap == 0); | |
705 | sgf_header = sprintf("(;\nGM[1]FF[4]\nSZ[%d]HA[%d]KM[%.1f]\n", | |
706 | board_size, handicap, komi); | |
707 | if (handicap) | |
708 | sgf_header += "AB" + arrange_values_nicely(stones, ""); | |
709 | } | |
710 | ||
711 | ||
712 | array(string) | |
713 | play(string|int sgf_file_name) | |
714 | { | |
715 | array(string) sgf_moves = ({}); | |
716 | array(array(string)) move_history = ({}); | |
717 | string special_win = ""; | |
718 | GtpServer player; | |
719 | GtpServer opponent; | |
720 | ||
721 | if (verbose) | |
722 | werror("\nBeginning a new game.\n"); | |
723 | ||
724 | white->reset_engine(); | |
725 | black->reset_engine(); | |
726 | territory = UNDEFINED; | |
727 | ||
728 | array error = catch { | |
729 | int passes = 0; | |
730 | while (1) { | |
731 | player = black_to_play ? black : white; | |
732 | opponent = black_to_play ? white : black; | |
733 | ||
734 | string move = player->generate_move(1); | |
735 | string move_lower_case = lower_case(move); | |
736 | ||
737 | if (move_lower_case == "resign") { | |
738 | if (verbose) | |
739 | werror(player->capitalized_color + " resigns!\n"); | |
740 | ||
741 | special_win = sprintf("%c+Resign", opponent->capitalized_color[0]); | |
742 | break; | |
743 | } | |
744 | ||
745 | if (move_lower_case == "time") { | |
746 | if (verbose) | |
747 | werror(player->capitalized_color + " loses on time!\n"); | |
748 | ||
749 | special_win = sprintf("%c+Time", opponent->capitalized_color[0]); | |
750 | break; | |
751 | } | |
752 | ||
753 | opponent->play(player->color, move); | |
754 | sgf_moves += ({ sprintf("%c%s", player->capitalized_color[0], | |
755 | player->move_to_sgf_notation(move)) }); | |
756 | if (arbiter) | |
757 | move_history += ({ ({ player->color, move }) }); | |
758 | ||
759 | if (move_lower_case == "pass") { | |
760 | if (verbose) | |
761 | werror("play " + player->capitalized_color + " pass\n"); | |
762 | ||
763 | if (++passes == 2) | |
764 | break; | |
765 | } | |
766 | else { | |
767 | if (verbose) { | |
768 | string time_left = " (" + player->get_time_left() + ")"; | |
769 | if (time_left == " ()") | |
770 | time_left = ""; | |
771 | ||
772 | werror("play %s %s%s\n", player->capitalized_color, | |
773 | move, time_left); | |
774 | } | |
775 | ||
776 | passes = 0; | |
777 | } | |
778 | ||
779 | if (verbose >= 2) { | |
780 | string board = white->show_board(); | |
781 | if (board == "") | |
782 | board = black->show_board(); | |
783 | werror("%s\n", board); | |
784 | } | |
785 | black_to_play = !black_to_play; | |
786 | ||
787 | if (sgf_file_name) | |
788 | write_sgf_file(sgf_file_name, sgf_moves, ({ "Void" })); | |
789 | } | |
790 | }; | |
791 | ||
792 | white->finalize_time_control(); | |
793 | black->finalize_time_control(); | |
794 | ||
795 | array(string) result; | |
796 | if (error) { | |
797 | result = ({ "Void", error[0] }); | |
798 | if (sgf_file_name) | |
799 | werror("The game will be saved in file `%s'.\n", sgf_file_name); | |
800 | ||
801 | white->restart_if_crashed(); | |
802 | black->restart_if_crashed(); | |
803 | if (arbiter) | |
804 | arbiter->restart_if_crashed(); | |
805 | } | |
806 | else { | |
807 | if (special_win == "") { | |
808 | result = ({ white->final_score(), black->final_score() }); | |
809 | ||
810 | if (result[1] == "?" || result[0] == result[1]) | |
811 | result = ({ result[0] }); | |
812 | else if (result[0] == "?") | |
813 | result = ({ result[1] }); | |
814 | ||
815 | territory = player->get_territory_as_sgf(); | |
816 | if (territory == ({})) | |
817 | territory = opponent->get_territory_as_sgf(); | |
818 | ||
819 | if (arbiter && (sizeof(result) == 2 || result[0] == "?")) { | |
820 | foreach (move_history, array(string) move) | |
821 | arbiter->play(move[0], move[1]); | |
822 | result = ({ arbiter->final_score() }); | |
823 | ||
824 | if (territory == ({})) | |
825 | territory = arbiter->get_territory_as_sgf(); | |
826 | } | |
827 | } | |
828 | else | |
829 | result = ({ special_win }); | |
830 | } | |
831 | ||
832 | if (sgf_file_name) | |
833 | write_sgf_file(sgf_file_name, sgf_moves, result); | |
834 | return result; | |
835 | } | |
836 | ||
837 | ||
838 | int | |
839 | init_endgame_contest(string endgame_file_name, int endgame_moves) | |
840 | { | |
841 | array(string) sgf_nodes; | |
842 | array error = catch { | |
843 | sgf_nodes = Stdio.read_file(endgame_file_name) / ";"; | |
844 | }; | |
845 | if (error) { | |
846 | werror(error[0]); | |
847 | return 0; | |
848 | } | |
849 | ||
850 | Regexp move = Regexp("\\<([BW]\\[[a-z][a-z]\\])"); | |
851 | array(string) moves = ({}); | |
852 | foreach (sgf_nodes, string sgf_node) { | |
853 | array move_groups = move->split(sgf_node); | |
854 | if (move_groups) | |
855 | moves += ({ move_groups[0] }); | |
856 | } | |
857 | ||
858 | int load_up_to = max(sizeof(moves) - endgame_moves + 1, 1); | |
859 | array white_answer = white->load_sgf(endgame_file_name, load_up_to); | |
860 | array black_answer = black->load_sgf(endgame_file_name, load_up_to); | |
861 | array arbiter_answer = (arbiter | |
862 | ? arbiter->load_sgf(endgame_file_name, load_up_to) | |
863 | : ({0})); | |
864 | ||
865 | if (white_answer[0] || black_answer[0] || arbiter_answer[0]) { | |
866 | werror("File `%s' might be corrupt. Engines refuse to load it.\n", | |
867 | endgame_file_name); | |
868 | return 0; | |
869 | } | |
870 | ||
871 | sgf_header = sprintf("(;\nGM[1]FF[4]\nSZ[%d]KM[%.1f]\n" | |
872 | "C[Original game: `%s' loaded up to move %d]\n", | |
873 | white->board_size, white->get_komi(), | |
874 | endgame_file_name, load_up_to); | |
875 | array(array(string)) stones = white->get_position_as_sgf(); | |
876 | if (!stones) | |
877 | stones = black->get_position_as_sgf(); | |
878 | if (!stones && arbiter) | |
879 | stones = arbiter->get_position_as_sgf(); | |
880 | if (!stones) { | |
881 | stones = ({ ({}), ({}) }); | |
882 | moves = moves[..load_up_to - 2]; | |
883 | foreach (moves, string move) | |
884 | stones[move[0] == 'B'] += ({ move[1..] }); | |
885 | } | |
886 | ||
887 | sgf_header += list_sgf_positions(stones, ({ "AW", "AB" })); | |
888 | ||
889 | white->set_random_seed(0); | |
890 | black->set_random_seed(0); | |
891 | ||
892 | black_to_play = (black_answer[1] == "black"); | |
893 | return load_up_to; | |
894 | } | |
895 | ||
896 | ||
897 | void | |
898 | reinit_endgame_contest(string endgame_file_name, int load_up_to) | |
899 | { | |
900 | swap_engines(); | |
901 | white->load_sgf(endgame_file_name, load_up_to); | |
902 | array black_answer = black->load_sgf(endgame_file_name, load_up_to); | |
903 | ||
904 | if (arbiter) | |
905 | arbiter->load_sgf(endgame_file_name, load_up_to); | |
906 | ||
907 | white->set_random_seed(0); | |
908 | black->set_random_seed(0); | |
909 | black_to_play = (black_answer[1] == "black"); | |
910 | } | |
911 | ||
912 | ||
913 | void | |
914 | write_sgf_file(string sgf_file_name, array(string) sgf_moves, | |
915 | array(string) result) | |
916 | { | |
917 | string sgf_data = sprintf("PW[%s (random seed %s)]\nPB[%s (random seed %s)]\n" | |
918 | "RU[Japanese]\nRE[%s]\n", | |
919 | white->full_engine_name, white->random_seed, | |
920 | black->full_engine_name, black->random_seed, | |
921 | result[0]); | |
922 | if (sizeof(result) > 1) { | |
923 | sgf_data += sprintf("GC[%s]\n", result[0] == "Void" ? result[1] : | |
924 | sprintf("Engines disagreed on results:\n" | |
925 | "White claimed %s\nBlack claimed %s\n", | |
926 | result[0], result[1])); | |
927 | } | |
928 | ||
929 | sgf_data += arrange_values_nicely(sgf_moves, ";"); | |
930 | if (sizeof(result) == 1) { | |
931 | if (territory) | |
932 | sgf_data += ";" + list_sgf_positions(territory, ({ "TW", "TB" })); | |
933 | } | |
934 | else if (result[0] == "Void") | |
935 | sgf_data += ";C[" + result[1] + "]\n"; | |
936 | sgf_data += ")\n"; | |
937 | ||
938 | array error = catch { | |
939 | Stdio.write_file(sgf_file_name, sgf_header + sgf_data); | |
940 | }; | |
941 | ||
942 | if (error) | |
943 | werror(error[0]); | |
944 | } | |
945 | ||
946 | ||
947 | void | |
948 | print_time_report() | |
949 | { | |
950 | string cpu_time_white = white->cpu_time(); | |
951 | string cpu_time_black = black->cpu_time(); | |
952 | if (cpu_time_white != "") | |
953 | write("White: %ss CPU time.\n", cpu_time_white); | |
954 | if (cpu_time_black != "") | |
955 | write("Black: %ss CPU time.\n", cpu_time_black); | |
956 | ||
957 | write("\nTime control report (wall move generation time):\n"); | |
958 | write(" White: %s\n", nice_time(white->total_used_time)); | |
959 | write(" Black: %s\n", nice_time(black->total_used_time)); | |
960 | } | |
961 | ||
962 | ||
963 | void | |
964 | print_statistics() | |
965 | { | |
966 | white->print_statistics(totals_only); | |
967 | black->print_statistics(totals_only); | |
968 | } | |
969 | ||
970 | ||
971 | void | |
972 | print_statistic_totals() | |
973 | { | |
974 | white->print_statistic_totals(); | |
975 | black->print_statistic_totals(); | |
976 | } | |
977 | ||
978 | ||
979 | void | |
980 | finalize() | |
981 | { | |
982 | white->quit(); | |
983 | black->quit(); | |
984 | } | |
985 | } | |
986 | ||
987 | ||
988 | void | |
989 | run_twogtp_match(GtpGame game, int num_games, int board_size, int handicap, | |
990 | string handicap_mode, int adjust_handicap, float komi, | |
991 | int verbose, string|int sgf_base, int skip_games) | |
992 | { | |
993 | int white_wins = 0; | |
994 | int black_wins = 0; | |
995 | int jigos = 0; | |
996 | int result_unknown = 0; | |
997 | int disagreed_games = 0; | |
998 | int last_streak = 0; | |
999 | int last_to_win = '0'; | |
1000 | array(array(string)) results = ({}); | |
1001 | ||
1002 | for (int k = skip_games; k < skip_games + num_games; k++) { | |
1003 | game->start_new_game(board_size, handicap, handicap_mode, komi); | |
1004 | array(string) result | |
1005 | = game->play(sgf_base ? sprintf("%s%03d.sgf", sgf_base, k + 1) : 0); | |
1006 | ||
1007 | write("Game %d: %s\n", k + 1, result * " "); | |
1008 | game->print_statistics(); | |
1009 | results += ({result}); | |
1010 | ||
1011 | if (sizeof(result) == 1 || (result[0][0] == result[1][0])) { | |
1012 | switch (result[0][0]) { | |
1013 | case 'W': | |
1014 | case 'B': | |
1015 | if (result[0][0] == 'W') | |
1016 | white_wins++; | |
1017 | else | |
1018 | black_wins++; | |
1019 | ||
1020 | if (result[0][0] == last_to_win) | |
1021 | last_streak++; | |
1022 | else { | |
1023 | last_to_win = result[0][0]; | |
1024 | last_streak = 1; | |
1025 | } | |
1026 | break; | |
1027 | ||
1028 | case '0': jigos++; break; | |
1029 | ||
1030 | default: | |
1031 | result_unknown++; | |
1032 | last_to_win = '0'; | |
1033 | } | |
1034 | } | |
1035 | else { | |
1036 | result_unknown++; | |
1037 | last_to_win = '0'; | |
1038 | } | |
1039 | ||
1040 | if (adjust_handicap && last_streak == adjust_handicap) { | |
1041 | if (result[0][0] == 'W') { | |
1042 | if (handicap_mode == "free" | |
1043 | || handicap < maximal_fixed_handicap(board_size)) { | |
1044 | if (++handicap == 1) | |
1045 | handicap = 2; | |
1046 | write("White wins too often. Increasing handicap to %d.\n", handicap); | |
1047 | } | |
1048 | } | |
1049 | else { | |
1050 | if (handicap == 0) { | |
1051 | handicap = 2; | |
1052 | if (handicap_mode == "fixed") | |
1053 | handicap = min(maximal_fixed_handicap(board_size), 2); | |
1054 | game->swap_engines(); | |
1055 | write("Black looks stronger than white. Swapping colors and setting handicap to %d.\n", | |
1056 | handicap); | |
1057 | } | |
1058 | else { | |
1059 | if (--handicap == 1) | |
1060 | handicap = 0; | |
1061 | write("Black wins too often. Decreasing handicap to %d.\n", | |
1062 | handicap); | |
1063 | } | |
1064 | } | |
1065 | ||
1066 | last_streak = 0; | |
1067 | } | |
1068 | ||
1069 | if (sizeof(result) == 2 && result[0] != "Void") | |
1070 | disagreed_games++; | |
1071 | } | |
1072 | ||
1073 | if (verbose) { | |
1074 | write("\n"); | |
1075 | for (int k = 0; k < num_games; k++) | |
1076 | write("Game %d: %s\n", skip_games + k + 1, results[k] * " "); | |
1077 | } | |
1078 | ||
1079 | write("\nTotal %d game(s).\nWhite won %d. Black won %d.", | |
1080 | num_games, white_wins, black_wins); | |
1081 | if (jigos) | |
1082 | write(" %d jigos.", jigos); | |
1083 | if (result_unknown) | |
1084 | write(" Results of %d game(s) are unknown.", result_unknown); | |
1085 | write("\n"); | |
1086 | if (disagreed_games) | |
1087 | write("Engines disagreed on results of %d game(s).\n", disagreed_games); | |
1088 | ||
1089 | game->print_time_report(); | |
1090 | game->print_statistic_totals(); | |
1091 | game->finalize(); | |
1092 | } | |
1093 | ||
1094 | ||
1095 | void | |
1096 | endgame_contest(GtpGame game, int endgame_moves, array(string) endgame_files, | |
1097 | int verbose, string|int sgf_base, int skip_games) | |
1098 | { | |
1099 | array(string) differences = ({}); | |
1100 | for (int k = skip_games; k < sizeof(endgame_files); k++) { | |
1101 | int load_up_to = game->init_endgame_contest(endgame_files[k], endgame_moves); | |
1102 | if (load_up_to) { | |
1103 | if (verbose) | |
1104 | werror("Replaying game `%s'.\n", endgame_files[k]); | |
1105 | ||
1106 | array(string) result1 | |
1107 | = game->play(sgf_base ? sprintf("%s%03d_1.sgf", sgf_base, k + 1) : 0); | |
1108 | game->print_statistics(); | |
1109 | game->reinit_endgame_contest(endgame_files[k], load_up_to); | |
1110 | ||
1111 | array(string) result2 | |
1112 | = game->play(sgf_base ? sprintf("%s%03d_2.sgf", sgf_base, k + 1) : 0); | |
1113 | game->print_statistics(); | |
1114 | game->swap_engines(); | |
1115 | ||
1116 | write("%s: ", endgame_files[k]); | |
1117 | if (sizeof(result1) > 1 || sizeof(result2) > 1) { | |
1118 | write("can't determine difference, engines disagreed on results.\n"); | |
1119 | write("\t%s\n", result1 * " "); | |
1120 | write("\t%s\n", result2 * " "); | |
1121 | differences += ({ "unknown" }); | |
1122 | } | |
1123 | else { | |
1124 | string difference = sprintf("%+.1f", (result_to_float(result1[0]) | |
1125 | - result_to_float(result2[0]))); | |
1126 | if (difference == "+0.0") { | |
1127 | write("same result: %s\n", result1[0]); | |
1128 | differences += ({ "0" }); | |
1129 | } | |
1130 | else { | |
1131 | write("%s %s; difference %s.\n", result1[0], result2[0], difference); | |
1132 | differences += ({ difference }); | |
1133 | } | |
1134 | } | |
1135 | } | |
1136 | } | |
1137 | ||
1138 | int white_wins = 0; | |
1139 | int black_wins = 0; | |
1140 | write("\n"); | |
1141 | foreach (differences, string difference) { | |
1142 | write(difference + "\n"); | |
1143 | if (difference[0] == '+') | |
1144 | white_wins++; | |
1145 | else if (difference[0] == '-') | |
1146 | black_wins++; | |
1147 | } | |
1148 | ||
1149 | write("\nTotal %d game(s) replayed. White won %d. Black won %d.\n", | |
1150 | sizeof(differences), white_wins, black_wins); | |
1151 | game->print_time_report(); | |
1152 | game->print_statistic_totals(); | |
1153 | game->finalize(); | |
1154 | } | |
1155 | ||
1156 | ||
1157 | string help_message = | |
1158 | "Usage: %s [OPTION]... [FILE]...\n\n" | |
1159 | "Runs either a match or endgame contest between two GTP engines.\n" | |
1160 | "`--white' and `--black' options are mandatory.\n\n" | |
1161 | "Options:\n" | |
1162 | " -w, --white=COMMAND_LINE\n" | |
1163 | " -b, --black=COMMAND_LINE command lines to run the two engines with.\n\n" | |
1164 | " -A, --arbiter=COMMAND_LINE command line to run arbiter--program that will\n" | |
1165 | " score disputed games--with.\n" | |
1166 | " --help display this help and exit.\n" | |
1167 | " --help-statistics display help on statistics options and exit.\n" | |
1168 | " -v, --verbose=LEVEL 1 - print moves, 2 and higher - draw boards.\n" | |
1169 | " --no-sgf do not create SGF game recods.\n" | |
1170 | " --sgf-base=FILENAME create SGF files with FILENAME as base (default\n" | |
1171 | " is `twogtp' or `endgame' depending on mode).\n" | |
1172 | " -m, --match runs a match between the engines (the default).\n" | |
1173 | " -e, --endgame=MOVES runs an endgame contest instead of a match.\n" | |
1174 | " -c, --continue continue a match or endgame contest.\n\n" | |
1175 | "Options valid only in match mode:\n" | |
1176 | " -g, --games=GAMES number of games in the match (one by default).\n" | |
1177 | " -s, --board-size=SIZE the board size for the match (default is 19).\n" | |
1178 | " -h, --handicap=STONES fixed handicap for the black player.\n" | |
1179 | " -f, --free-handicap=STONES free handicap for the black player.\n" | |
1180 | " -a, --adjust-handicap=LENGTH use simple adjusting scheme: change handicap\n" | |
1181 | " by 1 after LENGTH wins in a row.\n" | |
1182 | " -k, --komi=KOMI the komi to use.\n\n" | |
1183 | "Time control options:\n" | |
1184 | " -t, --main-time=TIME main time for a game (default is forever with\n" | |
1185 | " no byo-yomi or zero otherwise).\n" | |
1186 | " -B, --byo-yomi-time=TIME byo-yomi time for a game (zero by default).\n" | |
1187 | " TIMEs are in minutes and can be fractional.\n" | |
1188 | " -S, --byo-yomi-stones=STONES stones to be played in a byo-yomi period\n" | |
1189 | " (default is 25).\n\n" | |
1190 | "Default is no handicap. Komi defaults to 5.5 with no handicap or 0.5 with\n" | |
1191 | "nonzero handicap. Note that `--adjust-handicap' option not only can change\n" | |
1192 | "handicap, but can also swap engines' colors if black appears stronger.\n\n" | |
1193 | "FILEs are only used in endgame contest mode. They must be non-branched SGF\n" | |
1194 | "game records. In endgame contest mode the FILEs are loaded into the engines\n" | |
1195 | "excluding last non-pass MOVES specified with `--endgame' option. For each of\n" | |
1196 | "the FILEs two games are played with alternating colors and the difference in\n" | |
1197 | "results is determined.\n\n" | |
1198 | "Option `--continue' allows to have a continuous set of game records for\n" | |
1199 | "several script runs. It restarts a match or endgame contest skipping all\n" | |
1200 | "games for which game records already exist. In case of an endgame contest\n" | |
1201 | "it also skips appropriate number of FILEs.\n"; | |
1202 | ||
1203 | string help_statistics_message = | |
1204 | "Engine statistics options:\n" | |
1205 | " --statistics=COMMANDS\n" | |
1206 | " --statistics-white=COMMANDS\n" | |
1207 | " --statistics-black=COMMANDS COMMANDS is a semicolon separated list of GTP\n" | |
1208 | " commands to be executed after each game; if\n" | |
1209 | " engines' responses appear to be numeric, totals\n" | |
1210 | " are printed after all games are played.\n" | |
1211 | " --reset=COMMANDS\n" | |
1212 | " --reset-white=COMMANDS\n" | |
1213 | " --reset-black=COMMANDS semicolon separated list of GTP commands needed\n" | |
1214 | " to reset statistics before each game.\n\n" | |
1215 | " --totals-only don't print numeric statistics after each game.\n" | |
1216 | "Note that you can use `--statistics' and `--reset' options to acquire similar\n" | |
1217 | "statistics from both engines (provided they both understand the commands). If\n" | |
1218 | "you use color-specific options together with common options, both command lists\n" | |
1219 | "are used as one would expect.\n"; | |
1220 | ||
1221 | ||
1222 | int | |
1223 | main(int argc, array(string) argv) | |
1224 | { | |
1225 | string hint = sprintf("Try `%s --help' for more information.\n", | |
1226 | basename(argv[0])); | |
1227 | ||
1228 | if (Getopt.find_option(argv, UNDEFINED, "help")) { | |
1229 | write(help_message, basename(argv[0])); | |
1230 | return 0; | |
1231 | } | |
1232 | ||
1233 | if (Getopt.find_option(argv, UNDEFINED, "help-statistics")) { | |
1234 | write(help_statistics_message); | |
1235 | return 0; | |
1236 | } | |
1237 | ||
1238 | string white = Getopt.find_option(argv, "w", "white", UNDEFINED, ""); | |
1239 | if (white == "") { | |
1240 | werror("White player is not specified.\n" + hint); | |
1241 | return 1; | |
1242 | } | |
1243 | ||
1244 | string black = Getopt.find_option(argv, "b", "black", UNDEFINED, ""); | |
1245 | if (black == "") { | |
1246 | werror("Black player is not specified.\n" + hint); | |
1247 | return 1; | |
1248 | } | |
1249 | ||
1250 | string arbiter = Getopt.find_option(argv, "A", "arbiter", UNDEFINED, ""); | |
1251 | ||
1252 | int verbose = (int) Getopt.find_option(argv, "v", "verbose", | |
1253 | UNDEFINED, "0"); | |
1254 | Getopt.find_option(argv, "m", "match"); | |
1255 | int endgame_moves = (int) Getopt.find_option(argv, "e", "endgame", | |
1256 | UNDEFINED, "0"); | |
1257 | int mode = (endgame_moves > 0); | |
1258 | ||
1259 | string|int sgf_base = 0; | |
1260 | if (!Getopt.find_option(argv, UNDEFINED, "no-sgf")) { | |
1261 | sgf_base = Getopt.find_option(argv, UNDEFINED, "sgf-base", | |
1262 | UNDEFINED, mode ? "endgame" : "twogtp"); | |
1263 | } | |
1264 | else { | |
1265 | if (Getopt.find_option(argv, UNDEFINED, "sgf-base")) | |
1266 | werror("Warning: `--no-sgf' option specified, `--sgf-base' has no effect"); | |
1267 | } | |
1268 | ||
1269 | int skip_games = Getopt.find_option(argv, "c", "continue"); | |
1270 | if (skip_games) { | |
1271 | for (skip_games = 0; ; skip_games++) { | |
1272 | if (!Stdio.is_file(sprintf("%s%03d%s.sgf", sgf_base, skip_games + 1, | |
1273 | mode ? "_1" : ""))) | |
1274 | break; | |
1275 | } | |
1276 | } | |
1277 | ||
1278 | float main_time = (float) Getopt.find_option(argv, "t", "main-time", | |
1279 | UNDEFINED, "0") * 60.0; | |
1280 | float byo_yomi_time = (float) Getopt.find_option(argv, "B", "byo-yomi-time", | |
1281 | UNDEFINED, "0") * 60.0; | |
1282 | if (main_time == 0.0 && byo_yomi_time <= 0.0) | |
1283 | main_time = -1.0; | |
1284 | int byo_yomi_stones = (int) Getopt.find_option(argv, "S", "byo-yomi-stones", | |
1285 | UNDEFINED, "25"); | |
1286 | byo_yomi_stones = max(byo_yomi_stones, 1); | |
1287 | ||
1288 | string|int statistics_value | |
1289 | = Getopt.find_option(argv, UNDEFINED, "statistics", UNDEFINED, ""); | |
1290 | string|int statistics_white_value | |
1291 | = Getopt.find_option(argv, UNDEFINED, "statistics-white", UNDEFINED, ""); | |
1292 | string|int statistics_black_value | |
1293 | = Getopt.find_option(argv, UNDEFINED, "statistics-black", UNDEFINED, ""); | |
1294 | ||
1295 | array(string) statistics_white = ({}); | |
1296 | array(string) statistics_black = ({}); | |
1297 | ||
1298 | if (statistics_value && statistics_value != "") { | |
1299 | statistics_white = map(statistics_value / ";", String.trim_all_whites); | |
1300 | statistics_black = map(statistics_value / ";", String.trim_all_whites); | |
1301 | } | |
1302 | ||
1303 | if (statistics_white_value && statistics_white_value != "") { | |
1304 | statistics_white |= map(statistics_white_value / ";", | |
1305 | String.trim_all_whites); | |
1306 | } | |
1307 | ||
1308 | if (statistics_black_value && statistics_black_value != "") { | |
1309 | statistics_black |= map(statistics_black_value / ";", | |
1310 | String.trim_all_whites); | |
1311 | } | |
1312 | ||
1313 | string|int reset_value | |
1314 | = Getopt.find_option(argv, UNDEFINED, "reset", UNDEFINED, ""); | |
1315 | string|int reset_white_value | |
1316 | = Getopt.find_option(argv, UNDEFINED, "reset-white", UNDEFINED, ""); | |
1317 | string|int reset_black_value | |
1318 | = Getopt.find_option(argv, UNDEFINED, "reset-black", UNDEFINED, ""); | |
1319 | ||
1320 | array(string) reset_white = ({}); | |
1321 | array(string) reset_black = ({}); | |
1322 | ||
1323 | if (reset_value && reset_value != "") { | |
1324 | reset_white = map(reset_value / ";", String.trim_all_whites); | |
1325 | reset_black = map(reset_value / ";", String.trim_all_whites); | |
1326 | } | |
1327 | ||
1328 | if (reset_white_value && reset_white_value != "") | |
1329 | reset_white |= map(reset_white_value / ";", String.trim_all_whites); | |
1330 | ||
1331 | if (reset_black_value && reset_black_value != "") | |
1332 | reset_black |= map(reset_black_value / ";", String.trim_all_whites); | |
1333 | ||
1334 | int totals_only = (Getopt.find_option(argv, UNDEFINED, "totals-only") != 0); | |
1335 | ||
1336 | if (!mode) { | |
1337 | string handicap_mode = "fixed"; | |
1338 | int handicap = 0; | |
1339 | ||
1340 | int games = (int) Getopt.find_option(argv, "g", "games", UNDEFINED, "1"); | |
1341 | games = max(games, 1); | |
1342 | int board_size = (int) Getopt.find_option(argv, "s", "board-size", | |
1343 | UNDEFINED, "19"); | |
1344 | if (board_size < 1 || 25 < board_size) { | |
1345 | werror("GTP only supports boards with size from 1 to 25.\n"); | |
1346 | return 1; | |
1347 | } | |
1348 | ||
1349 | int fixed_handicap = (int) Getopt.find_option(argv, "h", "handicap", | |
1350 | UNDEFINED, "-1"); | |
1351 | ||
1352 | int free_handicap = (int) Getopt.find_option(argv, "f", "free-handicap", | |
1353 | UNDEFINED, "-1"); | |
1354 | ||
1355 | if (fixed_handicap >= 0 && free_handicap >= 0) { | |
1356 | werror("Fixed and free handicaps are mutually exclusive.\n" + hint); | |
1357 | return 1; | |
1358 | } | |
1359 | ||
1360 | if (fixed_handicap >= 0) { | |
1361 | int maximum = maximal_fixed_handicap(board_size); | |
1362 | if (fixed_handicap > maximum) { | |
1363 | write("Maximal allowed handicap for board size %d is %d.\n", | |
1364 | board_size, maximum); | |
1365 | return 1; | |
1366 | } | |
1367 | ||
1368 | handicap = fixed_handicap; | |
1369 | handicap_mode = "fixed"; | |
1370 | } | |
1371 | else if (free_handicap >= 0) { | |
1372 | handicap = free_handicap; | |
1373 | handicap_mode = "free"; | |
1374 | } | |
1375 | ||
1376 | if (handicap == 1) { | |
1377 | werror("Warning: handicap 1 is not allowed, falling back on handicap 0.\n"); | |
1378 | handicap = 0; | |
1379 | } | |
1380 | ||
1381 | int adjust_handicap = (int) Getopt.find_option(argv, "a", "adjust-handicap", | |
1382 | UNDEFINED, "0"); | |
1383 | adjust_handicap = max(adjust_handicap, 0); | |
1384 | ||
1385 | float komi = handicap ? 0.5 : 5.5; | |
1386 | komi = (float) Getopt.find_option(argv, "k", "komi", | |
1387 | UNDEFINED, (string) komi); | |
1388 | ||
1389 | if (sizeof(Getopt.get_args(argv)) != 1) { | |
1390 | werror("Unrecognized input in command line.\n" + hint); | |
1391 | return 1; | |
1392 | } | |
1393 | ||
1394 | ||
1395 | GtpGame game = GtpGame(white, black, arbiter, | |
1396 | statistics_white, statistics_black, | |
1397 | reset_white, reset_black, totals_only, | |
1398 | verbose, main_time, byo_yomi_time, byo_yomi_stones); | |
1399 | if (game) { | |
1400 | run_twogtp_match(game, games, board_size, handicap, handicap_mode, | |
1401 | adjust_handicap, komi, verbose, sgf_base, skip_games); | |
1402 | } | |
1403 | } | |
1404 | else { | |
1405 | array(string) endgame_files = Getopt.get_args(argv)[1..]; | |
1406 | if (sizeof(endgame_files) == 0) { | |
1407 | werror("No SGF files specified for endgame contest.\n" + hint); | |
1408 | return 1; | |
1409 | } | |
1410 | ||
1411 | GtpGame game = GtpGame(white, black, arbiter, | |
1412 | statistics_white, statistics_black, | |
1413 | reset_white, reset_black, totals_only, | |
1414 | verbose, main_time, byo_yomi_time, byo_yomi_stones); | |
1415 | if (game) { | |
1416 | endgame_contest(game, endgame_moves, endgame_files, | |
1417 | verbose, sgf_base, skip_games); | |
1418 | } | |
1419 | } | |
1420 | } |