#!/bin/env python # coding: utf-8 # PROGRAM NAME: PYGO.PY # pygo.py使用说明 # Usage: pygo.py [sgf_file] # [sgf_file] 是可选参数 # pygo遵循GPL # Copyright (C) 2002 James Tauber # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 由ggzzzzzzz@gmail.com注释,并改动了几行代码 # 1.改进了星位的显示效果 # 2.原来的0.10.1版本在创建新对局时,如果handicap设置不等于0时出错, # 为此我在place_handicap_stones函数中增加了一行代码,这一个FIX还有待验证 # 以下导入Tk模块 from Tkinter import * import tkFileDialog # 文件对话框控件 import tkSimpleDialog # 简单对话框控件 import tkMessageBox # 消息框控件 import ScrolledText # 滚动文本控件 import sys # 导入sys模块 # 以下导入James Tauber自己写的模块 import gogame # gogame.py是表示对局状态,提子、打劫的规则,及各种算法的模块,比如:计算棋块的算法、记录黑白双方被捕获的棋子数量、计算胜负等 # gogame.py中的算法我不太懂 import showgo # showgo.py是显示棋盘、棋子的模块 import sgf # showgo.py是读写sgf文件的模块 # pygo.py中定义了3个类: # class PyGo 创建菜单 # class PlayGameDiaglog 创建新对局的对话窗口 # class Game 对局、打谱、摆死活题需要的各种功能函数 # 其中class PyGo使用class PlayGameDiaglog和class Game app_name = "PyGo -- 用Python写的围棋客户端" version = "0.10.11" # 我将版本号从0.10.1改成0.10.11,因为改动了pygo.py和showgo.py的一些代码 mv_stone=0 class Game: # 处理鼠标事件、键盘事件的类 def __init__(self, play_go, collection, size=19, handicap=0, scale=25): # 初始化的方式: # class PyGo打开sgf文件:self.game = Game(self, collection, scale=self.scale) # class PyGo创建新对局: self.game = Game(self, None, size=play_game_dialog.board_size, handicap=play_game_dialog.handicap, scale=self.scale) self.play_go = play_go # play_go是PyGo类对象 play_go.variations.config(state=NORMAL) # Tk的调用,什么作用? play_go.variations.delete("1.0", END) # Tk的调用,什么作用? play_go.variations.config(state=DISABLED) # Tk的调用,什么作用? self.scale = scale # 棋盘大小因子? self.dead_points = [] # 用于记录死子的列表? self.play_go.root.bind("", self.prev_node) # 将左箭头键和self.prev_node函数绑定,跳到上一个节点 self.play_go.root.bind("", self.next_node) # 将右箭头键和self.next_node函数绑定,跳到下一个节点 self.play_go.root.bind("", self.prev_variation) # 跳到上一个分支 self.play_go.root.bind("", self.next_variation) # 跳到下一个分支 self.play_go.root.bind("v", self.make_variation) # "v"键创建分支 self.play_go.root.bind("p", self.toggle_placement) # "p"键和self.toggle_placement函数绑定,修改self.placement self.play_go.root.bind("u", self.undo) # "u"键和self.undo函数绑定,悔棋功能 self.play_go.root.bind("t", self.toggle_territory) # "t"键和self.toggle_territory函数绑定,修改self.show_territory self.play_go.root.bind("i", self.insert_node) # 在SGF的Gametree中增加节点(Node) self.play_go.root.bind("", self.first_node_in_variation) # 跳到分支中第一个节点 self.play_go.root.bind("", self.last_node_in_variation) # 跳到分支中最后一个节点,也就是分支的末端 self.play_go.canvas.bind("", self.handle_move_event) # 点击鼠标左键走子 self.placement = 0 # 等于0就是黑白轮流下,1表示放置黑子,2表示放置白子 self.show_territory = 0 # 1表示进入点算双方势力范围模式,计算胜负 if collection: # collection是棋谱记录类对象,在sgf.py中定义 # 如果已经导入sgf文件 self.is_playable = 0 # 0表示不允许走子,1表示允许走子。打谱时只有创建分支时才能走子,对局时一般都为1 self.play_go.root.bind("c", self.continue_game) # 导入sgf文件后,按"c"可以继续对局 self.collection = collection # self.collection赋值,棋谱记录类对象 self.current_node = collection.children[0].nodes[0] # 创建一个空的节点 self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 else: # 新建对局 self.is_playable = 1 # 1表示允许走子 self.size = size # 棋盘大小 self.play_go.root.unbind("c") # 新建对局情况下,按"c"键不发生作用 self.collection = sgf.Collection() # 将self.collection初始化成Collection类对象 self.current_gametree = sgf.GameTree(self.collection) # 将self.current_gametree初始化成GameTree类对象 self.collection.children.append(self.current_gametree) # 在collection中创建gametree self.current_node = sgf.Node(self.current_gametree, None) # 初始化current_node节点 self.current_gametree.nodes.append(self.current_node) # self.current_gametree增加一个节点 self.current_node.properties["SZ"] = [str(size)] # SiZe,棋盘大小 if handicap: self.current_node.properties["HA"] = [str(handicap)] # Handicap, 让子数 self.current_node.properties["FF"] = ["4"] # File Format,文件格式 self.current_node.properties["GM"] = ["1"] # GaMe,对局类别 self.current_node.properties["AP"] = ["%s:%s" % (app_name, version)] # APplication,应用软件 self.place_handicap_stones(handicap) # 根据handicap放置让子 self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def toggle_placement(self, event): # 按键盘'p'所调用的函数,切换放置棋子颜色。主要用于摆死活题。 # 如果需要摆死活题,可以在创建新对局后,用self.placement == 1或self.placement == 2来添加黑子或白子。 # 死活题摆好后或对局开始后,self.placement只能是0,就是黑白轮流下,1或2是无效的 # 'p' to toggle play / place black / place white if self.placement == 0: self.placement = 1 # 添加黑子 elif self.placement == 1: self.placement = 2 # 添加白子 elif self.placement == 2: self.placement = 0 # 黑白轮流下 self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def toggle_territory(self, event): # 按键盘't'所调用的函数,用于改变self.show_territory,控制是否进入点算双方势力范围模式、计算胜负 # 't' to toggle territory/score if self.show_territory == 0: # self.show_territory = 1 # 1表示进入点算双方势力范围模式、计算胜负 else: self.show_territory = 0 self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def make_variation(self, event): # 按键盘'v'创建分支。这个函数可能放在sgf.py模块较为合适 # @@@ much of this method could be in sgf library self.current_gametree = self.current_node.parent pos = self.current_gametree.nodes.index(self.current_node) nodes1 = self.current_gametree.nodes[:pos] nodes2 = self.current_gametree.nodes[pos:] tree1 = sgf.GameTree(self.current_gametree) tree2 = sgf.GameTree(self.current_gametree) self.current_gametree.nodes = nodes1 self.current_gametree.children.append(tree1) self.current_gametree.children.append(tree2) tree1.nodes = nodes2 new_node = sgf.Node(tree2, nodes1[-1]) tree2.nodes.append(new_node) self.current_node.previous.variations = [self.current_node, new_node] self.current_node.next_variation = new_node new_node.previous_variation = self.current_node self.current_node = new_node self.current_gametree = tree2 self.is_playable = 1 # 表示在创建分支后,就可以用走子的方式创建Node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def continue_game(self, event): # 导入SGF文件后,浏览到分支末端,按键盘'c'可以继续对局,进入黑白轮流下的走子状态。 # 如果没有下一个Node,就是到了一个分支的末端,可以按键盘'c'走子 # don't do anything if not at end of variation if self.current_node.next: # 如果有下一个节点,就是说在打谱状态,没有到分支的末端,所以不能走子 self.display_result("NOT AT END OF VARIATION") return self.is_playable = 1 # 设置为走子状态 self.current_gametree = self.current_node.parent # 初始化self.current_gametree self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def place_handicap_stones(self, handicap): # 在棋盘上放置让子 # uses: handicap, size # changes: contents # 只有对应9,13, 19棋盘大小三种handicap_positions handicap_positions = [ [], [], [], [], [], [], [], [], [], [(7,3),(3,7),(7,7),(3,3),(5,5),(3,5),(7,5),(5,3),(5,7)], [], [], [], [(10,4),(4,10),(10,10),(4,4),(7,7),(4,7),(10,7),(7,4),(7,10)], [], [], [], [], [], [(16,4),(4,16),(16,16),(4,4),(10,10),(4,10),(16,10),(10,4),(10,16)] ] h = handicap if h == 6 or h == 8: # 如果让子数量为6或8,不放置天元(正中的星位) h = h + 1 for i in range(h): (x, y) = handicap_positions[self.size][i] if handicap == 6 or handicap == 8: if (x, y) == ((self.size+1)/2, (self.size+1)/2): # 如果让子数量为6或8 continue # 跳过,不放置天元(正中的星位) self.go_game = g = gogame.GoGame(size=self.size, handicap=handicap) # FIX 这一行我加的, # 否则执行下一行时报错:self没有go_game这一个对象 result = self.go_game.place_stone(x, y, gogame.BLACK) sgf_node = sgf.Node(self.current_gametree, self.current_node) self.current_gametree.nodes.append(sgf_node) sgf_node.properties["AB"] = ["%s%s" % (sgf.SGF_POS[x], sgf.SGF_POS[y])] # SGF的设置属性,Add Black, 增加黑子 self.current_node.next = sgf_node self.current_node = sgf_node def output(self, f): self.collection.output(f) # 将collection类对象转换成sgf格式保存成文件 ### STATUS DISPLAY 在窗口最下面的状态提示栏显示信息的函数 def display_state(self): # 在窗口最下面的状态提示栏左边显示提示信息 if self.placement == 1: # 如果设置了添加黑子变量 self.play_go.label1.config(text = "PLACE BLACK STONES 添加黑子") elif self.placement == 2: # 如果设置了添加白子变量 self.play_go.label1.config(text = "PLACE WHITE STONES 添加白子") elif not self.is_playable: # 不能走子 self.play_go.label1.config(text = "") elif self.current_node.next: # not at end of a variation 并非最后一个节点 self.play_go.label1.config(text = "") elif self.go_game.game_completed: # 如果已经设置了棋局终止变量 self.play_go.label1.config(text = "GAME OVER 棋局终止") elif self.go_game.to_play == gogame.BLACK:# 轮到黑方走子 self.play_go.label1.config(text = "%s: BLACK TO PLAY 黑方走子" % self.go_game.move_number) else: # 轮到白方走子 self.play_go.label1.config(text = "%s: WHITE TO PLAY 白方走子" % self.go_game.move_number) def display_prisoner_count(self): # 在窗口最下面的状态提示栏右边显示黑白双方被捕获的棋子数量 self.play_go.label2.config(text = "black: %s white: %s" % (self.go_game.white_prisoners, self.go_game.black_prisoners)) def display_result(self, message=""): # 在窗口最下面的状态提示栏中间显示提示信息 self.play_go.label3.config(text = message) ### NODE NAVIGATION 浏览棋谱的函数,键盘的, , , , , def prev_node(self, event): # 键盘的,上一个节点 if self.current_node.previous: self.current_node = self.current_node.previous self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def next_node(self, event): # 键盘的,下一个节点 if self.current_node.next: self.current_node = self.current_node.next self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def prev_variation(self, event): # 键盘的 ,上一个分支 if self.current_node.previous_variation: self.current_node = self.current_node.previous_variation self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def next_variation(self, event): # 键盘的,下一个分支 if self.current_node.next_variation: self.current_node = self.current_node.next_variation self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def first_node_in_variation(self, event): # 键盘的 ,跳到分支的第一个节点 while not self.current_node.first: self.current_node = self.current_node.previous self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def last_node_in_variation(self, event): # 键盘的,跳到分支的最后一个节点 while self.current_node.next: self.current_node = self.current_node.next self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 ### DRAW TERRITORY 显示势力范围的函数 def draw_territory(self): # 显示势力范围 (b, w) = self.go_game.calculate_score(self.dead_points) self.display_result("black: %s white: %s" % (b, w)) # 在窗口最下面的状态提示栏中间显示提示信息 t = self.go_game.calculate_territory(self.dead_points) for (x, y) in t[0]: self.show_go.draw_shape(x, y, self.show_go.SQUARE) for (x, y) in t[1]: self.show_go.draw_territory(x, y, self.show_go.BLACK) for (x, y) in t[2]: self.show_go.draw_territory(x, y, self.show_go.WHITE) ### TOGGLE GROUP ALIVE/DEAD 如果self.show_territory被设为1,表示已经进入点算双方势力范围模式,切换被鼠标点击的棋串的死活状态 def toggle_group_alive_dead(self, x, y): # 点击鼠标左键后,从handle_move_event跳到这里。切换被鼠标点击的棋串的死活状态 group = self.go_game.calculate_group(x, y) # 调用gogame.py中的函数 if (x,y) in self.dead_points: new_dead_points = [] for point in self.dead_points: if not point in group: new_dead_points.append(point) self.dead_points = new_dead_points else: self.dead_points.extend(group) # 将group列表加入dead_points列表 ### def move_property(self): # 切换棋子颜色 if self.go_game.to_play == gogame.BLACK: return "B" else: return "W" ### HANDLE INSERT NODE 插入(增加)一个Node def insert_node(self, event): # 插入(增加)一个Node # @@@ much of this method could be in sgf library self.current_gametree = self.current_node.parent next = self.current_node.next new_node = sgf.Node(self.current_gametree, self.current_node) if next: new_node.next = next new_node.next.previous = new_node pos = self.current_gametree.nodes.index(self.current_node) nodes1 = self.current_gametree.nodes[:pos] nodes2 = self.current_gametree.nodes[pos:] self.current_gametree.nodes = nodes1 self.current_gametree.nodes.append(self.current_node) self.current_gametree.nodes.append(new_node) self.current_gametree.nodes.extend(nodes2[1:]) else: self.current_gametree.nodes.append(new_node) self.current_node = new_node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 ### HANDLE PASS 弃权一手功能 def pass_one(self, event): sgf_node.properties[self.move_property()] = [""] if sgf_node != self.current_node: self.current_gametree.nodes.append(sgf_node) self.current_node.next = sgf_node self.current_node = sgf_node self.go_game.pass_move() # 设置弃权一手的标记 # draw current node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 return ### HANDLE UNDO 悔棋功能,可以不断悔棋 def undo(self, event): # 按键盘'u'后执行undo操作调用的函数 # @@@ much of this method could be in sgf library # don't do anything if game not playable 如果不是走子状态 if not self.is_playable: self.display_result("GAME NOT PLAYABLE") # 在窗口最下面的状态提示栏中间显示提示信息 return # don't do anything if not at end of variation 如果不是分支中最末端的一个节点 if self.current_node.next: self.display_result("NOT AT END OF VARIATION") # 在窗口最下面的状态提示栏中间显示提示信息 return if self.current_node.previous: self.current_node = self.current_node.previous self.current_node.next = None self.current_node.parent.nodes = self.current_node.parent.nodes[:-1] # @@@ possibly still need to remove variation links else: # 如果这是根节点,没有上一个节点 self.display_result("CAN'T UNDO START NODE") # 在窗口最下面的状态提示栏中间显示提示信息 return self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 ### HANDLE CLICK 处理鼠标点击事件 def handle_move_event(self, event): # 鼠标左键事件 # get point coordinates clicked on # 将鼠标点击canvas的坐标转变成棋盘坐标 x = self.show_go.get_x(event.x) y = self.show_go.get_y(event.y) # if showing territory... 如果self.show_territory设置为1,表示要点算双方的势力范围,计算胜负 if self.show_territory: # ignore clicks outside board area # 如果点击在棋盘之外,什么都不做 if x < 1 or y < 1 or x > self.size or y > self.size: return # otherwise toggle alive/dead status of group clicked on # 否则切换被点击棋块的死活状态 self.toggle_group_alive_dead(x, y) # 切换棋串的生死状态 # draw current node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 return if self.placement: # 如果是在摆死活题模式,在棋盘上放置一子 self.handle_placement(event) # 摆死活题的模式不能捕获棋子 return # don't do anything if game not playable # 如果是处于打谱状态,只显示"GAME NOT PLAYABLE",什么都不做 if not self.is_playable: self.display_result("GAME NOT PLAYABLE") # 在窗口最下面的状态提示栏中间显示提示信息 return # don't do anything if not at end of variation # 如果不是处于分支末端,显示"NOT AT END OF VARIATION",什么都不做 if self.current_node.next: self.display_result("NOT AT END OF VARIATION") # 在窗口最下面的状态提示栏中间显示提示信息 return # @@@ does the following need to be here? if self.go_game.last_move_pass: self.display_result("PASS") # 在窗口最下面的状态提示栏中间显示提示信息 else: self.display_result() # 在窗口最下面的状态提示栏中间显示提示信息 # if the current_node is empty, use it otherwise 如果目前节点是空的,就使用目前节点 # tentatively create new SGF node for this move 否则为当前落子临时创建一个新的SGF节点 if len(self.current_node.properties.keys()) == 0: sgf_node = self.current_node else: sgf_node = sgf.Node(self.current_gametree, self.current_node) # if click outside board area... # 如果鼠标点击在棋盘外侧,当作弃权一手处理 if x < 1 or y < 1 or x > self.size or y > self.size: # treat as pass # 当作弃权一手处理 sgf_node.properties[self.move_property()] = [""] if sgf_node != self.current_node: self.current_gametree.nodes.append(sgf_node) self.current_node.next = sgf_node self.current_node = sgf_node self.go_game.pass_move() # 设置弃权一手的标记 # draw current node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 return # if point already occupied... # 如果这个点上已经有棋子,在状态栏提示"ILLEGAL MOVE (ALREADY OCCUPIED)" if self.go_game.is_occupied(x, y): # report to user self.display_result("ILLEGAL MOVE (ALREADY OCCUPIED)") # 在窗口最下面的状态提示栏中间显示提示信息 self.current_node.next = None return # set up some variables depending on whose go it is 根据这一手该哪一方下来设置一些变量 # @@@ could factor this out and use it elsewhere 这些变量在其他地方将会使用 if self.go_game.to_play == gogame.BLACK: move_property = "B" # 表示这一手该黑子下 prisoner_colour_name = "WHITE" # 表示俘虏颜色应该为白 else: move_property = "W" # 表示这一手该白子下 prisoner_colour_name = "BLACK" # 表示俘虏颜色应该为白 # attempt move (subject to ko and suicide rules) 根据规则确定该落子是否有效,是否叫吃,是否自杀) result = self.go_game.move_stone(x, y) if result.status == gogame.KO: # 违反打劫规则 self.display_result("ILLEGAL MOVE (KO)") # 在窗口最下面的状态提示栏中间显示提示信息 self.current_node.next = None elif result.status == gogame.SUICIDE: # 自杀,pygo默认的规则不允许自杀 self.display_result("ILLEGAL MOVE (SUICIDE)") # 在窗口最下面的状态提示栏中间显示提示信息 self.current_node.next = None elif result.status == gogame.VALID: # 有效落子 sgf_node.properties[move_property] = ["%s%s" % (sgf.SGF_POS[x], sgf.SGF_POS[y])] if sgf_node != self.current_node: self.current_gametree.nodes.append(sgf_node) self.current_node.next = sgf_node self.current_node = sgf_node self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 if len(result.prisoners) > 0: # if pieces captured... 如果有棋块被吃,在状态栏上显示被吃棋子的数量 # 在窗口最下面的状态提示栏中间显示提示信息 self.display_result("%s %s CAPTURED" % (len(result.prisoners), prisoner_colour_name)) self.display_prisoner_count() def handle_placement(self, event): # 摆死活题模式,鼠标左键点击后在棋盘上添加一个棋子 # can't place stones on a node with moves 如果当前节点已经有"B"或"W"的走子属性,添加棋子是非法的 if self.current_node.properties.has_key("B") or self.current_node.properties.has_key("W"): # 在窗口最下面的状态提示栏中间显示提示信息 self.display_result("CAN'T PLACE STONES ON A NODE WITH A MOVE") # 对局开始后,不能任意添加黑子或白子,只能黑白轮流下 return # FIXME,如果这种状态不允许添加,按'p'键时的处理是否需要改进 # get point coordinates clicked on 得到鼠标点击的棋盘坐标 x(0-19), y(0-19)? x = self.show_go.get_x(event.x) y = self.show_go.get_y(event.y) sgf_node = self.current_node # if click outside board area... 如果点击在棋盘外侧,什么都不做,立即返回 if x < 1 or y < 1 or x > self.size or y > self.size: # do nothing return if self.placement == 1: colour = gogame.BLACK property = "AB" # SGF的设置属性,Add Black,增加黑子 elif self.placement == 2: colour = gogame.WHITE property = "AW" # SGF的设置属性,Add White,增加白子 self.go_game.place_stone(x, y, colour) # 放置棋子,应该是用于摆死活题 if not sgf_node.properties.has_key(property): sgf_node.properties[property] = [] sgf_node.properties[property].append("%s%s" % (sgf.SGF_POS[x], sgf.SGF_POS[y])) self.draw_current_node() # 在右下角的小窗口显示SGF Node的信息 def draw_current_node(self): # 在右下角的小窗口显示SGF Node的信息的函数 node = self.current_node # display property info 显示SGF属性信息 self.play_go.properties.config(state=NORMAL) self.play_go.properties.delete("1.0", END) for property in node.properties.keys(): self.play_go.properties.insert(END, property+"\n") for value in node.properties[property]: self.play_go.properties.insert(END, "\t"+value+"\n") self.play_go.properties.config(state=DISABLED) # display variation info 显示分支信息 self.play_go.variations.config(state=NORMAL) self.play_go.variations.delete("1.0", END) if node.previous: for variation in node.previous.variations: if variation.properties.has_key("N"): node_name = variation.properties["N"][0] elif variation.properties.has_key("B"): node_name = "B" + variation.properties["B"][0] elif variation.properties.has_key("W"): node_name = "W" + variation.properties["W"][0] else: node_name = "node" self.play_go.variations.insert(END, node_name) if variation==node: self.play_go.variations.insert(END, "<---") self.play_go.variations.insert(END, "\n") else: for gametree in self.collection.children: self.play_go.variations.insert(END, "Game") if gametree == self.current_node.parent: self.play_go.variations.insert(END, "<---") self.play_go.variations.insert(END, "\n") self.play_go.variations.insert(END, "--------\n") # 显示“--------” for variation in node.variations: if variation.properties.has_key("N"): # 节点注释属性,Nodename,给节点命名 node_name = variation.properties["N"][0] elif variation.properties.has_key("B"): # 走子属性,Black,黑走子 node_name = "B" + variation.properties["B"][0] elif variation.properties.has_key("W"): # 走子属性,White,白走子 node_name = "W" + variation.properties["W"][0] else: node_name = "node" self.play_go.variations.insert(END, node_name+"\n") if not self.current_node.next: # 如果下一个节点不存在,就是到了一个分支的末端?显示***END*** self.play_go.variations.insert(END, "***END***") self.play_go.variations.config(state=DISABLED) # construct a list of nodes that apply to this game up until # (but not including) node being displayed, # finding last size and handicap # @@@ should this be in SGF module? node_list = [] size = 0 handicap = 0 if node.properties.has_key("SZ") and not size: size = int(node.properties["SZ"][0]) # 根据SGF的根属性"SZ"来设置棋盘大小 if node.properties.has_key("HA") and not handicap: handicap = int(node.properties["HA"][0]) # 根据SGF的根属性"HA"来设置让子数量 node = node.previous while node: node_list.insert(0, node) if node.properties.has_key("SZ") and not size: size = int(node.properties["SZ"][0]) if node.properties.has_key("HA") and not handicap: handicap = int(node.properties["HA"][0]) node = node.previous # if no size specified assume 19 如果没有指定棋盘大小,默认为19x19 if not size: size = 19 self.size = size # create a new ShowGo and GoGame 创建新的ShowGo和GoGame类对象 # @@@ this suggests that some of the calls to gogame in handle_move_event are redundant self.show_go = s = showgo.ShowGo(self.play_go.canvas, size, self.scale) self.go_game = g = gogame.GoGame(size, handicap=handicap) def move_stone(move_property, s_colour, g_colour): # draw_current_node -> move_stone # global mv_stone # mv_stone=mv_stone+1 # print "mv_stone", mv_stone pos = node.properties[move_property][0] if pos == "" or pos == "tt": g.pass_move() # @@@ should it give colour? 弃权方的颜色需要记录吗? else: x = ord(pos[0]) - 96 y = ord(pos[1]) - 96 s.draw_stone(x, y, s_colour) result = g.move_stone(x, y, g_colour, allow_suicide=1) if result.status == gogame.VALID: for (xx,yy) in result.prisoners: s.erase_stone(xx,yy) def place_stones(move_property, s_colour, g_colour): # 添加棋子,主要用于摆死活题 for pos in node.properties[move_property]: if ":" in pos: sx = ord(pos[0])-96 sy = ord(pos[1])-96 ex = ord(pos[3])-96 ey = ord(pos[4])-96 for xx in range(sx, ex + 1): for yy in range(sy, ey + 1): s.draw_stone(xx, yy, s_colour) g.place_stone(xx, yy, g_colour) else: xx = ord(pos[0])-96 yy = ord(pos[1])-96 s.draw_stone(xx, yy, s_colour) g.place_stone(xx, yy, g_colour) def erase_stones(property): # 删除棋子 for pos in node.properties["AE"]: # SGF设置属性,Add Empty,删除棋子 if ":" in pos: sx = ord(pos[0])-96 sy = ord(pos[1])-96 ex = ord(pos[3])-96 ey = ord(pos[4])-96 for xx in range(sx, ex + 1): for yy in range(sy, ey + 1): s.erase_stone(xx, yy) g.erase_stone(xx, yy) else: xx = ord(pos[0])-96 yy = ord(pos[1])-96 s.erase_stone(xx, yy) g.erase_stone(xx, yy) def draw_territory(property, s_colour): # 显示势力范围标记 for pos in node.properties[property]: if ":" in pos: sx = ord(pos[0])-96 sy = ord(pos[1])-96 ex = ord(pos[3])-96 ey = ord(pos[4])-96 for xx in range(sx, ex + 1): for yy in range(sy, ey + 1): s.draw_territory(xx, yy, s_colour) else: xx = ord(pos[0])-96 yy = ord(pos[1])-96 s.draw_territory(xx, yy, s_colour) def draw_shape(property, s_shape): # 在棋子上显示各种不同的记号,三角、方块、圆圈等 for pos in node.properties[property]: if pos == "" or pos == "tt": continue if ":" in pos: sx = ord(pos[0])-96 sy = ord(pos[1])-96 ex = ord(pos[3])-96 ey = ord(pos[4])-96 for xx in range(sx, ex + 1): for yy in range(sy, ey + 1): s.draw_shape(xx, yy, s_shape) else: xx = ord(pos[0])-96 yy = ord(pos[1])-96 s.draw_shape(xx, yy, s_shape) def draw_label(): # SGF标记属性,LaBel,棋盘标签 for value in node.properties["LB"]: pos = value[:2] if len(value) == 4: label = value[3] else: label = value[3:5] xx = ord(pos[0])-96 yy = ord(pos[1])-96 s.draw_label(xx, yy, label) # go through each node in list 历遍节点列表node_list中的每一个节点 for node in node_list: if node.properties.has_key("B"): # 走子属性Black,黑走子 move_stone("B", s.BLACK, gogame.BLACK) elif node.properties.has_key("W"): # 走子属性White,白走子 move_stone("W", s.WHITE, gogame.WHITE) else: if node.properties.has_key("AB"): # 设置属性Add Black,增加黑子 place_stones("AB", s.BLACK, gogame.BLACK) if node.properties.has_key("AW"): # 设置属性Add White,增加白子 place_stones("AW", s.WHITE, gogame.WHITE) if node.properties.has_key("AE"): # 设置属性Add Empty,删除棋子 erase_stones("AE") # display current_node 显示当前节点信息 node = self.current_node if node.properties.has_key("B"): move_stone("B", s.BLACK, gogame.BLACK) draw_shape("B", s.SELECT) elif node.properties.has_key("W"): move_stone("W", s.WHITE, gogame.WHITE) draw_shape("W", s.SELECT) else: if node.properties.has_key("AB"): # 设置属性Add Black,增加黑子 place_stones("AB", s.BLACK, gogame.BLACK) if node.properties.has_key("AW"): # 设置属性Add White,增加白子 place_stones("AW", s.WHITE, gogame.WHITE) if node.properties.has_key("AE"): # 设置属性Add Empty,删除棋子 erase_stones("AE") if node.properties.has_key("TB"): # 属性Territory Black, 黑棋的势力范围 draw_territory("TB", s.BLACK) if node.properties.has_key("TW"): # 属性Territory White, 白棋的势力范围 draw_territory("TW", s.WHITE) if node.properties.has_key("TR"): # 点列表,三角(Triangle) draw_shape("TR", s.TRIANGLE) if node.properties.has_key("CR"): # 点列表,圆形(Circle) draw_shape("CR", s.CIRCLE) if node.properties.has_key("SQ"): # 点列表,方块(Square) draw_shape("SQ", s.SQUARE) if node.properties.has_key("SL"): # 点列表,选择(Selected) draw_shape("SL", s.SELECT) if node.properties.has_key("MA"): # 点列表,X形记号(Mark) draw_shape("MA", s.MARKER) if node.properties.has_key("LB"): # 单文本,标签(Label) draw_label() self.display_state() # 在窗口最下面的状态提示栏左边显示提示信息 self.display_prisoner_count() # 显示被捕获的棋子数量 if self.go_game.last_move_pass: self.display_result("PASS") # 在窗口最下面的状态提示栏中间显示提示信息 else: self.display_result() # 在窗口最下面的状态提示栏中间显示提示信息 if self.show_territory: # 如果已经进入点算双方势力范围模式、计算胜负 self.draw_territory() # 显示势力范围 class PlayGameDialog(tkSimpleDialog.Dialog): # 创建新对局的对话窗口类 def __init__(self, master): self.board_size = 0 self.handicap = 0 tkSimpleDialog.Dialog.__init__(self, master, "New Game 新建对局") def body(self, master): label_1 = Label(master, text="Board Size 棋 盘 大 小", justify=LEFT) self.board_size_entry = Entry(master, name="board_size") # @@@ make a drop down,将来应该变成下拉式选择 self.board_size_entry.insert(0, 19) # 默认棋盘大小为19x19 self.board_size_entry.select_range(0, END) label_2 = Label(master, text="Handicap 让 子 数 目", justify=LEFT) # 默认让子数量为0 self.handicap_entry = Entry(master, name="handicap") self.handicap_entry.insert(0, 0) label_1.pack() self.board_size_entry.pack() label_2.pack() self.handicap_entry.pack() return self.board_size_entry def validate(self): # 检验输入的棋盘大小是否为9, 13, 19中的一种 try: # 检查输入的棋盘大小是否数字 board_size = int(self.board_size_entry.get()) except ValueError: tkMessageBox.showerror("Invalid Board Size", "Board Size must be a number", parent=self) return 0 if board_size not in [9,13,19]: # 如果输入的棋盘大小不是9,13,19其中一种 tkMessageBox.showerror("Invalid Board Size", "Board Size must be 9, 13 or 19", parent=self) return 0 try: # 检查输入的让子数量是否数字 handicap = int(self.handicap_entry.get()) except ValueError: tkMessageBox.showerror("Invalid Handicap", "Handicap must be a number", parent=self) return 0 if handicap < 0 or handicap > 9: # 如果输入的让子数量<0或>9 tkMessageBox.showerror("Invalid Handicap", "Handicap must be between 0 and 9", parent=self) return 0 self.board_size = board_size # 根据正确的输入设置棋盘大小 self.handicap = handicap # 根据正确的输入设置让子数量 return 1 # 返回1,表示什么? class PyGo: # 创建主窗口的类 def __init__(self, filename=None): self.scale = 33 # 棋盘格子的宽度 self.root = Tk() # self.root初始化为Tk类对象。每个Tk程序都有一个,而且只有一个root widget self.root.title(app_name + " " + version) # 设置窗口标题 self.create_menu() # 初始化菜单 frame = Frame(self.root) self.canvas = Canvas(frame, width=self.scale*21, height=self.scale*21, background="#DD8") # background是画布canvas的底色 self.canvas.pack(side=TOP) status = Frame(frame) self.label1 = Label(status, relief="sunken") self.label1.pack(side=LEFT, expand=YES, fill=BOTH) self.label2 = Label(status, relief="sunken") self.label2.pack(side=RIGHT, expand=YES, fill=BOTH) self.label3 = Label(status, relief="sunken") self.label3.pack(side=BOTTOM, expand=YES, fill=BOTH) status.pack(side=BOTTOM, fill=BOTH) frame.grid(column=0, row=0, rowspan=2) self.variations = ScrolledText.ScrolledText(self.root, width=40, height=20, state=DISABLED, background="#EEE", relief="flat") self.variations.grid(column=1, row=0) self.properties = ScrolledText.ScrolledText(self.root, width=40, height=20, state=DISABLED, background="#EEE" , relief="flat") self.properties.grid(column=1, row=1) self.game = None if filename: self.open_sgf(filename) # 打开sgf文件?? def create_menu(self): # 创建菜单 menu_bar = Menu(self.root) file_menu = Menu(menu_bar, name="file", tearoff=0) menu_bar.add_cascade(menu=file_menu, label="File 文件", underline=0) file_menu.add_command(label="Open SGF 打开SGF文件", command=self.open_sgf, underline=0) file_menu.add_command(label="New Game 新建对局", command=self.play_game, underline=0) file_menu.add_command(label="Save SGF 保存为SGF文件", command=self.save_sgf, underline=0) # @@@ should be disabled file_menu.add_command(label="Quit 退出", command=self.quit, underline=0) help_menu = Menu(menu_bar, name="help", tearoff=0) menu_bar.add_cascade(menu=help_menu, label="Help 帮助", underline=0) help_menu.add_command(label="Keys... 鼠标、键盘的使用", command=self.show_help, underline=0) help_menu.add_command(label="About... 关于", command=self.show_about, underline=0) self.root.config(menu=menu_bar) ### MENU ACTIONS def open_sgf(self, filename=None): # 打开sgf棋谱文件 if filename: fn = filename else: fn = tkFileDialog.askopenfilename(filetypes=[("Smart Game Format","*.sgf"),("All files","*")]) if fn: try: parser = sgf.Parser() # Parser()是sgf.py中定义的一个类 f = file(fn) # 打开sgf文件 x = f.read() # 读取sgf文件 f.close() # 关闭文件 collection = sgf.Collection(parser) # 将collection初始化成Collection类对象 parser.parse(x) # 调用Parse类中parser()函数将sgf文件内容转化成Gametree self.game = Game(self, collection, scale=self.scale) # 用collection对象做参数创建Game类对象 except sgf.ParseException, x: # 无效的文件或文件格式无法正确解释 tkMessageBox.showerror("Invalid File", "The SGF file could not be parsed [%s]" % x) def play_game(self): # 创建新的对局,两人在一台机器上对弈,目前没有网络对弈功能 play_game_dialog = PlayGameDialog(self.root) # 调用PlayGameDialog对话窗口,确定棋盘大小和让子数 # PlayGameDialog对话窗口返回play_game_dialog.board_size和play_game_dialog.handicap两个整数 if play_game_dialog.board_size: self.game = Game(self, None, size=play_game_dialog.board_size, handicap=play_game_dialog.handicap, scale=self.scale) def save_sgf(self): # 将当前局面保存为sgf文件 if self.game: # 调用tkFileDialog的save as file对话窗口 fn = tkFileDialog.asksaveasfilename(filetypes=[("Smart Game Format", "*.sgf"), ("All files", "*")]) if fn: # 如果已经输入文件名 f = file(fn, "w") # 打开文件 self.game.output(f) # 将Gametree保存为sgf文件 f.close() # 关闭文件 def quit(self): # 退出pygo程序 self.root.destroy() def show_help(self): # 显示帮助信息 tkMessageBox.showinfo("Help", """click to move stones 点击鼠标左键走子\n 注:如果鼠标点击在棋盘外侧当作弃权一手,如果一方弃权后另一方接着也弃权则棋局终止\n left-arrow to go to previous node 键跳到上一个节点 right-arrow to go to next node 键跳到下一个节点 up-arrow to go to previous variation 键跳到上一个分支 down-arrow to go to next variation 键跳到下一个分支 shift-left-arrow to go to first node in variation Shift+ 键跳到分支的第一个节点 shift-right-arrow to go to last node in variation Shift+键跳到分支的最后一个节点\n 'v' to make a new variation 'v'创建新分支 'u' to undo a move 'u'悔棋 'p' to toggle play / place black / place white 'p'交替走子 / 添加黑子 / 添加白子 'c' to continue playing from end of a loaded game 'c'在导入的棋谱末端开始继续走子 't' to toggle territory/score 't'切换到点算状态,计算胜负 'i' to insert a new node 'i'插入一个新节点 """) def show_about(self): # 显示About信息 tkMessageBox.showinfo("About 关于", "%s %s\nby James Tauber\nhttp://jtauber.com/\n \nMinor modification and Chinese comments made by ggzzzzzzz@gmail" % (app_name, version)) if len(sys.argv) > 1: # 如果pygo执行时带有[sgf_file]参数,就进入打谱模式 p = PyGo(sys.argv[1]) else: # 如果pygo执行时没有带参数,等待用户选择菜单 p = PyGo() p.root.mainloop() # 进入Tk循环