def _can_escape_old(board, my_position, blast_strength): for row, col in [(-1, 0), (1, 0), (0, -1), (0, 1)]: for d in range(1, blast_strength + 1): new_pos = (my_position[0] + row * d, my_position[1] + col * d) if TestSimpleAgent._out_of_board(new_pos): continue if not utility.position_is_passage(board, new_pos): break if d == blast_strength: return True if row == 0: for row2, col2 in [(-1, 0), (1, 0)]: new_pos2 = (new_pos[0] + row2, new_pos[1] + col2) if TestSimpleAgent._out_of_board(new_pos2): continue if utility.position_is_passage(board, new_pos2): return True else: for row2, col2 in [(0, -1), (0, 1)]: new_pos2 = (new_pos[0] + row2, new_pos[1] + col2) if TestSimpleAgent._out_of_board(new_pos2): continue if utility.position_is_passage(board, new_pos2): return True return False
def me_to_enemy_all_corridor(board, pos1, pos2): assert (pos1[0] == pos2[0] or pos1[1] == pos2[1]) if pos1[0] == pos2[0]: if pos1[1] < pos2[1]: direction = constants.Action.Right else: direction = constants.Action.Left else: if pos1[0] < pos2[0]: direction = constants.Action.Down else: direction = constants.Action.Up p_dirs = perpendicular_directions(direction) pos2_next = utility.get_next_position(pos2, direction) next_is_impasse = (not utility.position_on_board( board, pos2_next)) or utility.position_is_wall(board, pos2_next) if utility.position_on_board(board, pos2_next) and utility.position_is_fog( board, pos2_next): next_is_impasse = False if not (position_is_in_corridor(board, pos2, p_dirs) and next_is_impasse): # pos2:enempy must be in impasse return False all_corridor_flag = True pos = utility.get_next_position(pos1, direction) while pos != pos2: if not (utility.position_is_passage(board, pos)): all_corridor_flag = False break if not position_is_in_corridor(board, pos, p_dirs): all_corridor_flag = False break pos = utility.get_next_position(pos, direction) return all_corridor_flag
def _compute_min_evade_step(obs, parent_pos_list, pos, bomb_real_life): flag_cover, min_cover_value, max_cover_value = _position_covered_by_bomb(obs, pos, bomb_real_life) if not flag_cover: return 0 elif len(parent_pos_list) >= max_cover_value: if len(parent_pos_list) > max_cover_value + FLAME_LIFE: return 0 else: return INT_MAX elif len(parent_pos_list) >= min_cover_value: if len(parent_pos_list) > min_cover_value + FLAME_LIFE: return 0 else: return INT_MAX else: board = obs['board'] dirs = _all_directions(exclude_stop=True) min_step = INT_MAX for d in dirs: next_pos = utility.get_next_position(pos, d) if not utility.position_on_board(board, next_pos): continue if not (utility.position_is_passage(board, next_pos) or \ utility.position_is_powerup(board, next_pos)): continue if next_pos in parent_pos_list: continue x = _compute_min_evade_step(obs, parent_pos_list + [next_pos], next_pos, bomb_real_life) min_step = min(min_step, x + 1) return min_step
def _kick_test(board, blast_st, bomb_life, my_position, direction): def moving_bomb_check(moving_bomb_pos, p_dir, time_elapsed): pos2=utility.get_next_position(moving_bomb_pos, p_dir) dist=0 for i in range(10): dist +=1 if not utility.position_on_board(board, pos2): break if not (utility.position_is_powerup(board, pos2) or utility.position_is_passage(board, pos2)): break life_now=bomb_life[pos2] - time_elapsed if bomb_life[pos2]>0 and life_now>=-2 and life_now <= 0 and dist<blast_st[pos2]: return False pos2=utility.get_next_position(pos2, p_dir) return True next_position=utility.get_next_position(my_position, direction) assert(utility.position_in_items(board, next_position, [constants.Item.Bomb])) life_value=int(bomb_life[next_position]) strength=int(blast_st[next_position]) dist=0 pos=utility.get_next_position(next_position, direction) perpendicular_dirs=[constants.Action.Left, constants.Action.Right] if direction == constants.Action.Left or direction == constants.Action.Right: perpendicular_dirs=[constants.Action.Down, constants.Action.Up] for i in range(life_value): if utility.position_on_board(board, pos) and utility.position_is_passage(board, pos): #do a check if this position happens to be in flame when the moving bomb arrives! if not (moving_bomb_check(pos, perpendicular_dirs[0], i) and moving_bomb_check(pos, perpendicular_dirs[1], i)): break dist +=1 else: break pos=utility.get_next_position(pos, direction) #can kick and kick direction is valid return dist > strength
def position_can_be_bomb_through(board, position): if utility.position_is_flames(board, position): return True if utility.position_is_passage(board, position): return True if utility.position_is_powerup(board, position): return True return False
def position_is_bombable(board, position, bombs): return any([ utility.position_is_agent(board, position), utility.position_is_powerup(board, position), utility.position_is_passage(board, position), position_is_flame(board, position), position_is_bomb(bombs, position) ])
def position_is_passable(board, position, enemies): '''Determins if a possible can be passed''' return all([ any([ utility.position_is_agent(board, position), utility.position_is_powerup(board, position), utility.position_is_passage(board, position), utility.position_is_fog(board, position), ]), not utility.position_is_enemy(board, position, enemies) ])
def is_moving_direction(bomb_pos, direction): rev_d = _opposite_direction(direction) rev_pos = utility.get_next_position(bomb_pos, rev_d) if not utility.position_on_board(prev_obs['board'], rev_pos): return False if prev_obs['bomb_life'][rev_pos] - 1 == obs['bomb_life'][bomb_pos] \ and prev_obs['bomb_blast_strength'][rev_pos] == obs['bomb_blast_strength'][bomb_pos] \ and utility.position_is_passage(prev_obs['board'], bomb_pos): return True return False
def is_moving_direction(bomb_pos, direction): # 炸弹是否在移动 rev_d = _opposite_direction(direction) # 返回 bomb 移动方向的反向 rev_pos = utility.get_next_position(bomb_pos, rev_d) # 上一帧 bomb 的位置 if not utility.position_on_board( prev_obs['board'], rev_pos): # 如果上一帧位置不在 board 上, 返回 False return False if prev_obs['bomb_life'][rev_pos] - 1 == obs['bomb_life'][bomb_pos] \ and prev_obs['bomb_blast_strength'][rev_pos] == obs['bomb_blast_strength'][bomb_pos] \ and utility.position_is_passage(prev_obs['board'], bomb_pos): return True # 与上一帧是同一个bomb 并且当前位置是一个过道0 返回 True return False
def moving_bomb_check(moving_bomb_pos, p_dir, time_elapsed): pos2 = utility.get_next_position(moving_bomb_pos, p_dir) dist = 0 for i in range(10): dist += 1 if not utility.position_on_board(board, pos2): break if not (utility.position_is_powerup(board, pos2) or utility.position_is_passage(board, pos2)): break life_now = bomb_life[pos2] - time_elapsed if bomb_life[pos2] > 0 and life_now >= -2 and life_now <= 0 and dist < blast_st[pos2]: return False pos2 = utility.get_next_position(pos2, p_dir) return True
def move_moving_bombs_to_next_position(prev_obs, obs): def is_moving_direction(bomb_pos, direction): # 炸弹是否在移动 rev_d = _opposite_direction(direction) # 返回 bomb 移动方向的反向 rev_pos = utility.get_next_position(bomb_pos, rev_d) # 上一帧 bomb 的位置 if not utility.position_on_board( prev_obs['board'], rev_pos): # 如果上一帧位置不在 board 上, 返回 False return False if prev_obs['bomb_life'][rev_pos] - 1 == obs['bomb_life'][bomb_pos] \ and prev_obs['bomb_blast_strength'][rev_pos] == obs['bomb_blast_strength'][bomb_pos] \ and utility.position_is_passage(prev_obs['board'], bomb_pos): return True # 与上一帧是同一个bomb 并且当前位置是一个过道0 返回 True return False bombs = zip(*np.where(obs['bomb_life'] > 1)) moving_bombs = [] for bomb_pos in bombs: moving_dir = None for d in [constants.Action.Left, constants.Action.Right, \ constants.Action.Up, constants.Action.Down]: if is_moving_direction(bomb_pos, d): moving_dir = d break if moving_dir is not None: moving_bombs.append((bomb_pos, moving_dir)) board = obs['board'] bomb_life = obs['bomb_life'] bomb_blast_strength = obs['bomb_blast_strength'] for bomb_pos, moving_dir in moving_bombs: next_pos = utility.get_next_position(bomb_pos, moving_dir) if not utility.position_on_board(obs['board'], next_pos): continue if utility.position_is_passage(obs['board'], next_pos): board[next_pos] = board[bomb_pos] bomb_life[next_pos] = bomb_life[bomb_pos] bomb_blast_strength[next_pos] = bomb_blast_strength[bomb_pos] board[bomb_pos] = constants.Item.Passage.value bomb_life[bomb_pos] = 0 bomb_blast_strength[bomb_pos] = 0 return obs
def _compute_safe_actions(obs, exclude_kicking=False, prev_two_obs=(None, None)): dirs = _all_directions(exclude_stop=True) ret = set() my_position, board, blast_st, bomb_life, can_kick = obs['position'], obs['board'], obs['bomb_blast_strength'], obs[ 'bomb_life'], obs['can_kick'] kick_dir = None bomb_real_life_map = _all_bomb_real_life(board, bomb_life, blast_st) flag_cover_passages = [] for direction in dirs: position = utility.get_next_position(my_position, direction) if not utility.position_on_board(board, position): continue if (not exclude_kicking) and utility.position_in_items(board, position, [constants.Item.Bomb]) and can_kick: # filter kick if kick is unsafe if _kick_test(board, blast_st, bomb_real_life_map, my_position, direction): ret.add(direction.value) kick_dir = direction.value gone_flame_pos = None if (prev_two_obs[0] != None and prev_two_obs[1] != None) and _check_if_flame_will_gone(obs, prev_two_obs, position): # three consecutive flames means next step this position must be good # make this a candidate obs['board'][position] = constants.Item.Passage.value gone_flame_pos = position if utility.position_is_passage(board, position) or utility.position_is_powerup(board, position): my_id = obs['board'][my_position] obs['board'][my_position] = constants.Item.Bomb.value if bomb_life[ my_position] > 0 else constants.Item.Passage.value flag_cover, min_cover_value, max_cover_value = _position_covered_by_bomb(obs, position, bomb_real_life_map) flag_cover_passages.append(flag_cover) if not flag_cover: ret.add(direction.value) else: min_escape_step = _compute_min_evade_step(obs, [position], position, bomb_real_life_map) assert (min_escape_step > 0) if min_escape_step < min_cover_value: ret.add(direction.value) obs['board'][my_position] = my_id if gone_flame_pos is not None: obs['board'][gone_flame_pos] = constants.Item.Flames.value # Test Stop action only when agent is covered by bomb, # otherwise Stop is always an viable option my_id = obs['board'][my_position] obs['board'][my_position] = constants.Item.Bomb.value if bomb_life[ my_position] > 0 else constants.Item.Passage.value # REMEMBER: before compute min evade step, modify board accordingly first.. flag_cover, min_cover_value, max_cover_value = _position_covered_by_bomb(obs, my_position, bomb_real_life_map) if flag_cover: min_escape_step = _compute_min_evade_step(obs, [None, my_position], my_position, bomb_real_life_map) if min_escape_step < min_cover_value: ret.add(constants.Action.Stop.value) else: ret.add(constants.Action.Stop.value) obs['board'][my_position] = my_id # REMEBER: change the board back # Now test bomb action if not (obs['ammo'] <= 0 or obs['bomb_life'][obs['position']] > 0): # place bomb might be possible assert (BOMBING_TEST in ['simple', 'simple_adjacent', 'lookahead']) if BOMBING_TEST == 'simple': if not flag_cover: ret.add(constants.Action.Bomb.value) elif BOMBING_TEST == 'simple_adjacent': if (not flag_cover) and (not any(flag_cover_passages)): ret.add(constants.Action.Bomb.value) else: # lookahead if (constants.Action.Stop.value in ret) and len(ret) > 1 and (kick_dir is None): obs2 = copy.deepcopy(obs) my_pos = obs2['position'] obs2['board'][my_pos] = constants.Item.Bomb.value obs2['bomb_life'][my_pos] = min_cover_value if flag_cover else 10 obs2['bomb_blast_strength'][my_pos] = obs2['blast_strength'] bomb_life2, bomb_blast_st2, board2 = obs2['bomb_life'], obs2['bomb_blast_strength'], obs2['board'] bomb_real_life_map = _all_bomb_real_life(board2, bomb_life2, bomb_blast_st2) min_evade_step = _compute_min_evade_step(obs2, [None, my_position], my_pos, bomb_real_life_map) current_cover_value = obs2['bomb_life'][my_pos] if min_evade_step < current_cover_value: ret.add(constants.Action.Bomb.value) return ret
def act(self, obs, action_space, info): # # Definitions # board = obs['board'] recently_seen_positions = (info["since_last_seen"] < 3) board[recently_seen_positions] = info["last_seen"][recently_seen_positions] my_position = obs["position"] # tuple([x,y]): my position my_ammo = obs['ammo'] # int: the number of bombs I have my_blast_strength = obs['blast_strength'] my_enemies = [constants.Item(e) for e in obs['enemies'] if e != constants.Item.AgentDummy] if obs["teammate"] != constants.Item.AgentDummy: my_teammate = obs["teammate"] else: my_teammate = None my_kick = obs["can_kick"] # whether I can kick if verbose: print("my position", my_position, "ammo", my_ammo, "blast", my_blast_strength, "kick", my_kick, end="\t") my_next_position = {constants.Action.Stop: my_position, constants.Action.Bomb: my_position} for action in [constants.Action.Up, constants.Action.Down, constants.Action.Left, constants.Action.Right]: next_position = self._get_next_position(my_position, action) if self._on_board(next_position): if board[next_position] == constants.Item.Rigid.value: my_next_position[action] = None else: my_next_position[action] = next_position else: my_next_position[action] = None # # Understand current situation # if all([info["prev_action"] not in [constants.Action.Stop, constants.Action.Bomb], info["prev_position"] == my_position]): # if previously blocked, do not reapeat with some probability self._inv_tmp *= self._backoff else: self._inv_tmp = self._inv_tmp_init # enemy positions enemy_positions = list() for enemy in my_enemies: rows, cols = np.where(board==enemy.value) if len(rows) == 0: continue enemy_positions.append((rows[0], cols[0])) # teammate position teammate_position = None if my_teammate is not None: rows, cols = np.where(board==my_teammate.value) if len(rows): teammate_position = (rows[0], cols[0]) # Positions where we kick a bomb if we move to if my_kick: kickable, might_kickable = self._kickable_positions(obs, info["moving_direction"]) else: kickable = set() might_kickable = set() # positions that might be blocked if teammate_position is None: agent_positions = enemy_positions else: agent_positions = enemy_positions + [teammate_position] might_blocked = self._get_might_blocked(board, my_position, agent_positions, might_kickable) # enemy positions over time # these might be dissappeared due to extra flames if len(enemy_positions): rows = [p[0] for p in enemy_positions] cols = [p[1] for p in enemy_positions] list_enemy_positions = [(rows, cols)] _enemy_positions = list() for t in range(self._enemy_mobility): rows, cols = list_enemy_positions[-1] for x, y in zip(rows, cols): for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]: next_position = (x + dx, y + dy) if not self._on_board(next_position): continue _board = info["list_boards_no_move"][t] if utility.position_is_passage(_board, next_position): _enemy_positions.append(next_position) _enemy_positions = set(_enemy_positions) rows = [p[0] for p in _enemy_positions] cols = [p[1] for p in _enemy_positions] list_enemy_positions.append((rows, cols)) else: list_enemy_positions = [] # survivable actions is_survivable = dict() for a in self._get_all_actions(): is_survivable[a] = False n_survivable = dict() list_boards = dict() for my_action in self._get_all_actions(): next_position = my_next_position[my_action] if next_position is None: continue if my_action == constants.Action.Bomb: if any([my_ammo == 0, obs["bomb_blast_strength"][next_position] > 0]): continue if all([utility.position_is_flames(board, next_position), info["flame_life"][next_position] > 1]): continue if all([my_action != constants.Action.Stop, obs["bomb_blast_strength"][next_position] > 0, next_position not in set.union(kickable, might_kickable)]): continue if next_position in set.union(kickable, might_kickable): # do not kick into fog dx = next_position[0] - my_position[0] dy = next_position[1] - my_position[1] position = next_position is_fog = False while self._on_board(position): if utility.position_is_fog(board, position): is_fog = True break position = (position[0] + dx, position[1] + dy) if is_fog: continue # list of boards from next steps list_boards[my_action], _ \ = self._board_sequence(obs["board"], info["curr_bombs"], info["curr_flames"], self._search_range, my_position, my_blast_strength=my_blast_strength, my_action=my_action, can_kick=my_kick, enemy_mobility=self._enemy_mobility, enemy_bomb=self._enemy_bomb, agent_blast_strength=info["agent_blast_strength"]) # agents might be disappeared, because of overestimated bombs for t, positions in enumerate(list_enemy_positions): list_boards[my_action][t][positions] = constants.Item.AgentDummy.value # some bombs may explode with extra bombs, leading to under estimation for t in range(len(list_boards[my_action])): flame_positions = np.where(info["list_boards_no_move"][t] == constants.Item.Flames.value) list_boards[my_action][t][flame_positions] = constants.Item.Flames.value """ processed = Parallel(n_jobs=-1, verbose=0)( [delayed(search_time_expanded_network)(list_boards[action][1:], my_next_position[action], action) for action in list_boards] ) for survivable, my_action in processed: if my_next_position[my_action] in survivable[0]: is_survivable[my_action] = True n_survivable[my_action] = [1] + [len(s) for s in survivable[1:]] """ for my_action in list_boards: survivable = search_time_expanded_network(list_boards[my_action][1:], my_next_position[my_action]) if my_next_position[my_action] in survivable[0]: is_survivable[my_action] = True n_survivable[my_action] = [1] + [len(s) for s in survivable[1:]] survivable_actions = list() for a in is_survivable: if not is_survivable[a]: continue if might_blocked[a] and not is_survivable[constants.Action.Stop]: continue if n_survivable[a][-1] <= 1: is_survivable[a] = False continue survivable_actions.append(a) # # Choose action # if len(survivable_actions) == 0: # # return None, if no survivable actions # return None elif len(survivable_actions) == 1: # # Choose the survivable action, if it is the only choice # action = survivable_actions[0] if verbose: print("The only survivable action", action) return action.value # # Bomb at a target # # fraction of blocked node in the survival trees of enemies _list_boards = deepcopy(info["list_boards_no_move"]) if obs["bomb_blast_strength"][my_position]: for b in _list_boards: if utility.position_is_agent(b, my_position): b[my_position] = constants.Item.Bomb.value else: for b in _list_boards: if utility.position_is_agent(b, my_position): b[my_position] = constants.Item.Passage.value total_frac_blocked, n_survivable_nodes, blocked_time_positions \ = self._get_frac_blocked(_list_boards, my_enemies, board, obs["bomb_life"]) if teammate_position is not None: total_frac_blocked_teammate, n_survivable_nodes_teammate, blocked_time_positions_teammate \ = self._get_frac_blocked(_list_boards, [my_teammate], board, obs["bomb_life"]) """ np.set_printoptions(precision=3) print("enemy") print(total_frac_blocked) print("teammate") print(total_frac_blocked_teammate) print("product") prod = total_frac_blocked * (1 - total_frac_blocked_teammate) print(prod[:5,:5]) """ p_survivable = defaultdict(float) for action in n_survivable: p_survivable[action] = sum(n_survivable[action]) / self._my_survivability_threshold if p_survivable[action] > 1: p_survivable[action] = 1 block = defaultdict(float) for action in [constants.Action.Stop, constants.Action.Up, constants.Action.Down, constants.Action.Left, constants.Action.Right]: next_position = my_next_position[action] if next_position is None: continue if next_position in set.union(kickable, might_kickable): # kick will be considered later continue if all([utility.position_is_flames(board, next_position), info["flame_life"][next_position] > 1, is_survivable[constants.Action.Stop]]): # if the next position is flames, # I want to stop to wait, which must be feasible block[action] = total_frac_blocked[next_position] * p_survivable[constants.Action.Stop] if teammate_position is not None: block[action] *= (1 - total_frac_blocked_teammate[next_position]) block[action] *= self._inv_tmp block[action] -= np.log(-np.log(self.random.uniform())) continue elif not is_survivable[action]: continue if all([might_blocked[action], not is_survivable[constants.Action.Stop]]): continue block[action] = total_frac_blocked[next_position] * p_survivable[action] if teammate_position is not None: block[action] *= (1 - total_frac_blocked_teammate[next_position]) block[action] *= self._inv_tmp block[action] -= np.log(-np.log(self.random.uniform())) if might_blocked[action]: block[action] = (total_frac_blocked[my_position] * p_survivable[constants.Action.Stop] + total_frac_blocked[next_position] * p_survivable[action]) / 2 if teammate_position is not None: block[action] *= (1 - total_frac_blocked_teammate[next_position]) block[action] *= self._inv_tmp block[action] -= np.log(-np.log(self.random.uniform())) if is_survivable[constants.Action.Bomb]: list_boards_with_bomb, _ \ = self._board_sequence(board, info["curr_bombs"], info["curr_flames"], self._search_range, my_position, my_blast_strength=my_blast_strength, my_action=constants.Action.Bomb) n_survivable_nodes_with_bomb = defaultdict(int) for enemy in my_enemies: # get survivable tree of the enemy rows, cols = np.where(board==enemy.value) if len(rows) == 0: continue enemy_position = (rows[0], cols[0]) _survivable = search_time_expanded_network(list_boards_with_bomb, enemy_position) n_survivable_nodes_with_bomb[enemy] = sum([len(positions) for positions in _survivable]) n_with_bomb = sum([n_survivable_nodes_with_bomb[enemy] for enemy in my_enemies]) n_with_none = sum([n_survivable_nodes[enemy] for enemy in my_enemies]) if n_with_none == 0: total_frac_blocked_with_bomb = 0 # place more bombs, so the stacked enemy cannot kick x, y = my_position for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: next_position = (x + dx, y + dy) following_position = (x + 2 * dx, y + 2 * dy) if not self._on_board(following_position): continue if all([obs["bomb_life"][next_position] > 0, board[following_position] > constants.Item.AgentDummy.value]): total_frac_blocked_with_bomb = 1 else: total_frac_blocked_with_bomb = 1 - n_with_bomb / n_with_none if teammate_position is not None: # get survivable tree of the teammate _survivable = search_time_expanded_network(list_boards_with_bomb, teammate_position) n_survivable_nodes_with_bomb_teammate = sum([len(positions) for positions in _survivable]) n_with_bomb = n_survivable_nodes_with_bomb_teammate n_with_none = n_survivable_nodes_teammate[my_teammate] if n_with_none == 0: total_frac_blocked_with_bomb_teammate = 0 else: total_frac_blocked_with_bomb_teammate = 1 - n_with_bomb / n_with_none action = constants.Action.Bomb block[action] = total_frac_blocked_with_bomb * p_survivable[action] if teammate_position is not None: block[action] *= (1 - total_frac_blocked_with_bomb_teammate) block[action] *= self._inv_tmp block[action] -= np.log(-np.log(self.random.uniform())) for next_position in kickable: action = self._get_direction(my_position, next_position) if not is_survivable[action]: continue list_boards_with_kick, _ \ = self._board_sequence(obs["board"], info["curr_bombs"], info["curr_flames"], self._search_range, my_position, my_action=action, can_kick=True) n_survivable_nodes_with_kick = defaultdict(int) for enemy in my_enemies: # get survivable tree of the enemy rows, cols = np.where(board==enemy.value) if len(rows) == 0: continue enemy_position = (rows[0], cols[0]) _survivable = search_time_expanded_network(list_boards_with_kick, enemy_position) n_survivable_nodes_with_kick[enemy] = sum([len(positions) for positions in _survivable]) n_with_kick = sum([n_survivable_nodes_with_kick[enemy] for enemy in my_enemies]) n_with_none = sum([n_survivable_nodes[enemy] for enemy in my_enemies]) if n_with_none == 0: total_frac_blocked[next_position] = 0 else: total_frac_blocked[next_position] = 1 - n_with_kick / n_with_none if teammate_position is not None: # get survivable tree of the teammate _survivable = search_time_expanded_network(list_boards_with_kick, teammate_position) n_survivable_nodes_with_kick_teammate = sum([len(positions) for positions in _survivable]) n_with_kick = n_survivable_nodes_with_kick_teammate n_with_none = n_survivable_nodes_teammate[my_teammate] if n_with_none == 0: total_frac_blocked_teammate[next_position] = 0 else: total_frac_blocked_teammate[next_position] = 1 - n_with_kick / n_with_none block[action] = total_frac_blocked[next_position] * p_survivable[action] if teammate_position is not None: block[action] *= (1 - total_frac_blocked_teammate[next_position]) block[action] *= self._inv_tmp block[action] -= np.log(-np.log(self.random.uniform())) max_block = -np.inf best_action = None for action in block: if block[action] > max_block: max_block = block[action] best_action = action if block[constants.Action.Bomb] > self._bomb_threshold * self._inv_tmp: if teammate_position is not None: teammate_safety = total_frac_blocked_with_bomb_teammate * n_survivable_nodes_with_bomb_teammate if any([teammate_safety > self._teammate_survivability_threshold, total_frac_blocked_with_bomb_teammate < self._interfere_threshold, total_frac_blocked_with_bomb_teammate < total_frac_blocked_teammate[my_position]]): teammate_ok = True else: teammate_ok = False else: teammate_ok = True if teammate_ok: if best_action == constants.Action.Bomb: if verbose: print("Bomb is best", constants.Action.Bomb) return constants.Action.Bomb.value if best_action == constants.Action.Stop: if verbose: print("Place a bomb at a locally optimal position", constants.Action.Bomb) return constants.Action.Bomb.value # # Move towards where to bomb # if best_action not in [None, constants.Action.Bomb]: next_position = my_next_position[best_action] should_chase = (total_frac_blocked[next_position] > self._chase_threshold) if teammate_position is not None: teammate_safety = total_frac_blocked_teammate[next_position] * n_survivable_nodes_teammate[my_teammate] if any([teammate_safety > self._teammate_survivability_threshold, total_frac_blocked_teammate[next_position] < self._interfere_threshold, total_frac_blocked_teammate[next_position] < total_frac_blocked_teammate[my_position]]): teammate_ok = True else: teammate_ok = False else: teammate_ok = True if should_chase and teammate_ok: if all([utility.position_is_flames(board, next_position), info["flame_life"][next_position] > 1, is_survivable[constants.Action.Stop]]): action = constants.Action.Stop if verbose: print("Wait flames life", action) return action.value else: if verbose: print("Move towards better place to bomb", best_action) return best_action.value # Exclude the action representing stop to wait max_block = -np.inf best_action = None for action in survivable_actions: if block[action] > max_block: max_block = block[action] best_action = action # # Do not take risky actions # most_survivable_action = self._action_most_survivable(n_survivable) # ignore actions with low survivability _survivable_actions = list() for action in n_survivable: n = sum(n_survivable[action]) if not is_survivable[action]: continue elif n > self._my_survivability_threshold: _survivable_actions.append(action) else: print("RISKY", action) is_survivable[action] = False if len(_survivable_actions) > 1: survivable_actions = _survivable_actions elif best_action is not None: if verbose: print("Take the best action in danger", best_action) return best_action.value else: # Take the most survivable action if verbose: print("Take the most survivable action", most_survivable_action) return most_survivable_action.value # # Do not interfere with teammate # if all([teammate_position is not None, len(enemy_positions) > 0 or len(info["curr_bombs"]) > 0]): # ignore actions that interfere with teammate min_interfere = np.inf least_interfere_action = None _survivable_actions = list() for action in survivable_actions: if action == constants.Action.Bomb: frac = total_frac_blocked_with_bomb_teammate else: next_position = my_next_position[action] frac = total_frac_blocked_teammate[next_position] if frac < min_interfere: min_interfere = frac least_interfere_action = action if frac < self._interfere_threshold: _survivable_actions.append(action) else: print("INTERFERE", action) is_survivable[action] = False if len(_survivable_actions) > 1: survivable_actions = _survivable_actions elif best_action is not None: # Take the least interfering action if verbose: print("Take the best action in intereference", best_action) return best_action.value else: if verbose: print("Take the least interfering action", least_interfere_action) return least_interfere_action.value consider_bomb = True if not is_survivable[constants.Action.Bomb]: consider_bomb = False # # Find reachable items # # List of boards simulated list_boards, _ = self._board_sequence(board, info["curr_bombs"], info["curr_flames"], self._search_range, my_position, enemy_mobility=self._enemy_mobility, enemy_bomb=self._enemy_bomb, agent_blast_strength=info["agent_blast_strength"]) # some bombs may explode with extra bombs, leading to under estimation for t in range(len(list_boards)): flame_positions = np.where(info["list_boards_no_move"][t] == constants.Item.Flames.value) list_boards[t][flame_positions] = constants.Item.Flames.value # List of the set of survivable time-positions at each time # and preceding positions survivable, prev, _, _ = self._search_time_expanded_network(list_boards, my_position) if len(survivable[-1]) == 0: survivable = [set() for _ in range(len(survivable))] # Items and bomb target that can be reached in a survivable manner _, _, next_to_items \ = self._find_reachable_items(list_boards, my_position, survivable) # # If I have seen an enemy recently and cannot see him now, them move to the last seen position # action = self._action_to_enemy(my_position, next_to_items[constants.Item.Fog], prev, is_survivable, info, my_enemies) if action is not None: if verbose: print("Moving toward last seen enemy", action) return action.value # # If I have seen a teammate recently, them move away from the last seen position # action = self._action_away_from_teammate(my_position, next_to_items[constants.Item.Fog], prev, is_survivable, info, my_teammate) if action is not None: if verbose: print("Moving away from last seen teammate", action) return action.value # # Move towards a fog where we have not seen longest # action = self._action_to_fog(my_position, next_to_items[constants.Item.Fog], prev, is_survivable, info) if action is not None: #if True: if self.random.uniform() < 0.8: if verbose: print("Moving toward oldest fog", action) return action.value # # Choose most survivable action # max_block = -np.inf best_action = None for action in survivable_actions: if action == constants.Action.Bomb: continue if block[action] > max_block: max_block = block[action] best_action = action if verbose: print("Take the best action among safe actions (nothing else to do)", best_action) if best_action is None: # this should not be the case return None else: return best_action.value
def position_is_passable(board, pos, enemies=[]): #hard code the smallest agent id on board if board[pos] >= 10: return False return utility.position_is_powerup( board, pos) or utility.position_is_passage(board, pos)
def _kickable_positions(self, obs, moving_direction): """ Parameters ---------- obs : dict pommerman observation """ if not obs["can_kick"]: return set() kickable = set() x, y = obs["position"] # Find neigoboring positions on board on_board_next_positions = list() for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: next_position = (x + dx, y + dy) if self._on_board(next_position): on_board_next_positions.append(next_position) # Check if can kick a static bomb for next_position in on_board_next_positions: if obs["board"][next_position] != constants.Item.Bomb.value: # not a bomb continue if moving_direction[next_position] is not None: # moving continue if obs["bomb_life"][next_position] <= 1: # kick and die continue following_position = (2 * next_position[0] - x, 2 * next_position[1] - y) if not self._on_board(following_position): # cannot kick to that direction continue if not utility.position_is_passage(obs["board"], following_position): # cannot kick to that direction continue kickable.add(next_position) # Check if can kick a moving bomb for next_position in on_board_next_positions: if next_position in kickable: # can kick a static bomb continue x, y = next_position for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: coming_position = (x + dx, y + dy) if coming_position == obs["position"]: # cannot come from my position continue if not self._on_board(coming_position): # cannot come from out of board continue if obs["bomb_life"][coming_position] <= 1: # kick and die continue if all([moving_direction[coming_position] == constants.Action.Up, dx == 0, dy == 1]): # coming from below kickable.add(next_position) break if all([moving_direction[coming_position] == constants.Action.Down, dx == 0, dy == -1]): # coming from above kickable.add(next_position) break if all([moving_direction[coming_position] == constants.Action.Right, dx == -1, dy == 0]): # coming from left kickable.add(next_position) break if all([moving_direction[coming_position] == constants.Action.Left, dx == 1, dy == 0]): # coming from right kickable.add(next_position) break return kickable
def _board_sequence(self, board, bombs, flames, length, my_position, my_action=None, can_kick=False, enemy_mobility=3): """ Simulate the sequence of boards, assuming agents stay unmoved Parameters ---------- board : array initial board bombs : list list of initial bombs flames : list list of initial flames length : int length of the board sequence to simulate my_position : tuple position of my agent my_action : Action, optional my action at the first step can_kick : boolean, optional whether I can kick enemy_mobility : int, optional number of steps where enemies move nondeterministically Return ------ list_boards : list list of boards """ # Forward model to simulate model = ForwardModel() # Prepare initial state _board = board.copy() _bombs = deepcopy(bombs) _flames = deepcopy(flames) _items = dict() # we never know hidden items _actions = [constants.Action.Stop.value] * 4 if my_action is not None: agent = characters.Bomber() agent.agent_id = board[my_position] - 10 agent.position = my_position agent.can_kick = can_kick _agents = [agent] _actions[agent.agent_id] = my_action else: _agents = list() my_next_position = None # Get enemy positions to take into account their mobility rows, cols = np.where(_board > constants.Item.AgentDummy.value) enemy_positions = [position for position in zip(rows, cols) if position != my_position] # List of enemies enemies = list() for position in enemy_positions: agent = characters.Bomber() agent.agent_id = board[position] - 10 agent.position = position enemies.append(agent) _agents = _agents + enemies # Overwrite bomb over agent if they overlap for bomb in _bombs: _board[bomb.position] = constants.Item.Bomb.value # Simulate list_boards = [_board.copy()] for t in range(length): # Standard simulation step _board, _agents, _bombs, _, _flames \ = model.step(_actions, _board, _agents, _bombs, _items, _flames) # Overwrite passage over my agent when it has moved to a passage if t == 0 and len(_agents) > 0: agent = _agents[0] my_next_position = agent.position if all([agent.position != my_position, _board[agent.position] != constants.Item.Flames.value, _board[agent.position] != constants.Item.Bomb.value]): # I did not die and did not stay on a bomb _board[agent.position] = constants.Item.Passage.value # Overwrite bomb over agent if they overlap for bomb in _bombs: _board[bomb.position] = constants.Item.Bomb.value # Take into account the nondeterministic mobility of enemies if t < enemy_mobility: _enemy_positions = set() for x, y in enemy_positions: # for each enemy position in the previous step for dx, dy in [(0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)]: # consider the next possible position next_position = (x + dx, y + dy) if not self._on_board(next_position): # ignore if out of board continue if any([utility.position_is_passage(_board, next_position), utility.position_is_powerup(_board, next_position), (next_position == my_position and utility.position_is_agent(_board, next_position) )]): # possible as a next position # TODO : what to do with my position _enemy_positions.add(next_position) _board[next_position] = constants.Item.AgentDummy.value enemy_positions = _enemy_positions _actions = [constants.Action.Stop.value] * 4 _agents = enemies list_boards.append(_board.copy()) return list_boards, my_next_position
def act(self, obs, action_space, info): # # Definitions # self._search_range = 10 board = obs['board'] my_position = obs["position"] # tuple([x,y]): my position my_ammo = obs['ammo'] # int: the number of bombs I have my_blast_strength = obs['blast_strength'] my_enemies = [constants.Item(e) for e in obs['enemies']] my_teammate = obs["teammate"] my_kick = obs["can_kick"] # whether I can kick print("my position", my_position, end="\t") # # Understand current situation # # List of the set of survivable time-positions at each time # and preceding positions survivable_no_move, prev_no_move \ = self._search_time_expanded_network(info["list_boards_no_move"], my_position) # Items that can be reached in a survivable manner reachable_items_no_move, reached_no_move, next_to_items_no_move \ = self._find_reachable_items(info["list_boards_no_move"], my_position, survivable_no_move) # Simulation assuming enemies move for enemy_mobility in range(3, -1, -1): # List of boards simulated list_boards, _ = self._board_sequence( board, info["curr_bombs"], info["curr_flames"], self._search_range, my_position, enemy_mobility=enemy_mobility) # List of the set of survivable time-positions at each time # and preceding positions survivable, prev = self._search_time_expanded_network( list_boards, my_position) if len(survivable[1]) > 0: # Gradually reduce the mobility of enemy, so we have at least one survivable action break # Items that can be reached in a survivable manner reachable_items, reached, next_to_items \ = self._find_reachable_items(list_boards, my_position, survivable) # Survivable actions is_survivable, survivable_with_bomb \ = self._get_survivable_actions(survivable, obs, info["curr_bombs"], info["curr_flames"]) survivable_actions = [a for a in is_survivable if is_survivable[a]] # if verbose: if True: print("survivable actions are", survivable_actions) # Positions where we kick a bomb if we move to if my_kick: kickable = self._kickable_positions(obs, info["moving_direction"]) else: kickable = set() print() for t in range(0): print(list_boards[t]) print(survivable[t]) for key in prev[t]: print(key, prev[t][key]) # # Choose an action # """ # This is not effective in the current form if len(survivable_actions) > 1: # avoid the position if only one position at the following step # the number of positions that can be reached from the next position next = defaultdict(set) next_count = defaultdict(int) for position in survivable[1]: next[position] = set([p for p in prev[2] if position in prev[2][p]]) next_count[position] = len(next[position]) print("next count", next_count) if max(next_count.values()) > 1: for position in survivable[1]: if next_count[position] == 1: risky_action = self._get_direction(my_position, position) is_survivable[risky_action] = False survivable_actions = [a for a in is_survivable if is_survivable[a]] """ # Do not stay on a bomb if I can if all([ obs["bomb_life"][my_position] > 0, len(survivable_actions) > 1, is_survivable[constants.Action.Stop] ]): is_survivable[constants.Action.Stop] = False survivable_actions = [a for a in is_survivable if is_survivable[a]] if len(survivable_actions) == 0: print("Must die") return None elif len(survivable_actions) == 1: # move to the position if it is the only survivable position action = survivable_actions[0] print("The only survivable action", action) return action.value # TODO : shoud check the survivability of all agents in one method # Place a bomb if # - it does not significantly reduce my survivability # - it can reduce the survivability of enemies consider_bomb = True if survivable_with_bomb is None: consider_bomb = False elif any([len(s) <= 2 for s in survivable_with_bomb[1:]]): # if not sufficiently survivable all the time after bomb, do not bomb consider_bomb = False if consider_bomb: # place bomb if can reach fog/enemy if self._can_break(info["list_boards_no_move"][-1], my_position, my_blast_strength, [constants.Item.Fog] + my_enemies): print("Bomb to break fog/enemy", constants.Action.Bomb) print(info["list_boards_no_move"][-1]) return constants.Action.Bomb.value for enemy in my_enemies: # check if the enemy is reachable if len(reachable_items_no_move[enemy]) == 0: continue # can reach the enemy at enemy_position in enemy_time step enemy_time = reachable_items_no_move[enemy][0][0] enemy_position = reachable_items_no_move[enemy][0][1:3] # check if placing a bomb can reduce the survivability # of the enemy survivable_before, _ = self._search_time_expanded_network( info["list_boards_no_move"], enemy_position) board_with_bomb = deepcopy(obs["board"]) curr_bombs_with_bomb = deepcopy(info["curr_bombs"]) # lay a bomb board_with_bomb[my_position] = constants.Item.Bomb.value bomb = characters.Bomb( characters.Bomber(), # dummy owner of the bomb my_position, constants.DEFAULT_BOMB_LIFE, my_blast_strength, None) curr_bombs_with_bomb.append(bomb) list_boards_with_bomb, _ \ = self._board_sequence(board_with_bomb, curr_bombs_with_bomb, info["curr_flames"], self._search_range, my_position, enemy_mobility=0) survivable_after, _ \ = self._search_time_expanded_network(list_boards_with_bomb, enemy_position) good_before = np.array([len(s) for s in survivable_before]) good_after = np.array([len(s) for s in survivable_after]) # TODO : what are good criteria? if any(good_after < good_before): # place a bomb if it makes sense print("Bomb to kill an enemy", constants.Action.Bomb) print("before", good_before) print("after ", good_after) print([len(s) for s in survivable]) print([len(s) for s in survivable_with_bomb]) return constants.Action.Bomb.value """ # find direction towards enemy positions = set([x[1:3] for x in next_to_items_no_move[enemy]]) for t in range(enemy_time, 1, -1): _positions = set() for position in positions: _positions = _positions.union(prev_no_move[t][position]) positions = _positions.copy() if enemy_time <= my_blast_strength: #if True: positions.add(my_position) positions_after_bomb = set(survivable[1]).difference(positions) if positions_after_bomb: print("Bomb to kill an enemy", enemy, constants.Action.Bomb) return constants.Action.Bomb.value """ # if I can kick, consider placing a bomb to kick if my_kick and my_position in survivable_with_bomb[3]: # consdier a sequence of actions: place bomb -> move (action) -> move back (kick) for action in [ constants.Action.Up, constants.Action.Down, constants.Action.Left, constants.Action.Right ]: if not is_survivable[action]: continue if action == constants.Action.Up: # kick direction is down dx = 1 dy = 0 elif action == constants.Action.Down: # kick direction is up dx = -1 dy = 0 elif action == constants.Action.Left: # kick direction is right dx = 0 dy = 1 elif action == constants.Action.Right: # kick direction is left dx = 0 dy = -1 else: raise ValueError() _next_position = (my_position[0] + dx, my_position[1] + dy) if not self._on_board(_next_position): continue else: next_position = _next_position # Find where the bomb stops if kicked for t in range(int(obs["bomb_life"][my_position]) - 2): if not utility.position_is_passage( board, next_position): break _next_position = (next_position[0] + dx, next_position[1] + dy) if not self._on_board(_next_position): break else: next_position = _next_position if utility.position_is_fog(board, next_position): print("Bomb to kick into fog", action) return constants.Action.Bomb.value elif utility.position_is_enemy(list_boards[t + 2], next_position, my_enemies): print("Bomb to kick towards enemy", action) return constants.Action.Bomb.value """ x0, y0 = my_position positions_against = [(2*x0-x, 2*y0-y) for (x, y) in positions] positions_after_bomb = set(survivable[1]).intersection(positions_against) if positions_after_bomb: print("Bomb to kick", enemy, constants.Action.Bomb) return constants.Action.Bomb.value """ # kick if len(kickable) > 0: while kickable: # then consider what happens if I kick a bomb next_position = kickable.pop() # do not kick a bomb if it will break enemies if info["moving_direction"][next_position] is None: # if it is a static bomb if self._can_break(info["list_boards_no_move"][0], next_position, my_blast_strength, my_enemies): continue my_action = self._get_direction(my_position, next_position) list_boards_with_kick, next_position \ = self._board_sequence(obs["board"], info["curr_bombs"], info["curr_flames"], self._search_range, my_position, my_action=my_action, can_kick=True, enemy_mobility=3) survivable_with_kick, prev_kick \ = self._search_time_expanded_network(list_boards_with_kick[1:], next_position) if next_position in survivable_with_kick[0]: print("Kicking", my_action) return my_action.value # if on a bomb, consider where to kick in the following step if obs["bomb_life"][my_position] > 0: # For each survivable move in the next step, # check what happens if we kick in the following step. # If the bomb is kicked into a fog, plan to kick. # If the bomb is kicked toward an enemy, plan to kick. # Otherwise, do not plan to kick. for action in [ constants.Action.Up, constants.Action.Down, constants.Action.Left, constants.Action.Right ]: if not is_survivable[action]: continue if action == constants.Action.Up: # kick direction is down dx = 1 dy = 0 elif action == constants.Action.Down: # kick direction is up dx = -1 dy = 0 elif action == constants.Action.Left: # kick direction is right dx = 0 dy = 1 elif action == constants.Action.Right: # kick direction is left dx = 0 dy = -1 else: raise ValueError() _next_position = (my_position[0] + dx, my_position[1] + dy) if not self._on_board(_next_position): continue else: next_position = _next_position # Find where the bomb stops if kicked for t in range(int(obs["bomb_life"][my_position]) - 1): if not utility.position_is_passage(board, next_position): break _next_position = (next_position[0] + dx, next_position[1] + dy) if not self._on_board(_next_position): break else: next_position = _next_position if utility.position_is_fog(board, next_position): print("Moving to kick into fog", action) return action.value elif utility.position_is_enemy(list_boards[t + 2], next_position, my_enemies): print("Moving to kick towards enemy", action) # Move towards an enemy good_time_positions = set() for enemy in my_enemies: good_time_positions = good_time_positions.union( next_to_items[enemy]) if len(good_time_positions) > 0: action = self._find_distance_minimizer(my_position, good_time_positions, prev, is_survivable) if action is not None: print("Moving toward enemy", action) return action.value # # Random action # action = random.choice(survivable_actions) print("Random action", action) return action.value
def _can_break_wood(cls, board, my_position, blast_strength): """ Whether one cay break a wood by placing a bomb at my position Parameters ---------- board : array board my_position : tuple where to place a bomb blast_strength : int strength of the bomb Return ------ boolean True iff can break a wood by placing a bomb """ x, y = my_position # To up for dx in range(1, blast_strength): if x + dx >= len(board[0]): break position = (x + dx, y) if utility.position_is_wood(board, position): return True elif not utility.position_is_passage(board, position): # stop searching this direction break # To down for dx in range(1, blast_strength): if x - dx < 0: break position = (x - dx, y) if utility.position_is_wood(board, position): return True elif not utility.position_is_passage(board, position): # stop searching this direction break # To right for dy in range(1, blast_strength): if y + dy >= len(board): break position = (x, y + dy) if utility.position_is_wood(board, position): return True elif not utility.position_is_passage(board, position): # stop searching this direction break # To left for dy in range(1, blast_strength): if y - dy < 0: break position = (x, y - dy) if utility.position_is_wood(board, position): return True elif not utility.position_is_passage(board, position): # stop searching this direction break return False
def _kickable_positions(self, obs, moving_direction, consider_agents=True): """ Parameters ---------- obs : dict pommerman observation """ if not obs["can_kick"]: return set() kickable = set() # my position x, y = obs["position"] # Find neigoboring positions around me on_board_next_positions = list() for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: next_position = (x + dx, y + dy) if self._on_board(next_position): on_board_next_positions.append(next_position) # Check if can kick a static bomb for next_position in on_board_next_positions: if obs["board"][next_position] != constants.Item.Bomb.value: # not a bomb continue if moving_direction[next_position] is not None: # moving continue if obs["bomb_life"][next_position] <= 1: # kick and die continue following_position = (2 * next_position[0] - x, 2 * next_position[1] - y) if not self._on_board(following_position): # cannot kick to that direction continue if not utility.position_is_passage(obs["board"], following_position): # cannot kick to that direction continue might_blocked = False if consider_agents: # neighboring agent might block (or change the direction) immediately for dx, dy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]: neighboring_position = (x + dx, y + dy) if not self._on_board(neighboring_position): continue if np.sum( np.abs( np.array(neighboring_position) - np.array(next_position))) != 1: continue if utility.position_is_agent(obs["board"], neighboring_position): print("agent is blocking at", neighboring_position) might_blocked = True break if might_blocked: continue for dx, dy in [(-1, -1), (1, -1), (-1, 1), (1, 1)]: neighboring_position = (next_position[0] + dx, next_position[1] + dy) if not self._on_board(neighboring_position): continue if np.sum( np.abs( np.array(neighboring_position) - np.array(following_position))) != 1: continue if utility.position_is_agent(obs["board"], neighboring_position): print("agent is blocking at", neighboring_position) might_blocked = True break if might_blocked: continue print("can kick a static bomb at", next_position) kickable.add(next_position) # Check if can kick a moving bomb for next_position in on_board_next_positions: if next_position in kickable: # can kick a static bomb continue x, y = next_position for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]: coming_position = (x + dx, y + dy) if coming_position == obs["position"]: # cannot come from my position continue if not self._on_board(coming_position): # cannot come from out of board continue #if obs["bomb_life"][coming_position] <= 1: # # kick and die # continue if all([ moving_direction[coming_position] == constants.Action.Up, dx == 1, dy == 0 ]): # coming from below print("can kick a moving bomb coming from below at", next_position) kickable.add(next_position) break if all([ moving_direction[coming_position] == constants.Action.Down, dx == -1, dy == 0 ]): # coming from above print("can kick a moving bomb coming from", coming_position, "above to", next_position) kickable.add(next_position) break if all([ moving_direction[coming_position] == constants.Action.Right, dx == 0, dy == -1 ]): # coming from left print("can kick a moving bomb coming from left at", next_position) kickable.add(next_position) break if all([ moving_direction[coming_position] == constants.Action.Left, dx == 0, dy == 1 ]): # coming from right print("can kick a moving bomb coming from right at", next_position) break return kickable