3 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
4 # This program is distributed with GNU Go, a Go program. #
6 # Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
7 # for more information. #
9 # Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 and 2007 #
10 # by the Free Software Foundation. #
12 # This program is free software; you can redistribute it and/or #
13 # modify it under the terms of the GNU General Public License #
14 # as published by the Free Software Foundation - version 3, #
15 # or (at your option) any later version. #
17 # This program is distributed in the hope that it will be #
18 # useful, but WITHOUT ANY WARRANTY; without even the implied #
19 # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
20 # PURPOSE. See the GNU General Public License in file COPYING #
23 # You should have received a copy of the GNU General Public #
24 # License along with this program; if not, write to the Free #
25 # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
26 # Boston, MA 02111, USA. #
27 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
39 def coords_to_sgf(size
, board_coords
):
42 board_coords
= string
.lower(board_coords
)
43 if board_coords
== "pass":
46 print "Coords: <" + board_coords
+ ">"
47 letter
= board_coords
[0]
48 digits
= board_coords
[1:]
50 sgffirst
= chr(ord(letter
) - 1)
53 sgfsecond
= chr(ord("a") + int(size
) - int(digits
))
54 return sgffirst
+ sgfsecond
62 # outfile File to write to
63 # infile File to read from
65 def __init__(self
, command
):
67 infile
, outfile
= popen2
.popen2(command
)
72 self
.outfile
= outfile
74 def exec_cmd(self
, cmd
):
78 sys
.stderr
.write("GTP command: " + cmd
+ "\n")
79 self
.outfile
.write(cmd
+ "\n\n")
82 line
= self
.infile
.readline()
84 result
= result
+ line
85 line
= self
.infile
.readline()
87 sys
.stderr
.write("Reply: " + line
+ "\n")
89 # Remove trailing newline from the result
90 if result
[-1] == "\n":
94 return "ERROR: len = 0"
95 if (result
[0] == "?"):
96 return "ERROR: GTP Command failed: " + result
[2:]
97 if (result
[0] == "="):
99 return "ERROR: Unrecognized answer: " + result
105 # connection GTP_connection
107 def __init__(self
, command
):
108 self
.connection
= GTP_connection(command
)
109 protocol_version
= self
.connection
.exec_cmd("protocol_version")
110 if protocol_version
[:5] != "ERROR":
111 self
.protocol_version
= protocol_version
113 self
.protocol_version
= "1"
115 def is_known_command(self
, command
):
116 return self
.connection
.exec_cmd("known_command " + command
) == "true"
118 def genmove(self
, color
):
119 if color
[0] in ["b", "B"]:
121 elif color
[0] in ["w", "W"]:
123 if self
.protocol_version
== "1":
124 command
= "genmove_" + command
126 command
= "genmove " + command
128 return self
.connection
.exec_cmd(command
)
130 def black(self
, move
):
131 if self
.protocol_version
== "1":
132 self
.connection
.exec_cmd("black " + move
)
134 self
.connection
.exec_cmd("play black " + move
)
136 def white(self
, move
):
137 if self
.protocol_version
== "1":
138 self
.connection
.exec_cmd("white " + move
)
140 self
.connection
.exec_cmd("play white " + move
)
142 def komi(self
, komi
):
143 self
.connection
.exec_cmd("komi " + komi
)
145 def boardsize(self
, size
):
146 self
.connection
.exec_cmd("boardsize " + size
)
147 if self
.protocol_version
!= "1":
148 self
.connection
.exec_cmd("clear_board")
150 def handicap(self
, handicap
, handicap_type
):
151 if handicap_type
== "fixed":
152 result
= self
.connection
.exec_cmd("fixed_handicap %d" % (handicap
))
154 result
= self
.connection
.exec_cmd("place_free_handicap %d"
157 return string
.split(result
, " ")
159 def loadsgf(self
, endgamefile
, move_number
):
160 self
.connection
.exec_cmd(string
.join(["loadsgf", endgamefile
,
163 def list_stones(self
, color
):
164 return string
.split(self
.connection
.exec_cmd("list_stones " + color
), " ")
167 return self
.connection
.exec_cmd("quit")
170 board
= self
.connection
.exec_cmd("showboard")
171 if board
and (board
[0] == "\n"):
175 def get_random_seed(self
):
176 result
= self
.connection
.exec_cmd("get_random_seed")
177 if result
[:5] == "ERROR":
181 def set_random_seed(self
, seed
):
182 self
.connection
.exec_cmd("set_random_seed " + seed
)
184 def get_program_name(self
):
185 return self
.connection
.exec_cmd("name") + " " + \
186 self
.connection
.exec_cmd("version")
188 def final_score(self
):
189 return self
.connection
.exec_cmd("final_score")
192 return self
.final_score(self
)
195 if (self
.is_known_command("cputime")):
196 return self
.connection
.exec_cmd("cputime")
204 # whiteplayer GTP_player
205 # blackplayer GTP_player
209 # handicap_type string
210 # handicap_stones int
211 # moves list of string
215 def __init__(self
, whitecommand
, blackcommand
, size
, komi
, handicap
,
216 handicap_type
, endgamefile
):
217 self
.whiteplayer
= GTP_player(whitecommand
)
218 self
.blackplayer
= GTP_player(blackcommand
)
221 self
.handicap
= handicap
222 self
.handicap_type
= handicap_type
223 self
.endgamefile
= endgamefile
224 self
.sgffilestart
= ""
225 if endgamefile
!= "":
226 self
.init_endgame_contest_game()
228 self
.sgffilestart
= ""
230 def init_endgame_contest_game(self
):
231 infile
= open(self
.endgamefile
)
233 print "Couldn't read " + self
.endgamefile
235 sgflines
= infile
.readlines()
237 size
= re
.compile("SZ\[[0-9]+\]")
238 move
= re
.compile(";[BW]\[[a-z]{0,2}\]")
240 for line
in sgflines
:
241 match
= size
.search(line
)
243 self
.size
= match
.group()[3:-1]
244 match
= move
.search(line
)
246 sgf_start
.append("A" + match
.group()[1:])
247 line
= line
[match
.end():]
248 match
= move
.search(line
)
249 self
.endgame_start
= len(sgf_start
) - endgame_start_at
250 self
.sgffilestart
= ";" + string
.join(
251 sgf_start
[:self
.endgame_start
-1], "") + "\n"
252 if self
.endgame_start
% 2 == 0:
253 self
.first_to_play
= "W"
255 self
.first_to_play
= "B"
257 def get_position_from_engine(self
, engine
):
258 black_stones
= engine
.list_stones("black")
259 white_stones
= engine
.list_stones("white")
260 self
.sgffilestart
= ";"
261 if len(black_stones
) > 0:
262 self
.sgffilestart
+= "AB"
263 for stone
in black_stones
:
264 self
.sgffilestart
+= "[%s]" % coords_to_sgf(self
.size
, stone
)
265 self
.sgffilestart
+= "\n"
266 if len(white_stones
) > 0:
267 self
.sgffilestart
+= "AW"
268 for stone
in white_stones
:
269 self
.sgffilestart
+= "[%s]" % coords_to_sgf(self
.size
, stone
)
270 self
.sgffilestart
+= "\n"
272 def writesgf(self
, sgffilename
):
273 "Write the game to an SGF file after a game"
276 outfile
= open(sgffilename
, "w")
278 print "Couldn't create " + sgffilename
280 black_name
= self
.blackplayer
.get_program_name()
281 white_name
= self
.whiteplayer
.get_program_name()
282 black_seed
= self
.blackplayer
.get_random_seed()
283 white_seed
= self
.whiteplayer
.get_random_seed()
284 handicap
= self
.handicap
286 result
= self
.resultw
288 outfile
.write("(;GM[1]FF[4]RU[Japanese]SZ[%s]HA[%s]KM[%s]RE[%s]\n" %
289 (size
, handicap
, komi
, result
))
290 outfile
.write("PW[%s (random seed %s)]PB[%s (random seed %s)]\n" %
291 (white_name
, white_seed
, black_name
, black_seed
))
292 outfile
.write(self
.sgffilestart
)
296 for stone
in self
.handicap_stones
:
297 outfile
.write("[%s]" %(coords_to_sgf(size
, stone
)))
298 outfile
.write("PL[W]\n")
300 to_play
= self
.first_to_play
302 for move
in self
.moves
:
303 sgfmove
= coords_to_sgf(size
, move
)
304 outfile
.write(";%s[%s]\n" % (to_play
, sgfmove
))
312 def set_handicap(self
, handicap
):
313 self
.handicap
= handicap
315 def swap_players(self
):
316 temp
= self
.whiteplayer
317 self
.whiteplayer
= self
.blackplayer
318 self
.blackplayer
= temp
320 def play(self
, sgffile
):
325 print "Setting boardsize and komi for black\n"
326 self
.blackplayer
.boardsize(self
.size
)
327 self
.blackplayer
.komi(self
.komi
)
330 print "Setting boardsize and komi for white\n"
331 self
.whiteplayer
.boardsize(self
.size
)
332 self
.whiteplayer
.komi(self
.komi
)
334 self
.handicap_stones
= []
336 if self
.endgamefile
== "":
337 if self
.handicap
< 2:
338 self
.first_to_play
= "B"
340 self
.handicap_stones
= self
.blackplayer
.handicap(self
.handicap
, self
.handicap_type
)
341 for stone
in self
.handicap_stones
:
342 self
.whiteplayer
.black(stone
)
343 self
.first_to_play
= "W"
345 self
.blackplayer
.loadsgf(self
.endgamefile
, self
.endgame_start
)
346 self
.blackplayer
.set_random_seed("0")
347 self
.whiteplayer
.loadsgf(self
.endgamefile
, self
.endgame_start
)
348 self
.whiteplayer
.set_random_seed("0")
349 if self
.blackplayer
.is_known_command("list_stones"):
350 self
.get_position_from_engine(self
.blackplayer
)
351 elif self
.whiteplayer
.is_known_command("list_stones"):
352 self
.get_position_from_engine(self
.whiteplayer
)
354 to_play
= self
.first_to_play
358 won_by_resignation
= ""
361 move
= self
.blackplayer
.genmove("black")
363 if move
[:5] == "ERROR":
367 if move
[:6] == "resign":
369 print "Black resigns"
370 won_by_resignation
= "W+Resign"
373 self
.moves
.append(move
)
374 if string
.lower(move
[:4]) == "pass":
380 self
.whiteplayer
.black(move
)
382 print "Black plays " + move
385 move
= self
.whiteplayer
.genmove("white")
386 if move
[:5] == "ERROR":
390 if move
[:6] == "resign":
392 print "White resigns"
393 won_by_resignation
= "B+Resign"
396 self
.moves
.append(move
)
397 if string
.lower(move
[:4]) == "pass":
403 self
.blackplayer
.white(move
)
405 print "White plays " + move
409 print self
.whiteplayer
.showboard() + "\n"
411 if won_by_resignation
== "":
412 self
.resultw
= self
.whiteplayer
.final_score()
413 self
.resultb
= self
.blackplayer
.final_score()
415 self
.resultw
= won_by_resignation
;
416 self
.resultb
= won_by_resignation
;
417 # if self.resultb == self.resultw:
418 # print "Result: ", self.resultw
420 # print "Result according to W: ", self.resultw
421 # print "Result according to B: ", self.resultb
422 # FIXME: $self->writesgf($sgffile) if defined $sgffile;
424 self
.writesgf(sgffile
)
427 return (self
.resultw
, self
.resultb
)
431 cputime
["white"] = self
.whiteplayer
.cputime()
432 cputime
["black"] = self
.blackplayer
.cputime()
436 self
.blackplayer
.quit()
437 self
.whiteplayer
.quit()
450 def __init__(self
, whitecommand
, blackcommand
, size
, komi
, handicap
,
451 handicap_type
, streak_length
, endgamefilelist
):
452 self
.white
= whitecommand
453 self
.black
= blackcommand
456 self
.handicap
= handicap
457 self
.handicap_type
= handicap_type
458 self
.streak_length
= streak_length
459 self
.endgamefilelist
= endgamefilelist
461 def endgame_contest(self
, sgfbase
):
464 for endgamefile
in self
.endgamefilelist
:
465 game1
= GTP_game(self
.white
, self
.black
, self
.size
, self
.komi
,
467 game2
= GTP_game(self
.black
, self
.white
, self
.size
, self
.komi
,
470 print "Replaying", endgamefile
471 print "Black:", self
.black
472 print "White:", self
.white
474 result1
= game1
.result()[0]
476 plain_result1
= re
.search(r
"([BW]\+)([0-9]*\.[0-9]*)", result1
)
477 result1_float
= float(plain_result1
.group(2))
479 plain_result1
= re
.search(r
"(0)", "0")
481 if result1
[0] == "B":
484 print "Result:", result1
485 print "Replaying", endgamefile
486 print "Black:", self
.white
487 print "White:", self
.black
489 result2
= game2
.result()[1]
491 print "Result:", result2
493 plain_result2
= re
.search(r
"([BW]\+)([0-9]*\.[0-9]*)", result2
)
494 result2_float
= float(plain_result2
.group(2))
496 plain_result2
= re
.search(r
"(0)", "0")
499 if result2
[0] == "B":
501 results
.append(result1_float
- result2_float
)
502 if (result1
!= result2
):
503 print endgamefile
+ ":", plain_result1
.group(), \
504 plain_result2
.group(), "Difference:",
505 print result1_float
- result2_float
507 print endgamefile
+": Same result:", plain_result1
.group()
508 sgffilename
= "%s%03d" % (sgfbase
, i
)
509 game1
.writesgf(sgffilename
+ "_1.sgf")
510 game2
.writesgf(sgffilename
+ "_2.sgf")
516 def play(self
, games
, sgfbase
):
519 game
= GTP_game(self
.white
, self
.black
,
520 self
.size
, self
.komi
, self
.handicap
,
521 self
.handicap_type
, "")
523 for i
in range(games
):
524 sgffilename
= "%s%03d.sgf" % (sgfbase
, i
+ 1)
525 game
.play(sgffilename
)
526 result
= game
.result()
527 if result
[0] == result
[1]:
528 print "Game %d: %s" % (i
+ 1, result
[0])
530 print "Game %d: %s %s" % (i
+ 1, result
[0], result
[1])
532 if result
[0][0] == last_color
:
534 elif result
[0][0] != "0":
535 last_color
= result
[0][0]
538 if last_streak
== self
.streak_length
:
539 if last_color
== "W":
541 if self
.handicap
== 1:
543 print "White wins too often. Increasing handicap to %d" \
546 if self
.handicap
> 0:
548 if self
.handicap
== 1:
550 print "Black wins too often. Decreasing handicap to %d" \
555 print "Black looks stronger than white. Swapping colors and setting handicap to 2"
556 game
.set_handicap(self
.handicap
)
559 results
.append(result
)
560 cputime
= game
.cputime()
562 return results
, cputime
565 # ================================================================
578 handicap_type
= "fixed"
591 twogtp --white \'<path to program 1> --mode gtp [program options]\' \\
592 --black \'<path to program 2> --mode gtp [program options]\' \\
595 Possible twogtp options:
597 --verbose 1 (to list moves) or --verbose 2 (to draw board)
600 --free-handicap <amount>
601 --adjust-handicap <length> (change handicap by 1 after <length> wins
603 --size <board size> (default 19)
604 --games <number of games to play> (default 1)
605 --sgfbase <filename> (create sgf files with sgfbase as basename)
606 --endgame <moves before end> (endgame contest - add filenames of
607 games to be replayed after last option)
615 (opts
, params
) = getopt(sys
.argv
[1:], "",
632 for opt
, value
in opts
:
635 elif opt
== "--white":
637 elif opt
== "--verbose":
639 elif opt
== "--komi":
641 elif opt
== "--boardsize" or opt
== "--size":
643 elif opt
== "--handicap":
644 handicap
= int(value
)
645 handicap_type
= "fixed"
646 elif opt
== "--free-handicap":
647 handicap
= int(value
)
648 handicap_type
= "free"
649 elif opt
== "--adjust-handicap":
650 streak_length
= int(value
)
651 elif opt
== "--games":
653 elif opt
== "--sgfbase":
655 elif opt
== "--endgame":
656 endgame_start_at
= int(value
)
658 if endgame_start_at
!= 0:
659 endgame_filelist
= params
661 endgame_filelist
= []
666 if black
== "" or white
== "":
670 if handicap
> 1 and streak_length
== -1:
675 match
= GTP_match(white
, black
, size
, komi
, handicap
, handicap_type
,
676 streak_length
, endgame_filelist
)
677 if endgame_filelist
!= []:
678 results
= match
.endgame_contest(sgfbase
)
687 print "%d games, %d wins for black, %d wins for white." \
688 % (len(results
), win_black
, win_white
)
691 results
, cputimes
= match
.play(games
, sgfbase
)
694 for resw
, resb
in results
:
697 print "Game %d: %s" % (i
, resw
)
699 print "Game %d: %s %s" % (i
, resb
, resw
)
700 if (cputimes
["white"] != "0"):
701 print "White: %ss CPU time" % cputimes
["white"]
702 if (cputimes
["black"] != "0"):
703 print "Black: %ss CPU time" % cputimes
["black"]