# coding: utf-8 ### GOGAME.PY # 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 # Class representing the current state of a Go game with 表示对局状态的类 # knowledge of capture rules and ko as well as utility 提子、打劫的规则 # functions for performing various calculations 及各种算法 ### MoveResult VALID = 0 # 有效落子 OCCUPIED = 1 # 该点已经有棋子 SUICIDE = 2 # 自杀 KO = 3 # 违反打劫规则 COMPLETED = 4 # 棋局终止 class MoveResult: def __init__(self, status, prisoners=[]): self.status = status self.prisoners = prisoners ### GoGame EMPTY = 0 # 表示没有棋子,空 BLACK = 1 # 表示黑子 WHITE = -1 # 表示白子 class GoGame: def __init__(self, size=19, handicap=0): # 数据初始化 # 初始化的方式:self.go_game = g = gogame.GoGame(size, handicap=handicap) self.handicap = handicap # 让子数量 self.size = size # 棋盘大小 self.contents = [] # contents是一个列表,列表成员也是列表,用于表示该点是否有棋子 for i in range(size + 1): # 初始化棋盘,361个点都为空的状态 self.contents.append([EMPTY] * (size + 1)) if handicap: # 如果有让子,第一手棋为白子 self.to_play = WHITE else: # 如果没有让子,第一手棋为黑子 self.to_play = BLACK self.white_prisoners = 0 # 白棋被提掉的棋子数量 self.black_prisoners = 0 # 黑棋被提掉的棋子数量 self.move_number = 1 # 设置手数 self.last_move_pass = 0 # 上一手是否弃权 self.game_completed = 0 # 棋局终止 self.last_ko_move = None # 上一手提子的落子,用于判断当前落子是否违反打劫规则 self.last_ko_prisoner = None # 被捕获的棋子,用于判断当前落子是否违反打劫规则 def pass_move(self): # 弃权一手 # 使用的变量 uses: to_play, last_move_pass # 改变的变量 changes: to_play, move_number, game_completed self.move_number = self.move_number + 1 # 手数加一 if self.to_play == BLACK: # 如果这一手该黑子下,下一手则为白子 self.to_play = WHITE else: # 如果这一手该白子下,下一手则为黑子 self.to_play = BLACK if self.last_move_pass: # 如果上一手弃权 self.game_completed = 1 # 上一手弃权,加上这一手弃权,就认为双方同意棋局终止 self.last_move_pass = 1 # 设置弃权标志 def is_occupied(self, x, y): # 判断某一个点是否已经有棋子 # uses: contents return (self.contents[x][y] != EMPTY) def erase_stone(self, x, y): # 提掉某一个点上的棋子 # changes: contents self.contents[x][y] = EMPTY # 提子就将该点状态设置为空 def place_stone(self, x, y, color): # 在某一个点上放上黑子或白子 # changes: contents self.contents[x][y] = color # 走子则将该点状态设置为棋子颜色 def move_stone(self, x, y, color=None, allow_suicide=None): # 走一步棋到x,y坐标 # print "move_stone" # uses: to_play, contents, game_completed, last_ko_move, last_ko_prisoner # changes: last_move_pass, contents, last_ko_move, last_ko_prisoner # white_prisoner, black_prisoner, black_go, move_number if color == None: color = self.to_play # 这一手该谁下,黑或白 if self.contents[x][y] != EMPTY: # 如果该点上有棋子, return MoveResult(OCCUPIED) # 设置走子结果为1,表示该点上有棋子 if self.game_completed: # 如果棋局终止变量为1 return MoveResult(COMPLETED) # 设置走子结果为4,表示对局终止 self.contents[x][y] = color # 将该点状态设置为棋子颜色 if color == BLACK: # 设置对方棋子颜色 opposite = WHITE else: opposite = BLACK prisoners = [] # 用于记录捕获棋子的列表 for (xx,yy) in self.calculate_grouped_adjacent(x,y)[opposite]: # 历遍与(x, y)相反颜色、与(x, y)相邻的点 # 如果落子处被敌人围困(与敌方有接触就算是战俘?),新落子增加到战俘数组中。calculate_grouped_adjacent要再研究 new_prisoners = self.calculate_prisoners(xx,yy) for new_prisoner in new_prisoners: if not new_prisoner in prisoners: prisoners.append(new_prisoner) # 历遍结果:prisoners列表包含捕获的棋子 suicide = 0 # 用于表示落子之后是否形成自杀的变量。PyGo默认规则不允许自杀。中国规则、日本规则不允许自杀,应氏规则允许自杀。 if len(prisoners) == 0 and len(self.calculate_prisoners(x,y)) > 0: # 如果捕获对方棋子数量为0,而自己落子所属的棋串被捕获,就是出现自杀的情况 if allow_suicide: # 如果规则允许自杀 new_prisoners = self.calculate_prisoners(x, y) for new_prisoner in new_prisoners: if not new_prisoner in prisoners: prisoners.append(new_prisoner) if len(prisoners) > 0: suicide = 1 else: # 如果规则不允许自杀 self.contents[x][y] = EMPTY # reverse the move 如果落子后不能杀掉对方的棋,而自己落子构成的棋块被杀,就是自杀。 return MoveResult(SUICIDE) # 这一段代码用于检验是否违反打劫规则 if len(prisoners) == 1: # 如果这一手捕获一个棋子。 # 如果这一手捕获一个棋子,而这一棋子是上一手提掉一子的着手,就意味这一手违反打劫规则 if self.last_ko_move == prisoners[0] and self.last_ko_prisoner == (x, y): self.contents[x][y] = EMPTY # reverse the move,违反打劫规则,取消这一走子 return MoveResult(KO) # 设置走子结果为3,表示违反打劫规则 else: self.last_ko_move = (x, y) # 这一手棋构成提一子 self.last_ko_prisoner = prisoners[0] # 这一手棋捕获的一个棋子 else: # 如果这一手捕获一个棋子,而这一棋子不是上一手提掉一子的着手。不违反打劫规则 self.last_ko_move = None self.last_ko_prisoner = None # 处理自杀 for (xx, yy) in prisoners: if not suicide: # 如果不是自杀 if color == BLACK: self.white_prisoners = self.white_prisoners + 1 else: self.black_prisoners = self.black_prisoners + 1 else: # 如果是自杀 if color == BLACK: self.black_prisoners = self.black_prisoners + 1 else: self.white_prisoners = self.white_prisoners + 1 self.contents[xx][yy] = EMPTY # remove the stones,被捕获棋子的点设置为空状态 if color == BLACK: # 改变self.to_play,控制下一手颜色 self.to_play = WHITE else: self.to_play = BLACK self.move_number = self.move_number + 1 # 走子手数加一 self.last_move_pass = 0 # 表示这一手不是弃权 return MoveResult(VALID, prisoners) # 返回走子结果“有效”和被捕获的棋子 def calculate_grouped_adjacent(self, x, y, dead_points=[]): # 计算与某一点相邻的空点、黑子、白子 # 这个函数没有被pygo.py直接调用。pygo.py调用calculate_group,然后由calculate_group调用这个函数。 # pygo.py handle_move_event ->gogame.py move_stone ->calculate_prisoners ->calculate_grouped_adjacent # 运行结果改变了adjacent列表 # uses: contents, size #print self.move_number," : ", x,y adjacent = [[],[],[]] # @@@ assumes that EMPTY, BLACK and WHITE are 0-2 # adjacent[0]表示相邻的空点,adjacent[1]表示相邻的黑子,adjacent[2]表示相邻的白子 if x != 1: if (x - 1, y) in dead_points: color = EMPTY else: color = self.contents[x - 1][y] adjacent[color].append((x-1,y)) if y != 1: if (x, y - 1) in dead_points: color = EMPTY else: color = self.contents[x][y - 1] adjacent[color].append((x, y - 1)) if x != self.size: if (x + 1, y) in dead_points: color = EMPTY else: color = self.contents[x + 1][y] adjacent[color].append((x+1,y)) if y != self.size: if (x, y + 1) in dead_points: color = EMPTY else: color = self.contents[x][y + 1] adjacent[color].append((x,y+1)) #print adjacent return adjacent def calculate_prisoners(self, x, y): # 如果(x, y)的棋子(黑或白)没有气,把棋串中的点记录在points_looked_at列表里返回 [(x,y),(x,y)..] # 这个函数没有被pygo.py使用 # uses: contents 列表contents表示棋盘上每一个点的状态(空/黑/白) points_looked_at = [] points_to_look_at = [] points_to_look_at.append((x, y)) while 1: if len(points_to_look_at) == 0: # 如果所有的点都已经计算过, break # 就退出while循环体 else: (xx, yy) = points_to_look_at.pop() # 第一次循环时(xx, yy) == (x, y) points_looked_at.append((xx, yy)) # 表示这个点已被计算处理 adjacent = self.calculate_grouped_adjacent(xx, yy) # 计算 if len(adjacent[0]) > 0: # 如果存在相邻的空点。adjacent[0]表示相邻的空点,[1]表示相邻的黑子,[2]表示相邻的白子 return [] # 返回空列表,表示就没有捕获的棋子 else: for point in adjacent[self.contents[xx][yy]]: # 历遍与(x, y)相邻、与(x, y)相同颜色的点 if not point in points_looked_at and not point in points_to_look_at: # 如果该点不在已经被计算的点列表中,而且也不在等待计算的点列表中 points_to_look_at.append(point) # 将该点加入等待计算的点列表中 # print points_looked_at return points_looked_at def calculate_group(self, x, y, dead_points=[]): # 用于计算棋串?从一个点向周围扩展? # pygo.py:点算、计算胜负的时候才用,toggle_group_alive_dead调用这个函数, # uses: contents 列表contents表示棋盘上每一个点的状态(空/黑/白) points_looked_at = [] points_to_look_at = [] points_to_look_at.append((x, y)) while 1: if len(points_to_look_at) == 0: break else: (xx, yy) = points_to_look_at.pop() points_looked_at.append((xx, yy)) adjacent = self.calculate_grouped_adjacent(xx, yy, dead_points) if (xx, yy) in dead_points: color = EMPTY else: color = self.contents[xx][yy] for point in adjacent[color]: if not point in points_looked_at and not point in points_to_look_at: points_to_look_at.append(point) #print self.move_number, points_looked_at return points_looked_at def calculate_territory(self, dead_points=[]): # 计算势力范围(围住的目数) # uses: size, contents 列表contents表示棋盘上每一个点的状态(空/黑/白) unknown_territory = [] black_territory = [] white_territory = [] points_looked_at = [] for x in range(1, self.size + 1): for y in range(1, self.size + 1): if self.contents[x][y] == EMPTY or (x, y) in dead_points: if (x, y) in points_looked_at: continue group = self.calculate_group(x, y, dead_points) black_adjacent = 0 white_adjacent = 0 for (xx, yy) in group: adjacent = self.calculate_grouped_adjacent(xx, yy, dead_points) black_adjacent = black_adjacent + len(adjacent[BLACK]) white_adjacent = white_adjacent + len(adjacent[WHITE]) if white_adjacent and not black_adjacent: white_territory.extend(group) elif black_adjacent and not white_adjacent: black_territory.extend(group) else: unknown_territory.extend(group) points_looked_at.extend(group) # @@@ some code might assume position of black_territory and white_territory is BLACK and WHITE return (unknown_territory, black_territory, white_territory) def calculate_score(self, dead_points=[]): # 计算胜负,采用日本数目规则 # uses: white_prisoners, black_prisoners, contents territory = self.calculate_territory(dead_points) # @@@ assumes return tuple from territory used BLACK and WHITE black_score = self.white_prisoners + len(territory[BLACK]) # 黑方目数=白方死子+黑方围住的势力范围 white_score = self.black_prisoners + len(territory[WHITE]) # 白方目数=黑方死子+白方围住的势力范围 for (x, y) in dead_points: if self.contents[x][y] == BLACK: white_score = white_score + 1 # 黑方被提掉的棋子算入白方的目数 elif self.contents[x][y] == WHITE: black_score = black_score + 1 # 白方被提掉的棋子算入黑方的目数 return (black_score, white_score) # 返回最终计算结果,黑子目数、白子目数