def is_valid_move(self, pos): land_posx, land_posy = util.sgf_to_pos(pos) board_copy = copy.deepcopy(self.board) # 最先判断落子位置是不是空点,如果是不是空点,那么一定是不可以下的。 if self.board[land_posx][land_posy] != EMPTY_STONE: return False # 判断它的周围是不是有空点,如果有空点,那么一定是可以下的。 for i in range(4): if 0 <= land_posx + dx[i] < 19 and 0 <= land_posy + dy[i] < 19: if self.board[land_posx + dx[i]][land_posy + dy[i]] == EMPTY_STONE: return True # 判断吃子的话,先算一下zobrist哈希 num_pos = util.pos_to_num((land_posx, land_posy)) hash = self.zob_history[-1] if self._current_player() == SIDE_BLACK: c_zb_state = zb.STATE_BLACK o_zb_state = zb.STATE_WHITE current_stone = BLACK_STONE opposite_stone = WHITE_STONE else: c_zb_state = zb.STATE_WHITE o_zb_state = zb.STATE_BLACK current_stone = WHITE_STONE opposite_stone = BLACK_STONE # 判断是否吃子了,如果吃子了,那么应该是可以下的,但是要判断打劫,用zb哈希 has_eat = False for key, value in self.group[self._opposite_player()].items(): if value.count_liberty() == 1: if value.has_liberty(util.pos_to_num((land_posx, land_posy))): has_eat = True for s in self.group[self._opposite_player()][key].stone: px, py = util.num_to_pos(s) board_copy[px][py] = EMPTY_STONE # 取消对方子的状态 hash = zb.get_new_hash(hash, self.zob_arr, o_zb_state, s) # 赋予空点的状态 hash = zb.get_new_hash(hash, self.zob_arr, zb.STATE_EMPTY, s) board_copy[land_posx][land_posy] = current_stone hash = zb.get_new_hash(hash, self.zob_arr, zb.STATE_EMPTY, num_pos) hash = zb.get_new_hash(hash, self.zob_arr, c_zb_state, num_pos) if has_eat and len(self.zob_history) > 2: if self.zob_history[-2] == hash: # print 'ko rule!' return False if has_eat: return True # 如果没有吃子,那么可能就是往对方里面填子,那么用dfs遍历一下,看看这个棋串周围是否有空点 # pos_x, pos_y是坐标! self.found = False self._dfs(board_copy, land_posx, land_posy, current_stone) if self.found is False: self.found = False return False return True
def place_stone_num(self, pos): land_posx, land_posy = util.num_to_pos(pos) current_player = self._current_player() opposite_player = self._opposite_player() num_pos = util.pos_to_num((land_posx, land_posy)) current_stone, opposite_stone = None, None # 如果是停一手,则做如下操作 """ mistake !!!! 停一手忘记更新棋盘了! """ if land_posx < 0: self.history[current_player].append(pos) self.zob_history.append(self.zob_history[-1]) """ 2019年1月7日01:54:54修补该bug """ """ if current_player == SIDE_BLACK: current_stone = BLACK_STONE opposite_stone = WHITE_STONE else: current_stone = WHITE_STONE opposite_stone = BLACK_STONE """ self.round += 1 return # 先判断是否合法 if self.is_valid_move(util.pos_to_sgf((land_posx, land_posy))) is False: # print 'Invalid!' return False # 如果合法,则做这些操作: """ 1. 减去对方棋串的气 2. 将新的子加入本方的棋串中 2.1 遍历本方所有棋串的气,如果有重合,记录备用 2.2 将这些有重合的都加入这个新的棋串中去 2.3 删除那些重合加入的棋串 2.4 重新计算新串的气 3. 对对面所有没气了的棋串:如果对面没有没气了的棋串,并且本方的气是0,说明自尽,那么对本方这块棋的周围, 3.1 对于每个没气了的棋串的每个子,看上下左右(注意边界) 3.2 上下左右如果有本方的棋子,那么加入一个列表备用 3.3 删除这个棋串 4. 重新生成棋盘 5. 记录的这些黑子,判断属于哪些棋块,对这些棋块重新算气 """ # 合法了的话,棋盘更新 if current_player == SIDE_BLACK: self.board[land_posx][land_posy] = BLACK_STONE current_stone = BLACK_STONE opposite_stone = WHITE_STONE else: self.board[land_posx][land_posy] = WHITE_STONE current_stone = WHITE_STONE opposite_stone = BLACK_STONE # 1. 减去对方棋串的气,并用dead列表记录哪些死透了的棋子 dead = [] for key, value in self.group[opposite_player].items(): assert isinstance(value, Group) value.remove_liberty(num_pos) if value.count_liberty() == 0: dead.append(key) # 遍历本方所有棋串的气,如果有重合,记录备用在merge里面 merge = [] for key, value in self.group[current_player].items(): assert isinstance(value, Group) if value.has_liberty(num_pos): merge.append(key) # 2.2 新建一个棋串,将这些有重合的都加入这个新的棋串中去 self.group[current_player][self.round] = Group(self.round) self.group[current_player][self.round].add_stone(num_pos) # 2.3 删除那些重合加入的棋串 for ids in merge: self.group[current_player][self.round].merge_stone(self.group[current_player][ids]) self.group[current_player].pop(ids) # 2.4 重新计算新串的气(有一个不必要的参数side) self.group[current_player][self.round].recount_liberty(current_player, self.board) # 3 对面所有没气了的棋串,在dead里面,删掉这些子,但是记录一下他们 record_dead_stone = set() for d in dead: for st in self.group[opposite_player][d].stone: record_dead_stone.add(st) posx, posy = util.num_to_pos(st) # 在这里清除了对方的死子 self.board[posx][posy] = EMPTY_STONE # 由于记录了死子的位置,所以看一下它的上下左右,有没有本方的子 # 有的话,记录本方这些子的棋串号 recount_liberty_group = set() for dead_pos in record_dead_stone: posx, posy = util.num_to_pos(dead_pos) for i in range(4): if 0 <= posx + dx[i] < 19 and 0 <= posy + dy[i] < 19: if self.board[posx + dx[i]][posy + dy[i]] == current_stone: for key, value in self.group[current_player].items(): if value.has_stone(util.pos_to_num((posx + dx[i], posy + dy[i]))): recount_liberty_group.add(key) # 删掉这些棋串 for del_ids in dead: self.group[opposite_player].pop(del_ids) # 对需要重新算气的本方棋串重新算气 for rec in recount_liberty_group: self.group[current_player][rec].recount_liberty(current_player, self.board) if current_player == SIDE_BLACK: c_zb_state = zb.STATE_BLACK o_zb_state = zb.STATE_WHITE else: c_zb_state = zb.STATE_WHITE o_zb_state = zb.STATE_BLACK hash = self.zob_history[-1] # 取消本方落子的那个空点 hash = zb.get_new_hash(hash, self.zob_arr, zb.STATE_EMPTY, util.pos_to_num((land_posx, land_posy))) # 本方落子在那个空点 hash = zb.get_new_hash(hash, self.zob_arr, c_zb_state, util.pos_to_num((land_posx, land_posy))) for ds in record_dead_stone: hash = zb.get_new_hash(hash, self.zob_arr, o_zb_state, ds) hash = zb.get_new_hash(hash, self.zob_arr, zb.STATE_EMPTY, ds) self.zob_history.append(hash) self.history[current_player].append(pos) # self.print_board() self.round += 1 return True