def get_game_ended(state): """ :param state: :return: 0/1 = game not ended / game ended respectively """ m, n = state_utils.get_board_size(state) return int(np.count_nonzero(state[govars.DONE_CHNL] == 1) == m * n)
def get_areas(state): ''' Return black area, white area Use DFS helper to find territory. ''' m, n = state_utils.get_board_size(state) visited = np.zeros((m, n), dtype=np.bool) black_area = 0 white_area = 0 # loop through each intersection on board for r, c in itertools.product(range(m), range(n)): # count pieces towards area if state[BLACK][r, c] > 0: black_area += 1 elif state[WHITE][r, c] > 0: white_area += 1 # do DFS on unvisited territory elif not visited[r, c]: player, area = state_utils.explore_territory( state, (r, c), visited) # add area to corresponding player if player == BLACK: # BLACK black_area += area elif player == WHITE: # WHITE white_area += area return black_area, white_area
def get_action_size(state=None, board_size: int = None): # return number of actions if state is not None: m, n = state_utils.get_board_size(state) elif board_size is not None: m, n = board_size, board_size else: raise RuntimeError('No argument passed') return m * n + 1
def get_next_states(state, batch_action1d, group_map=None, canonical=False): """ Does not change the given state """ if group_map is None: group_map = state_utils.get_group_map(state) m, n = state_utils.get_board_size(state) pass_idcs = np.where(batch_action1d == m * n) non_pass_idcs = np.where(batch_action1d != m * n) batch_size = len(batch_action1d) board_shape = state.shape[1:] batch_action2d = np.empty((batch_size, 2), dtype=np.int) batch_action2d[:, 0] = batch_action1d // n batch_action2d[:, 1] = batch_action1d % n batch_action2d[np.where(batch_action1d == m * n)] = 0 player = state_utils.get_turn(state) opponent = 1 - player previously_passed = GoGame.get_prev_player_passed(state) batch_states = np.tile(state, (batch_size, 1, 1, 1)) # Check move is valid assert (batch_states[non_pass_idcs, govars.INVD_CHNL, batch_action2d[non_pass_idcs, 0], batch_action2d[ non_pass_idcs, 1]] == 0).all(), "Invalid move" batch_group_maps = [[group_map[0].copy(), group_map[1].copy()] for _ in range(batch_size)] batch_single_kill = [None for _ in range(batch_size)] batch_killed_groups = [set() for _ in range(batch_size)] batch_adj_locs, batch_surrounded = state_utils.get_batch_adj_data(state, batch_action2d) if previously_passed: batch_states[pass_idcs, govars.DONE_CHNL] = 1 else: batch_states[pass_idcs, govars.PASS_CHNL] = 1 # Non passes batch_states[non_pass_idcs, govars.PASS_CHNL] = 0 # Add pieces batch_states[non_pass_idcs, player, batch_action2d[non_pass_idcs, 0], batch_action2d[non_pass_idcs, 1]] = 1 batch_data = enumerate(zip(batch_action1d, batch_action2d, batch_states, batch_group_maps, batch_adj_locs, batch_killed_groups)) for i, (action_1d, action_2d, state, group_map, adj_locs, killed_groups) in batch_data: # if the current player passes if action_1d == m * n: continue action_2d = tuple(action_2d) # Get all adjacent information adj_own_groups, adj_opp_groups = state_utils.get_adjacent_groups(group_map, adj_locs, player) # Go through opponent groups for group in adj_opp_groups: assert action_2d in group.liberties, (action_2d, player, group, state) if len(group.liberties) <= 1: # Killed group killed_groups.add(group) # Remove group in board and group map for loc in group.locations: state[opponent, loc[0], loc[1]] = 0 group_map[opponent].remove(group) # Metric for ko-protection if len(group.locations) <= 1 and batch_single_kill[i] is None: batch_single_kill[i] = next(iter(group.locations)) adj_opp_groups.difference_update(killed_groups) # Update surviving adjacent opponent groups by removing liberties by the new action for opp_group in adj_opp_groups: assert action_2d in opp_group.liberties, (action_2d, opp_group, adj_opp_groups) # New group copy group_map[opponent].remove(opp_group) opp_group = opp_group.copy() group_map[opponent].add(opp_group) opp_group.liberties.remove(action_2d) # Update adjacent own groups that are merged with the action if len(adj_own_groups) > 0: merged_group = adj_own_groups.pop() group_map[player].remove(merged_group) merged_group = merged_group.copy() else: merged_group = govars.Group() group_map[player].add(merged_group) # Locations from action and adjacent groups merged_group.locations.add(action_2d) for own_group in adj_own_groups: merged_group.locations.update(own_group.locations) merged_group.liberties.update(own_group.liberties) group_map[player].remove(own_group) # Liberties from action for adj_loc in adj_locs: if np.count_nonzero(state[[govars.BLACK, govars.WHITE], adj_loc[0], adj_loc[1]]) == 0: merged_group.liberties.add(adj_loc) if action_2d in merged_group.liberties: merged_group.liberties.remove(action_2d) # More work to do if we killed if len(killed_groups) > 0: killed_map = np.zeros(board_shape) for group in killed_groups: for loc in group.locations: killed_map[loc] = 1 # Update own groups adjacent to opponent groups that we just killed killed_liberties = ndimage.binary_dilation(killed_map) affected_idcs = set(zip(*np.nonzero(state[player] * killed_liberties))) groups_to_update = set() for group in group_map[player]: if not affected_idcs.isdisjoint(group.locations): groups_to_update.add(group) all_pieces = np.sum(state[[govars.BLACK, govars.WHITE]], axis=0) empties = (1 - all_pieces) for group in groups_to_update: group_matrix = np.zeros(board_shape) for loc in group.locations: group_matrix[loc] = 1 additional_liberties = ndimage.binary_dilation(group_matrix) * empties * killed_map additional_liberties = set(zip(*np.where(additional_liberties))) group_map[player].remove(group) group = group.copy() group_map[player].add(group) group.liberties.update(additional_liberties) # Update illegal moves batch_states[:, govars.INVD_CHNL] = state_utils.get_batch_invalid_moves(batch_states, batch_group_maps, player) # If group was one piece, and location is surrounded by opponents, # activate ko protection for i, (single_kill, killed_groups, surrounded) in enumerate(zip(batch_single_kill, batch_killed_groups, batch_surrounded)): if single_kill is not None and len(killed_groups) == 1 and surrounded: state[govars.INVD_CHNL, single_kill[0], single_kill[1]] = 1 # Switch turn state_utils.batch_set_turn(batch_states) if canonical: GoGame.set_batch_canonical_form(batch_states, batch_group_maps, opponent) return batch_states, batch_group_maps
def get_prev_player_passed(state): m, n = state_utils.get_board_size(state) return np.count_nonzero(state[govars.PASS_CHNL] == 1) == m * n
def get_next_state(state, action, group_map=None, inplace=False): """ Does not change the given state :param state: :param action: :return: The next state """ # check if game is already over if GoGame.get_game_ended(state) != 0: raise Exception('Attempt to step at {} after game is over'.format(action)) state = np.copy(state) single_kill = None empty_adjacents_before_kill = None if group_map is None: group_map = state_utils.get_all_groups(state) # if the current player passes if action == GoGame.get_action_size(state) - 1: # if two consecutive passes, game is over if GoGame.get_prev_player_passed(state): state_utils.set_game_ended(state) else: state_utils.set_prev_player_passed(state) else: # This move was not a pass state_utils.set_prev_player_passed(state, 0) player = state_utils.get_turn(state) opponent = 1 - player m, n = state_utils.get_board_size(state) # convert the move to 2d action = (action // m, action % n) # Check move is valid if not state_utils.is_within_bounds(state, action): raise Exception("{} Not Within bounds".format(action)) elif state[INVD_CHNL, action[0], action[1]] > 0: raise Exception("Invalid Move", action, state) # Get all adjacent information adjacent_locations = state_utils.get_adjacent_locations(state, action) adj_own_groups, adj_opp_groups = state_utils.get_adjacent_groups(state, group_map, adjacent_locations, player) if not inplace: # Start new group map group_map = np.copy(group_map) # Go through opponent groups killed_groups = set() empty_adjacents_before_kill = adjacent_locations.copy() for group in adj_opp_groups: assert action in group.liberties, (action, group, state[[BLACK, WHITE]]) empty_adjacents_before_kill.difference_update(group.locations) if len(group.liberties) <= 1: # Killed group killed_groups.add(group) # Remove group in board and group map for loc in group.locations: group_map[loc] = None state[opponent, loc[0], loc[1]] = 0 # Metric for ko-protection if len(group.locations) <= 1: if single_kill is not None: single_kill = None else: single_kill = next(iter(group.locations)) adj_opp_groups.difference_update(killed_groups) # Add the piece! state[player, action[0], action[1]] = 1 # Update surviving adjacent opponent groups by removing liberties by the new action for opp_group in adj_opp_groups: assert action in opp_group.liberties, (action, opp_group, adj_opp_groups) if not inplace: # New group copy opp_group = opp_group.copy() for loc in opp_group.locations: group_map[loc] = opp_group opp_group.liberties.remove(action) # Update adjacent own groups that are merged with the action if len(adj_own_groups) > 0: merged_group = adj_own_groups.pop() if not inplace: merged_group = merged_group.copy() for loc in merged_group.locations: group_map[loc] = merged_group else: merged_group = Group() # Locations from action and adjacent groups merged_group.locations.add(action) group_map[action] = merged_group for own_group in adj_own_groups: for loc in own_group.locations: group_map[loc] = merged_group merged_group.locations.add(loc) merged_group.liberties.update(own_group.liberties) # Liberties from action for adj_loc in adjacent_locations: if np.count_nonzero(state[[BLACK, WHITE], adj_loc[0], adj_loc[1]]) == 0: merged_group.liberties.add(adj_loc) if action in merged_group.liberties: merged_group.liberties.remove(action) # More work to do if we killed if len(killed_groups) > 0: killed_map = np.zeros(state.shape[1:]) for group in killed_groups: for loc in group.locations: killed_map[loc] = 1 # Update own groups adjacent to opponent groups that we just killed killed_liberties = ndimage.binary_dilation(killed_map) affected_group_matrix = state[player] * killed_liberties groups_to_update = set(group_map[np.nonzero(affected_group_matrix)]) all_pieces = np.sum(state[[BLACK, WHITE]], axis=0) empties = (1 - all_pieces) for group in groups_to_update: group_matrix = group_map == group additional_liberties = ndimage.binary_dilation(group_matrix) * empties * killed_map additional_liberties = np.argwhere(additional_liberties) if not inplace: group = group.copy() for loc in group.locations: group_map[loc] = group for liberty in additional_liberties: group.liberties.add(tuple(liberty)) # Update illegal moves state_utils.set_invalid_moves(state, group_map) # If group was one piece, and location is surrounded by opponents, # activate ko protection if single_kill is not None and len(empty_adjacents_before_kill) <= 0: state[INVD_CHNL, single_kill[0], single_kill[1]] = 1 # Switch turn state_utils.set_turn(state) return state, group_map
def get_next_state(state, action): """ Does not change the given state :param state: :param action: :return: The next state """ # check if game is already over if GoGame.get_game_ended(state) != 0: raise Exception( 'Attempt to step at {} after game is over'.format(action)) state = np.copy(state) # if the current player passes if action == GoGame.get_action_size(state) - 1: # if two consecutive passes, game is over if GoGame.get_prev_player_passed(state): state_utils.set_game_ended(state) else: state_utils.set_prev_player_passed(state) # Update invalid channel state_utils.reset_invalid_moves(state) state_utils.add_invalid_moves(state) # Switch turn state_utils.set_turn(state) # Return event return state player = state_utils.get_turn(state) m, n = state_utils.get_board_size(state) # convert the move to 2d action = (action // m, action % n) # Check move is valid if not state_utils.is_within_bounds(state, action): raise Exception("{} Not Within bounds".format(action)) elif state[INVD_CHNL][action] > 0: raise Exception("Invalid Move", action, state) state_utils.reset_invalid_moves(state) # Get all adjacent groups _, opponent_groups = state_utils.get_adjacent_groups(state, action) # Go through opponent groups killed_single_piece = None empty_adjacents_before_kill = state_utils.get_adjacent_locations( state, action) for group in opponent_groups: empty_adjacents_before_kill = empty_adjacents_before_kill - group.locations if len(group.liberties) <= 1: assert action in group.liberties # Remove group in board for loc in group.locations: # TODO: Hardcoded other player. Make more generic state[1 - player][loc] = 0 # Metric for ko-protection if len(group.locations) <= 1: if killed_single_piece is not None: killed_single_piece = None else: killed_single_piece = group.locations.pop() # If group was one piece, and location is surrounded by opponents, # activate ko protection if killed_single_piece is not None and len( empty_adjacents_before_kill) <= 0: state[INVD_CHNL][killed_single_piece] = 1 # Add the piece! state[player][action] = 1 # Update illegal moves state_utils.add_invalid_moves(state) # This move was not a pass state_utils.set_prev_player_passed(state, 0) # Switch turn state_utils.set_turn(state) return state