PageRenderTime 23ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/pygo1963/network_game/twogtp.py

https://code.google.com/p/pygo1963/
Python | 678 lines | 645 code | 7 blank | 26 comment | 7 complexity | 98b0b26bd27558908ba08e5563a8baa3 MD5 | raw file
  1. #! /usr/bin/env python
  2. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  3. # This program is distributed with GNU Go, a Go program. #
  4. # #
  5. # Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ #
  6. # for more information. #
  7. # #
  8. # Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 and 2007 #
  9. # by the Free Software Foundation. #
  10. # #
  11. # This program is free software; you can redistribute it and/or #
  12. # modify it under the terms of the GNU General Public License #
  13. # as published by the Free Software Foundation - version 3, #
  14. # or (at your option) any later version. #
  15. # #
  16. # This program is distributed in the hope that it will be #
  17. # useful, but WITHOUT ANY WARRANTY; without even the implied #
  18. # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR #
  19. # PURPOSE. See the GNU General Public License in file COPYING #
  20. # for more details. #
  21. # #
  22. # You should have received a copy of the GNU General Public #
  23. # License along with this program; if not, write to the Free #
  24. # Software Foundation, Inc., 51 Franklin Street, Fifth Floor, #
  25. # Boston, MA 02111, USA. #
  26. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
  27. from getopt import *
  28. import popen2
  29. import sys
  30. import string
  31. import re
  32. debug = 0
  33. def coords_to_sgf(size, board_coords):
  34. global debug
  35. board_coords = string.lower(board_coords)
  36. if board_coords == "pass":
  37. return ""
  38. if debug:
  39. print "Coords: <" + board_coords + ">"
  40. letter = board_coords[0]
  41. digits = board_coords[1:]
  42. if letter > "i":
  43. sgffirst = chr(ord(letter) - 1)
  44. else:
  45. sgffirst = letter
  46. sgfsecond = chr(ord("a") + int(size) - int(digits))
  47. return sgffirst + sgfsecond
  48. class GTP_connection:
  49. #
  50. # Class members:
  51. # outfile File to write to
  52. # infile File to read from
  53. def __init__(self, command):
  54. try:
  55. infile, outfile = popen2.popen2(command)
  56. except:
  57. print "popen2 failed"
  58. sys.exit(1)
  59. self.infile = infile
  60. self.outfile = outfile
  61. def exec_cmd(self, cmd):
  62. global debug
  63. if debug:
  64. sys.stderr.write("GTP command: " + cmd + "\n")
  65. self.outfile.write(cmd + "\n\n")
  66. self.outfile.flush()
  67. result = ""
  68. line = self.infile.readline()
  69. while line != "\n":
  70. result = result + line
  71. line = self.infile.readline()
  72. if debug:
  73. sys.stderr.write("Reply: " + line + "\n")
  74. # Remove trailing newline from the result
  75. if result[-1] == "\n":
  76. result = result[:-1]
  77. if len(result) == 0:
  78. return "ERROR: len = 0"
  79. if (result[0] == "?"):
  80. return "ERROR: GTP Command failed: " + result[2:]
  81. if (result[0] == "="):
  82. return result[2:]
  83. return "ERROR: Unrecognized answer: " + result
  84. class GTP_player:
  85. # Class members:
  86. # connection GTP_connection
  87. def __init__(self, command):
  88. self.connection = GTP_connection(command)
  89. protocol_version = self.connection.exec_cmd("protocol_version")
  90. if protocol_version[:5] != "ERROR":
  91. self.protocol_version = protocol_version
  92. else:
  93. self.protocol_version = "1"
  94. def is_known_command(self, command):
  95. return self.connection.exec_cmd("known_command " + command) == "true"
  96. def genmove(self, color):
  97. if color[0] in ["b", "B"]:
  98. command = "black"
  99. elif color[0] in ["w", "W"]:
  100. command = "white"
  101. if self.protocol_version == "1":
  102. command = "genmove_" + command
  103. else:
  104. command = "genmove " + command
  105. return self.connection.exec_cmd(command)
  106. def black(self, move):
  107. if self.protocol_version == "1":
  108. self.connection.exec_cmd("black " + move)
  109. else:
  110. self.connection.exec_cmd("play black " + move)
  111. def white(self, move):
  112. if self.protocol_version == "1":
  113. self.connection.exec_cmd("white " + move)
  114. else:
  115. self.connection.exec_cmd("play white " + move)
  116. def komi(self, komi):
  117. self.connection.exec_cmd("komi " + komi)
  118. def boardsize(self, size):
  119. self.connection.exec_cmd("boardsize " + size)
  120. if self.protocol_version != "1":
  121. self.connection.exec_cmd("clear_board")
  122. def handicap(self, handicap, handicap_type):
  123. if handicap_type == "fixed":
  124. result = self.connection.exec_cmd("fixed_handicap %d" % (handicap))
  125. else:
  126. result = self.connection.exec_cmd("place_free_handicap %d"
  127. % (handicap))
  128. return string.split(result, " ")
  129. def loadsgf(self, endgamefile, move_number):
  130. self.connection.exec_cmd(string.join(["loadsgf", endgamefile,
  131. str(move_number)]))
  132. def list_stones(self, color):
  133. return string.split(self.connection.exec_cmd("list_stones " + color), " ")
  134. def quit(self):
  135. return self.connection.exec_cmd("quit")
  136. def showboard(self):
  137. board = self.connection.exec_cmd("showboard")
  138. if board and (board[0] == "\n"):
  139. board = board[1:]
  140. return board
  141. def get_random_seed(self):
  142. result = self.connection.exec_cmd("get_random_seed")
  143. if result[:5] == "ERROR":
  144. return "unknown"
  145. return result
  146. def set_random_seed(self, seed):
  147. self.connection.exec_cmd("set_random_seed " + seed)
  148. def get_program_name(self):
  149. return self.connection.exec_cmd("name") + " " + \
  150. self.connection.exec_cmd("version")
  151. def final_score(self):
  152. return self.connection.exec_cmd("final_score")
  153. def score(self):
  154. return self.final_score(self)
  155. def cputime(self):
  156. if (self.is_known_command("cputime")):
  157. return self.connection.exec_cmd("cputime")
  158. else:
  159. return "0"
  160. def captures(self, color):
  161. result = self.connection.exec_cmd("captures " + color)
  162. return result
  163. class GTP_game:
  164. # Class members:
  165. # whiteplayer GTP_player
  166. # blackplayer GTP_player
  167. # size int
  168. # komi float
  169. # handicap int
  170. # handicap_type string
  171. # handicap_stones int
  172. # moves list of string
  173. # resultw
  174. # resultb
  175. def __init__(self, whitecommand, blackcommand, size, komi, handicap,
  176. handicap_type, endgamefile):
  177. self.whiteplayer = GTP_player(whitecommand)
  178. self.blackplayer = GTP_player(blackcommand)
  179. self.size = size
  180. self.komi = komi
  181. self.handicap = handicap
  182. self.handicap_type = handicap_type
  183. self.endgamefile = endgamefile
  184. self.sgffilestart = ""
  185. if endgamefile != "":
  186. self.init_endgame_contest_game()
  187. else:
  188. self.sgffilestart = ""
  189. def init_endgame_contest_game(self):
  190. infile = open(self.endgamefile)
  191. if not infile:
  192. print "Couldn't read " + self.endgamefile
  193. sys.exit(2)
  194. sgflines = infile.readlines()
  195. infile.close
  196. size = re.compile("SZ\[[0-9]+\]")
  197. move = re.compile(";[BW]\[[a-z]{0,2}\]")
  198. sgf_start = []
  199. for line in sgflines:
  200. match = size.search(line)
  201. if match:
  202. self.size = match.group()[3:-1]
  203. match = move.search(line)
  204. while match:
  205. sgf_start.append("A" + match.group()[1:])
  206. line = line[match.end():]
  207. match = move.search(line)
  208. self.endgame_start = len(sgf_start) - endgame_start_at
  209. self.sgffilestart = ";" + string.join(
  210. sgf_start[:self.endgame_start-1], "") + "\n"
  211. if self.endgame_start % 2 == 0:
  212. self.first_to_play = "W"
  213. else:
  214. self.first_to_play = "B"
  215. def get_position_from_engine(self, engine):
  216. black_stones = engine.list_stones("black")
  217. white_stones = engine.list_stones("white")
  218. self.sgffilestart = ";"
  219. if len(black_stones) > 0:
  220. self.sgffilestart += "AB"
  221. for stone in black_stones:
  222. self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone)
  223. self.sgffilestart += "\n"
  224. if len(white_stones) > 0:
  225. self.sgffilestart += "AW"
  226. for stone in white_stones:
  227. self.sgffilestart += "[%s]" % coords_to_sgf(self.size, stone)
  228. self.sgffilestart += "\n"
  229. def writesgf(self, sgffilename):
  230. "Write the game to an SGF file after a game"
  231. size = self.size
  232. outfile = open(sgffilename, "w")
  233. if not outfile:
  234. print "Couldn't create " + sgffilename
  235. return
  236. black_name = self.blackplayer.get_program_name()
  237. white_name = self.whiteplayer.get_program_name()
  238. black_seed = self.blackplayer.get_random_seed()
  239. white_seed = self.whiteplayer.get_random_seed()
  240. handicap = self.handicap
  241. komi = self.komi
  242. result = self.resultw
  243. outfile.write("(;GM[1]FF[4]RU[Japanese]SZ[%s]HA[%s]KM[%s]RE[%s]\n" %
  244. (size, handicap, komi, result))
  245. outfile.write("PW[%s (random seed %s)]PB[%s (random seed %s)]\n" %
  246. (white_name, white_seed, black_name, black_seed))
  247. outfile.write(self.sgffilestart)
  248. if handicap > 1:
  249. outfile.write("AB");
  250. for stone in self.handicap_stones:
  251. outfile.write("[%s]" %(coords_to_sgf(size, stone)))
  252. outfile.write("PL[W]\n")
  253. to_play = self.first_to_play
  254. for move in self.moves:
  255. sgfmove = coords_to_sgf(size, move)
  256. outfile.write(";%s[%s]\n" % (to_play, sgfmove))
  257. if to_play == "B":
  258. to_play = "W"
  259. else:
  260. to_play = "B"
  261. outfile.write(")\n")
  262. outfile.close
  263. def set_handicap(self, handicap):
  264. self.handicap = handicap
  265. def swap_players(self):
  266. temp = self.whiteplayer
  267. self.whiteplayer = self.blackplayer
  268. self.blackplayer = temp
  269. def play(self, sgffile):
  270. "Play a game"
  271. global verbose
  272. if verbose >= 1:
  273. print "Setting boardsize and komi for black\n"
  274. self.blackplayer.boardsize(self.size)
  275. self.blackplayer.komi(self.komi)
  276. if verbose >= 1:
  277. print "Setting boardsize and komi for white\n"
  278. self.whiteplayer.boardsize(self.size)
  279. self.whiteplayer.komi(self.komi)
  280. self.handicap_stones = []
  281. if self.endgamefile == "":
  282. if self.handicap < 2:
  283. self.first_to_play = "B"
  284. else:
  285. self.handicap_stones = self.blackplayer.handicap(self.handicap, self.handicap_type)
  286. for stone in self.handicap_stones:
  287. self.whiteplayer.black(stone)
  288. self.first_to_play = "W"
  289. else:
  290. self.blackplayer.loadsgf(self.endgamefile, self.endgame_start)
  291. self.blackplayer.set_random_seed("0")
  292. self.whiteplayer.loadsgf(self.endgamefile, self.endgame_start)
  293. self.whiteplayer.set_random_seed("0")
  294. if self.blackplayer.is_known_command("list_stones"):
  295. self.get_position_from_engine(self.blackplayer)
  296. elif self.whiteplayer.is_known_command("list_stones"):
  297. self.get_position_from_engine(self.whiteplayer)
  298. to_play = self.first_to_play
  299. self.moves = []
  300. passes = 0
  301. won_by_resignation = ""
  302. while not self.is_game_finished():
  303. if to_play == "B":
  304. move = self.blackplayer.genmove("black")
  305. if move[:5] == "ERROR":
  306. # FIXME: write_sgf
  307. sys.exit(1)
  308. if move[:6] == "resign":
  309. if verbose >= 1:
  310. print "Black resigns"
  311. won_by_resignation = "W+Resign"
  312. break
  313. else:
  314. self.moves.append(move)
  315. if string.lower(move[:4]) == "pass":
  316. passes = passes + 1
  317. if verbose >= 1:
  318. print "Black passes"
  319. else:
  320. passes = 0
  321. self.whiteplayer.black(move)
  322. if verbose >= 1:
  323. print "Black plays " + move
  324. to_play = "W"
  325. else:
  326. move = self.whiteplayer.genmove("white")
  327. if move[:5] == "ERROR":
  328. # FIXME: write_sgf
  329. sys.exit(1)
  330. if move[:6] == "resign":
  331. if verbose >= 1:
  332. print "White resigns"
  333. won_by_resignation = "B+Resign"
  334. break
  335. else:
  336. self.moves.append(move)
  337. if string.lower(move[:4]) == "pass":
  338. passes = passes + 1
  339. if verbose >= 1:
  340. print "White passes"
  341. else:
  342. passes = 0
  343. self.blackplayer.white(move)
  344. if verbose >= 1:
  345. print "White plays " + move
  346. to_play = "B"
  347. if verbose >= 2:
  348. print self.whiteplayer.showboard() + "\n"
  349. if won_by_resignation == "":
  350. self.resultw = self.whiteplayer.final_score()
  351. self.resultb = self.blackplayer.final_score()
  352. else:
  353. self.resultw = won_by_resignation;
  354. self.resultb = won_by_resignation;
  355. # if self.resultb == self.resultw:
  356. # print "Result: ", self.resultw
  357. # else:
  358. # print "Result according to W: ", self.resultw
  359. # print "Result according to B: ", self.resultb
  360. # FIXME: $self->writesgf($sgffile) if defined $sgffile;
  361. if sgffile != "":
  362. self.writesgf(sgffile)
  363. def is_game_finished(self):
  364. """
  365. For Atari go. Returns True if any player made a capture.
  366. The 'captures' gtp command should be supported for this to work.
  367. """
  368. #TODO
  369. w_captures = 0
  370. b_captures = 0
  371. if self.blackplayer.is_known_command('captures'):
  372. w_captures = int(self.blackplayer.captures('white'))
  373. b_captures = int(self.blackplayer.captures('black'))
  374. elif self.whiteplayer.is_known_command('captures'):
  375. w_captures = int(self.whiteplayer.captures('white'))
  376. b_captures = int(self.whiteplayer.captures('black'))
  377. return w_captures > 0 or b_captures > 0
  378. def result(self):
  379. return (self.resultw, self.resultb)
  380. def cputime(self):
  381. cputime = {}
  382. cputime["white"] = self.whiteplayer.cputime()
  383. cputime["black"] = self.blackplayer.cputime()
  384. return cputime
  385. def quit(self):
  386. self.blackplayer.quit()
  387. self.whiteplayer.quit()
  388. class GTP_match:
  389. # Class members:
  390. # black
  391. # white
  392. # size
  393. # komi
  394. # handicap
  395. # handicap_type
  396. def __init__(self, whitecommand, blackcommand, size, komi, handicap,
  397. handicap_type, streak_length, endgamefilelist):
  398. self.white = whitecommand
  399. self.black = blackcommand
  400. self.size = size
  401. self.komi = komi
  402. self.handicap = handicap
  403. self.handicap_type = handicap_type
  404. self.streak_length = streak_length
  405. self.endgamefilelist = endgamefilelist
  406. def endgame_contest(self, sgfbase):
  407. results = []
  408. i = 1
  409. for endgamefile in self.endgamefilelist:
  410. game1 = GTP_game(self.white, self.black, self.size, self.komi,
  411. 0, "", endgamefile)
  412. game2 = GTP_game(self.black, self.white, self.size, self.komi,
  413. 0, "", endgamefile)
  414. if verbose:
  415. print "Replaying", endgamefile
  416. print "Black:", self.black
  417. print "White:", self.white
  418. game1.play("")
  419. result1 = game1.result()[0]
  420. if result1 != "0":
  421. plain_result1 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result1)
  422. result1_float = float(plain_result1.group(2))
  423. else:
  424. plain_result1 = re.search(r"(0)", "0")
  425. result1_float = 0.0
  426. if result1[0] == "B":
  427. result1_float *= -1
  428. if verbose:
  429. print "Result:", result1
  430. print "Replaying", endgamefile
  431. print "Black:", self.white
  432. print "White:", self.black
  433. game2.play("")
  434. result2 = game2.result()[1]
  435. if verbose:
  436. print "Result:", result2
  437. if result2 != "0":
  438. plain_result2 = re.search(r"([BW]\+)([0-9]*\.[0-9]*)", result2)
  439. result2_float = float(plain_result2.group(2))
  440. else:
  441. plain_result2 = re.search(r"(0)", "0")
  442. result2_float = 0.0
  443. if result2[0] == "B":
  444. result2_float *= -1
  445. results.append(result1_float - result2_float)
  446. if (result1 != result2):
  447. print endgamefile+ ":", plain_result1.group(), \
  448. plain_result2.group(), "Difference:",
  449. print result1_float - result2_float
  450. else:
  451. print endgamefile+": Same result:", plain_result1.group()
  452. sgffilename = "%s%03d" % (sgfbase, i)
  453. game1.writesgf(sgffilename + "_1.sgf")
  454. game2.writesgf(sgffilename + "_2.sgf")
  455. game1.quit()
  456. game2.quit()
  457. i += 1
  458. return results
  459. def play(self, games, sgfbase):
  460. game = GTP_game(self.white, self.black,
  461. self.size, self.komi, self.handicap,
  462. self.handicap_type, "")
  463. for i in range(games):
  464. sgffilename = "%s%03d.sgf" % (sgfbase, i + 1)
  465. game.play(sgffilename)
  466. game.quit()
  467. # ================================================================
  468. # Main program
  469. #
  470. # Default values
  471. #
  472. white = ""
  473. black = ""
  474. komi = ""
  475. size = "19"
  476. handicap = 0
  477. handicap_type = "fixed"
  478. streak_length = -1
  479. endgame_start_at = 0
  480. games = 1
  481. sgfbase = "twogtp"
  482. verbose = 0
  483. helpstring = """
  484. Run with:
  485. twogtp --white \'<path to program 1> --mode gtp [program options]\' \\
  486. --black \'<path to program 2> --mode gtp [program options]\' \\
  487. [twogtp options]
  488. Possible twogtp options:
  489. --verbose 1 (to list moves) or --verbose 2 (to draw board)
  490. --komi <amount>
  491. --handicap <amount>
  492. --free-handicap <amount>
  493. --adjust-handicap <length> (change handicap by 1 after <length> wins
  494. in a row)
  495. --size <board size> (default 19)
  496. --games <number of games to play> (default 1)
  497. --sgfbase <filename> (create sgf files with sgfbase as basename)
  498. --endgame <moves before end> (endgame contest - add filenames of
  499. games to be replayed after last option)
  500. """
  501. def usage():
  502. print helpstring
  503. sys.exit(1)
  504. try:
  505. args = sys.argv[1:]
  506. (opts, params) = getopt(args, "",
  507. ["black=",
  508. "white=",
  509. "verbose=",
  510. "komi=",
  511. "boardsize=",
  512. "size=",
  513. "handicap=",
  514. "free-handicap=",
  515. "adjust-handicap=",
  516. "games=",
  517. "sgfbase=",
  518. "endgame=",
  519. ])
  520. except:
  521. usage();
  522. for opt, value in opts:
  523. if opt == "--black":
  524. black = value
  525. elif opt == "--white":
  526. white = value
  527. elif opt == "--verbose":
  528. verbose = int(value)
  529. elif opt == "--komi":
  530. komi = value
  531. elif opt == "--boardsize" or opt == "--size":
  532. size = value
  533. elif opt == "--handicap":
  534. handicap = int(value)
  535. handicap_type = "fixed"
  536. elif opt == "--free-handicap":
  537. handicap = int(value)
  538. handicap_type = "free"
  539. elif opt == "--adjust-handicap":
  540. streak_length = int(value)
  541. elif opt == "--games":
  542. games = int(value)
  543. elif opt == "--sgfbase":
  544. sgfbase = value
  545. elif opt == "--endgame":
  546. endgame_start_at = int(value)
  547. if endgame_start_at != 0:
  548. endgame_filelist = params
  549. else:
  550. endgame_filelist = []
  551. if params != []:
  552. usage()
  553. if black == "" or white == "":
  554. usage()
  555. if komi == "":
  556. if handicap > 1 and streak_length == -1:
  557. komi = "0.5"
  558. else:
  559. komi = "5.5"
  560. match = GTP_match(white, black, size, komi, handicap, handicap_type,
  561. streak_length, endgame_filelist)
  562. if endgame_filelist != []:
  563. results = match.endgame_contest(sgfbase)
  564. win_black = 0
  565. win_white = 0
  566. for res in results:
  567. print res
  568. if res > 0.0:
  569. win_white += 1
  570. elif res < 0.0:
  571. win_black += 1
  572. print "%d games, %d wins for black, %d wins for white." \
  573. % (len(results), win_black, win_white)
  574. else:
  575. match.play(games, sgfbase)