Ticket #8: kombilo05m.patch
| File kombilo05m.patch, 25.5 kB (added by ug, 2 years ago) |
|---|
-
board1.py
diff -urN --exclude-from=kombilo05.exclude kombilo05.orig/board1.py kombilo05/board1.py
old new 416 416 417 417 return (x,y) 418 418 419 def placeMark(self, pos, color, outln='', size=''): 419 def placeInfluenceMark(self, pos, color, type): 420 if type=='territory': 421 self.placeMark(pos, color="", outln=color, size="small", type="rectangle") 422 elif type=='moyo': 423 self.placeMark(pos, color="", outln=color, size="small", type="triangle") 424 elif type=='area': 425 self.placeMark(pos, color="", outln=color, size="small", type="circle") 426 427 428 def placeMark(self, pos, color, outln='', size='', type='circle'): 420 429 """ Place colored mark at pos. """ 421 430 x1, x2, y1, y2 = self.getPixelCoord(pos, 1) 422 431 … … 426 435 else: 427 436 tmp1 = 2 428 437 tmp2 = 2 429 self.create_oval(x1+tmp1, x2+tmp2, y1-tmp1, y2-tmp2, fill = color, 430 outline = outln, tags=('marks', 'non-bg')) 438 if type=='circle': 439 self.create_oval(x1+tmp1, x2+tmp2, y1-tmp1, y2-tmp2, fill = color, 440 outline = outln, tags=('marks', 'non-bg')) 441 elif type=='rectangle': 442 self.create_rectangle(x1+6, x2+6, y1-6, y2-6, fill = color, 443 outline = outln, tags=('marks','non-bg')) 444 elif type=='triangle': 445 self.create_polygon((x1+y1)/2, x2+5-5, x1+5, y2-5-5, y1-5, y2-5-5, 446 fill = color, outline = outln, tags=('marks','non-bg')) 431 447 self.marks[pos]=color 432 448 self.onChange() 433 449 … … 565 581 y = int(r*math.sin((degree-90)*radPerDeg) + r) 566 582 return (x,y) 567 583 584 def drawShadedStone(self, pos, color, stipple='gray50', size='normal'): 585 x1, x2, y1, y2 = self.getPixelCoord(pos, 1) 586 if size=='normal': 587 tmp1 = 0 588 tmp2 = 0 589 elif size=='medium': 590 tmp1 = 3 591 tmp2 = 3 592 elif size=='small': 593 tmp1 = 6 594 tmp2 = 6 595 elif size=='tiny': 596 tmp1 = 9 597 tmp2 = 9 598 self.create_oval(x1+tmp1, x2+tmp2, y1-tmp1, y2-tmp2, 599 fill=color, stipple=stipple, 600 outline='', tags=('shaded','non-bg')) 601 568 602 def shadedStone(self, event): 569 603 x, y = self.getBoardCoord((event.x, event.y), 1) 570 604 if (x,y) == self.shadedStonePos: return # nothing changed -
kombilo05
diff -urN --exclude-from=kombilo05.exclude kombilo05.orig/gtp.py kombilo05/gtp.py
old new 1 #! /usr/bin/env python 2 3 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 4 # This program is distributed with GNU Go, a Go program. # 5 # # 6 # Write gnugo@gnu.org or see http://www.gnu.org/software/gnugo/ # 7 # for more information. # 8 # # 9 # Copyright 1999, 2000, 2001, 2002, 2003 and 2004 # 10 # by the Free Software Foundation. # 11 # # 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 2. # 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., 59 Temple Place - Suite 330, # 25 # Boston, MA 02111, USA. # 26 # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 27 28 # some comments (like above) and 29 # lots of code copied from twogtp.py from gnugo-3.6-pre4 30 # additions/changes by Aloril 2004 31 32 33 import popen2 34 import sys 35 import string 36 import time 37 import os 38 39 debug = 1 40 41 def coords_to_sgf(size, board_coords): 42 global debug 43 44 board_coords = string.lower(board_coords) 45 if board_coords == "pass": 46 return "" 47 if debug>1: 48 print "Coords: <" + board_coords + ">" 49 letter = board_coords[0] 50 digits = board_coords[1:] 51 if letter > "i": 52 sgffirst = chr(ord(letter) - 1) 53 else: 54 sgffirst = letter 55 sgfsecond = chr(ord("a") + int(size) - int(digits)) 56 return sgffirst + sgfsecond 57 58 59 def sgf_to_coords(size, sgf_coords): 60 print sgf_coords 61 letter = string.upper(sgf_coords[0]) 62 if letter>="I": 63 letter = chr(ord(letter)+1) 64 digit = str(ord('a') + int(size) - ord(sgf_coords[1])) 65 return letter+digit 66 67 68 class GTP_connection: 69 70 # 71 # Class members: 72 # outfile File to write to 73 # infile File to read from 74 75 def __init__(self, command): 76 try: 77 infile, outfile = popen2.popen2(command) 78 except: 79 print "popen2 failed" 80 sys.exit(1) 81 self.infile = infile 82 self.outfile = outfile 83 84 for i in range(100): 85 log_name = "gtp%03i.log" % i 86 if not os.path.exists(log_name): 87 break 88 self.log_fp = open(log_name, "w") 89 self.log_fp.write(command+"\n") 90 self.log_fp.flush() 91 92 def exec_cmd(self, cmd): 93 global debug 94 95 if debug: 96 sys.stderr.write("GTP command: " + cmd + "\n") 97 self.outfile.write(cmd + "\n\n") 98 self.outfile.flush() 99 if self.log_fp: 100 self.log_fp.write("Time: %f\n" % time.time()) 101 self.log_fp.write(cmd + "\n\n") 102 self.log_fp.flush() 103 result = "" 104 line = self.infile.readline() 105 if self.log_fp: 106 self.log_fp.write(line) 107 self.log_fp.flush() 108 while line != "\n": 109 result = result + line 110 line = self.infile.readline() 111 if self.log_fp: 112 self.log_fp.write(line) 113 self.log_fp.flush() 114 if debug: 115 sys.stderr.write("Reply: " + result + "\n") 116 117 # Remove trailing newline from the result 118 if result[-1] == "\n": 119 result = result[:-1] 120 121 if len(result) == 0: 122 return "ERROR: len = 0" 123 if (result[0] == "?"): 124 return "ERROR: GTP Command failed: " + result[2:] 125 if (result[0] == "="): 126 return result[2:] 127 return "ERROR: Unrecognized answer: " + result 128 129 130 131 class GTP_player: 132 133 # Class members: 134 # connection GTP_connection 135 136 def __init__(self, command): 137 self.connection = GTP_connection(command) 138 protocol_version = self.connection.exec_cmd("protocol_version") 139 if protocol_version[:5] != "ERROR": 140 self.protocol_version = protocol_version 141 else: 142 self.protocol_version = "1" 143 144 def is_known_command(self, command): 145 return self.connection.exec_cmd("known_command " + command) == "true" 146 147 def genmove(self, color): 148 if color[0] in ["b", "B"]: 149 command = "black" 150 elif color[0] in ["w", "W"]: 151 command = "white" 152 if self.protocol_version == "1": 153 command = "genmove_" + command 154 else: 155 command = "genmove " + command 156 157 return self.connection.exec_cmd(command) 158 159 def black(self, move): 160 if self.protocol_version == "1": 161 self.connection.exec_cmd("black " + move) 162 else: 163 self.connection.exec_cmd("play black " + move) 164 165 def white(self, move): 166 if self.protocol_version == "1": 167 self.connection.exec_cmd("white " + move) 168 else: 169 self.connection.exec_cmd("play white " + move) 170 171 def komi(self, komi): 172 self.connection.exec_cmd("komi " + komi) 173 174 def boardsize(self, size): 175 self.connection.exec_cmd("boardsize " + size) 176 if self.protocol_version != "1": 177 self.connection.exec_cmd("clear_board") 178 179 def handicap(self, handicap, handicap_type): 180 if handicap_type == "fixed": 181 result = self.connection.exec_cmd("fixed_handicap %d" % (handicap)) 182 else: 183 result = self.connection.exec_cmd("place_free_handicap %d" 184 % (handicap)) 185 186 return string.split(result, " ") 187 188 def loadsgf(self, endgamefile, move_number): 189 if self.connection.log_fp: 190 self.connection.log_fp.write(open(endgamefile).read()) 191 self.connection.log_fp.flush() 192 return self.connection.exec_cmd(string.join(["loadsgf", endgamefile, 193 str(move_number)])) 194 195 def list_stones(self, color): 196 return string.split(self.connection.exec_cmd("list_stones " + color), " ") 197 198 def quit(self): 199 return self.connection.exec_cmd("quit") 200 201 def showboard(self): 202 board = self.connection.exec_cmd("showboard") 203 if board and (board[0] == "\n"): 204 board = board[1:] 205 return board 206 207 def get_random_seed(self): 208 result = self.connection.exec_cmd("get_random_seed") 209 if result[:5] == "ERROR": 210 return "unknown" 211 return result 212 213 def set_random_seed(self, seed): 214 self.connection.exec_cmd("set_random_seed " + seed) 215 216 def get_program_name(self): 217 return self.connection.exec_cmd("name") + " " + \ 218 self.connection.exec_cmd("version") 219 220 def final_score(self): 221 return self.connection.exec_cmd("final_score") 222 223 def score(self): 224 return self.final_score(self) 225 226 def experimental_score(self, color): 227 self.showboard() 228 return self.connection.exec_cmd("experimental_score " + color) 229 230 def all_move_values(self): 231 moves = string.split(self.connection.exec_cmd("all_move_values")) 232 result = [] 233 for i in range(0, len(moves), 2): 234 result.append((moves[i], float(moves[i+1]))) 235 return result 236 237 def dragon_stones_and_status(self): 238 dragon_status = {} 239 for line in string.split(self.connection.exec_cmd("dragon_status"), "\n"): 240 lst = string.split(line) 241 stone = lst[0][:-1] 242 status = lst[1] 243 dragon_status[stone] = status 244 result = [] 245 for line in string.split(self.connection.exec_cmd("dragon_stones"), "\n"): 246 stone_lst = string.split(line) 247 for stone in stone_lst: 248 if dragon_status.has_key(stone): 249 key_stone = stone 250 break 251 else: 252 key_stone = "" 253 for stone in stone_lst: 254 status = dragon_status.get(key_stone, "unknown") 255 result.append((stone, status)) 256 return result 257 258 def influence_regions(self, color): 259 result = [] 260 data = self.connection.exec_cmd("initial_influence " + color + " influence_regions") 261 for line in string.split(data, "\n"): 262 result.append(map(int, string.split(line))) 263 return result 264 265 266 def set_board(self, black_stones, white_stones): 267 self.connection.exec_cmd("clear_board") 268 for color, stone_lst in (("black", black_stones), ("white", white_stones)): 269 for stone in stone_lst: 270 self.connection.exec_cmd("play " + color + " " + stone) 271 self.showboard() 272 273 def cputime(self): 274 if (self.is_known_command("cputime")): 275 return self.connection.exec_cmd("cputime") 276 else: 277 return "0" -
kombilo.py
diff -urN --exclude-from=kombilo05.exclude kombilo05.orig/kombilo.py kombilo05/kombilo.py
old new 65 65 66 66 from board1 import * 67 67 import v 68 from gtp import GTP_player 69 70 analyze_engine_command = "/usr/local/src/gnugo-3.6/interface/gnugo --mode gtp --chinese-rules --never-resign" 71 analyze_engine_command = "/usr/local/src/gnugo-3.6/interface/gnugo --mode gtp --chinese-rules --never-resign" 72 #for i in range(100): 73 # engine_sgf_name = "engine%03i.sgf" % i 74 # if not os.path.exists(engine_sgf_name): 75 # break 76 #play_engine_command = "/usr/local/src/gnugo-3.4/interface/gnugo --mode gtp --chinese-rules --never-resign -o " + engine_sgf_name 77 play_engine_command = "/usr/local/src/gnugo-3.4/interface/gnugo --mode gtp --chinese-rules --never-resign" 68 78 69 79 try: 70 80 from sgfpars import Node, Cursor, SGFError, SGFescape … … 5979 5989 except IOError: 5980 5990 showwarning('IOError', 'Could not write kombilo.def') 5981 5991 5992 self.analyze_engine.quit() 5993 self.play_engine.quit() 5982 5994 self.master.quit() 5983 5995 5984 5996 … … 6780 6792 self.currentSearchPattern = None 6781 6793 self.balloonHelp() 6782 6794 6795 self.analyze_engine = GTP_player(analyze_engine_command) 6796 print "Analyze:", self.analyze_engine.get_program_name() 6797 self.play_engine = GTP_player(play_engine_command) 6798 self.play_engine.get_random_seed() 6799 #self.play_engine.handicap(2, "fixed") 6800 print "Opponent:", self.play_engine.get_program_name() 6801 6783 6802 6784 6803 # --------------------------------------------------------------------------------------- 6785 6804 -
kombilo05
diff -urN --exclude-from=kombilo05.exclude kombilo05.orig/v.py kombilo05/v.py
old new 50 50 from sgfparser import Node, Cursor, SGFError, SGFescape 51 51 from board1 import * 52 52 53 import gtp 54 53 55 # --------------------------------------------------------------------------------------- 54 56 55 57 class BunchTkVar: … … 1523 1525 pass 1524 1526 1525 1527 return 0 1528 1529 def getCurrentPosAsSGFstring(self): 1530 n = self.cursor.currentN 1531 result = [] 1532 while n: 1533 result.append(n.SGFstring) 1534 n = n.previous 1535 result.reverse() 1536 return "(" + join(result, "") + ")\n" 1537 1538 def analyzePosition(self): 1539 class AnalyseInfo: pass 1540 pos_as_sgf = self.getCurrentPosAsSGFstring() 1541 if not hasattr(self, "analyze_cache"): self.analyze_cache = {} 1542 if self.analyze_cache.has_key(pos_as_sgf): 1543 print "Cached" 1544 print pos_as_sgf 1545 analyse_info = self.analyze_cache[pos_as_sgf] 1546 color = analyse_info.color 1547 move = analyse_info.move 1548 score = analyse_info.score 1549 moves = analyse_info.moves 1550 dragon_stone_status = analyse_info.dragon_stone_status 1551 influence = analyse_info.influence 1552 else: 1553 self.analyze_cache[pos_as_sgf] = analyse_info = AnalyseInfo() 1554 gtp_sgf = "gtp.sgf" 1555 fp = open(gtp_sgf, "w") 1556 fp.write(pos_as_sgf) 1557 fp.close() 1558 analyse_info.color = color = self.analyze_engine.loadsgf(gtp_sgf, "") 1559 self.analyze_engine.showboard() 1560 node_count0 = int(self.analyze_engine.connection.exec_cmd("get_trymove_counter")) 1561 analyse_info.move = move = self.analyze_engine.connection.exec_cmd("reg_genmove "+ color) 1562 print move 1563 score = self.analyze_engine.connection.exec_cmd("estimate_score " + color) 1564 #score1 = self.analyze_engine.experimental_score(color) 1565 #score = score0 + " | " + score1 1566 node_count1 = int(self.analyze_engine.connection.exec_cmd("get_trymove_counter")) 1567 tmp = node_count = node_count1-node_count0 1568 node_count_str = "" 1569 if not node_count: 1570 node_count_str = "0" 1571 else: 1572 while tmp: 1573 tmp, tmp_rem = divmod(tmp, 1000) 1574 if node_count_str: node_count_str = ","+node_count_str 1575 if tmp: 1576 node_count_str = ("%03i" % tmp_rem) + node_count_str 1577 else: 1578 node_count_str = str(tmp_rem) + node_count_str 1579 score += " ("+node_count_str+" nodes)" 1580 print "!:", score 1581 analyse_info.score = score 1582 1583 #Moves candidates 1584 if move!="PASS": 1585 #moves = split(self.analyze_engine.connection.exec_cmd("top_moves")) 1586 analyse_info.moves = moves = self.analyze_engine.all_move_values() 1587 else: 1588 analyse_info.moves = moves = [] 1589 1590 #Dragons 1591 if self.analyze_engine.connection.exec_cmd("list_stones black") or \ 1592 self.analyze_engine.connection.exec_cmd("list_stones white"): 1593 #dragon_data = self.analyze_engine.connection.exec_cmd("dragon_data") 1594 analyse_info.dragon_stone_status = dragon_stone_status = \ 1595 self.analyze_engine.dragon_stones_and_status() 1596 else: 1597 analyse_info.dragon_stone_status = dragon_stone_status = [] 1598 1599 #Influence 1600 analyse_info.influence = influence = self.analyze_engine.influence_regions(color) 1601 1602 #Display results 1603 self.scoreVar.set(score) 1604 #Move candidates 1605 for pos, value in moves: 1606 #print pos, move, pos==move 1607 if pos==move: postfix = "!" 1608 else: postfix = "" 1609 pos = self.convCoord(gtp.coords_to_sgf(self.board.boardSize, pos)) 1610 if value<1.0: 1611 value = "<1" 1612 else: 1613 value = str(int(value)) 1614 self.board.placeLabel(pos, "LB", value + postfix) 1615 #Dragon status 1616 for stone, status in dragon_stone_status: 1617 #print stone, status 1618 if status=="critical": 1619 label = "!" 1620 elif status=="dead": 1621 label = "x" 1622 else: 1623 label = "" 1624 if label: 1625 pos = self.convCoord(gtp.coords_to_sgf(self.board.boardSize, stone)) 1626 self.board.placeLabel(pos, "LB", label) 1627 #Influence 1628 for j in range(len(influence)): 1629 for i in range(len(influence[j])): 1630 inf = influence[j][i] 1631 pos = i+1, j+1 1632 if inf==3: 1633 self.board.placeInfluenceMark(pos, "green", "territory") 1634 elif inf==2: 1635 self.board.placeInfluenceMark(pos, "#00C000", "moyo") 1636 elif inf==1: 1637 self.board.placeInfluenceMark(pos, "#007000", "area") 1638 elif inf==-3: 1639 self.board.placeInfluenceMark(pos, "red", "territory") 1640 elif inf==-2: 1641 self.board.placeInfluenceMark(pos, "#C00000", "moyo") 1642 elif inf==-1: 1643 self.board.placeInfluenceMark(pos, "#700000", "area") 1644 1645 1646 def playCurrentMoveInEngine(self): 1647 n = self.cursor.currentNode() 1648 print n 1649 pos_as_sgf = self.getCurrentPosAsSGFstring() 1650 if len(n)==1: 1651 if hasattr(self, "lastEnginePos"): 1652 if self.lastEnginePos[:-2] + self.cursor.currentN.SGFstring + self.lastEnginePos[-2:]!=pos_as_sgf: 1653 print "Position not ok!" 1654 return 1655 gtp_move = gtp.sgf_to_coords(self.board.boardSize, n.values()[0][0]) 1656 print gtp_move 1657 if n.keys()[0]=="B": 1658 self.play_engine.black(gtp_move) 1659 next_color = "white" 1660 else: 1661 self.play_engine.white(gtp_move) 1662 next_color = "black" 1663 move = self.play_engine.genmove(next_color) 1664 print move 1665 pos = self.convCoord(gtp.coords_to_sgf(self.board.boardSize, move)) 1666 self.nextMove(pos) 1667 pos_as_sgf = self.getCurrentPosAsSGFstring() 1668 #x1,y1,x2,y2 = self.board.getPixelCoord(pos) 1669 #print pos, x1,y1,x2,y2 1670 #class Ev: pass 1671 #ev = Ev() 1672 #ev.x = (x1+x2)/2 1673 #ev.y = (y1+y2)/2 1674 #self.cursor.onButton1(ev) 1675 #self.board.play(self.convCoord(gtp.coords_to_sgf(self.board.boardSize, move))) 1676 #self.board.currentColor = self.board.invert(self.board.currentColor) 1677 self.play_engine.showboard() 1678 self.play_engine.connection.log_fp.write(pos_as_sgf) 1679 print pos_as_sgf 1680 self.lastEnginePos = pos_as_sgf 1526 1681 1527 1682 1528 1683 def gotoMove(self, event): … … 2739 2894 self.dataWindow.updateGameInfo(self.cursor) 2740 2895 2741 2896 2897 def showRestOfGame(self): 2898 n = self.cursor.currentN 2899 count = 0 2900 while n: 2901 n = n.next 2902 if not n: break 2903 count += 1 2904 if count<=2: 2905 stipple = 'gray75' 2906 elif count<=10: 2907 stipple = 'gray50' 2908 elif count<=30: 2909 stipple = 'gray25' 2910 else: 2911 stipple = 'gray12' 2912 if count<=50: 2913 size = 'normal' 2914 elif count<=100: 2915 size = 'medium' 2916 elif count<=150: 2917 size = 'small' 2918 else: 2919 size = 'tiny' 2920 d = n.getData() 2921 pos = None 2922 if d.has_key("B"): 2923 pos = self.convCoord(d["B"][0]) 2924 color = "black" 2925 if d.has_key("W"): 2926 pos = self.convCoord(d["W"][0]) 2927 color = "white" 2928 #self.board.placeLabel(pos, "LB", str(count), color) 2929 self.board.drawShadedStone(pos, color, stipple, size) 2930 2742 2931 def toggleDatawindow(self): 2743 2932 if self.options.datawindowVisible.get(): 2744 2933 self.dataWindow.window.deiconify() … … 2846 3035 self.options.datawindowVisible.set(1) 2847 3036 self.datawindowButton = Checkbutton(navFrame, text='DATA', variable = self.options.datawindowVisible, 2848 3037 command = self.toggleDatawindow, indicatoron=0) 2849 3038 3039 self.analyzeButton = Button(navFrame, text='Analyze', command = self.analyzePosition) 3040 self.boardFrame.bind('<a>', lambda e, s = self.analyzeButton: s.invoke()) 3041 self.playCurrentButton = Button(navFrame, text='Play', command = self.playCurrentMoveInEngine) 3042 #self.boardFrame.bind('<p>', lambda e, s = self.playCurrentButton: s.invoke()) 3043 self.showRestOfGameButton = Button(navFrame, text='Rest', command = self.showRestOfGame) 3044 self.boardFrame.bind('<r>', lambda e, s = self.showRestOfGameButton: s.invoke()) 3045 2850 3046 # try to load icons for navigation buttons 2851 3047 2852 3048 if sys.path[0].endswith('library.zip'): SYSPATH = os.path.split(sys.path[0])[0] … … 2878 3074 self.passButton.grid(row=0, column=10) 2879 3075 self.gameinfoButton.grid(row=0, column=11) 2880 3076 self.datawindowButton.grid(row=0, column=12) 3077 self.analyzeButton.grid(row=0, column=13) 3078 self.playCurrentButton.grid(row=0, column=14) 3079 self.showRestOfGameButton.grid(row=0, column=15) 2881 3080 2882 3081 self.modeVar = StringVar() 2883 3082 self.modeVar.set('blackwhite') … … 2936 3135 self.capLabel = Label(labelFrame, height=1, width=15, relief=SUNKEN, justify=LEFT, 2937 3136 textvariable=self.capVar) 2938 3137 3138 self.scoreVar = StringVar() 3139 self.scoreLabel = Label(labelFrame, height=1, width=53, relief=SUNKEN, justify=LEFT, 3140 textvariable=self.scoreVar) 3141 2939 3142 # pack everything 2940 3143 2941 3144 self.gameNameLabel.pack(expand=NO, fill=X, side=LEFT, padx = 5) 2942 3145 self.movenoLabel.pack(expand=NO, fill=X, side=LEFT, padx=5) 2943 3146 self.capLabel.pack(expand=NO, fill=X, side=LEFT, padx=5) 3147 self.scoreLabel.pack(expand=NO, fill=X, side=LEFT, padx=5) 2944 3148 2945 3149 2946 3150 def balloonHelp(self): … … 2954 3158 self.balloon.bind(self.passButton, 'Pass') 2955 3159 self.balloon.bind(self.gameinfoButton, 'Edit game info') 2956 3160 self.balloon.bind(self.datawindowButton, 'Open/withdraw data window') 3161 self.balloon.bind(self.analyzeButton, 'Analyze position with GnuGo') 3162 self.balloon.bind(self.playCurrentButton, 'Play current move on board against engine/something else using gtp') 2957 3163 2958 3164 self.balloon.bind(self.BWbutton, 'Play black/white stones') 2959 3165 self.balloon.bind(self.WBbutton, 'Play white/black stones')
