def __call__(self, game: Game): if len(game.state.reports) < self.last_report_idx: self.last_report_idx = 0 r = 0.0 own_team = game.active_team opp_team = game.get_opp_team(own_team) for outcome in game.state.reports[self.last_report_idx:]: team = None if outcome.player is not None: team = outcome.player.team elif outcome.team is not None: team = outcome.team if team == own_team and outcome.outcome_type in A2C_Reward.rewards_own: r += A2C_Reward.rewards_own[outcome.outcome_type] if team == opp_team and outcome.outcome_type in A2C_Reward.rewards_opp: r += A2C_Reward.rewards_opp[outcome.outcome_type] self.last_report_idx = len(game.state.reports) ball_carrier = game.get_ball_carrier() if ball_carrier is not None: if self.last_ball_team is own_team and ball_carrier.team is own_team: ball_progress = self.last_ball_x - ball_carrier.position.x if own_team is game.state.away_team: ball_progress *= -1 # End zone at max x coordinate r += A2C_Reward.ball_progression_reward * ball_progress self.last_ball_team = ball_carrier.team self.last_ball_x = ball_carrier.position.x else: self.last_ball_team = None self.last_ball_x = None return r
def __init__(self, game: botbowl.Game): self.actions = [] self.priority_actions = [] for action_choice in game.get_available_actions(): if action_choice.action_type not in { ActionType.START_MOVE, ActionType.MOVE, ActionType.END_PLAYER_TURN }: continue if action_choice.action_type == ActionType.MOVE: prio_move_square = get_priority_move_square( action_choice, game) if prio_move_square is not None: self.priority_actions.append( Action(action_choice.action_type, position=prio_move_square)) self.actions.extend(convert_to_actions(action_choice)) if len(self.actions) == 0: self.actions.extend( collapse( convert_to_actions(action_choice) for action_choice in game.get_available_actions())) assert len(self.actions) > 0 shuffle(self.actions)
def scripted_action(game: botbowl.Game) -> Optional[Action]: available_action_types = { action_choice.action_type for action_choice in game.get_available_actions() } for at in use_directly_action_types: if at in available_action_types: return Action(at) if ActionType.PLACE_BALL in available_action_types: x = game.arena.width // 4 if game.active_team is game.state.away_team: x *= 3 y = game.arena.height // 2 return Action(ActionType.PLACE_BALL, position=botbowl.Square(x, y)) proc = game.get_procedure() if type(proc) is Setup: if game.is_setup_legal(game.active_team): return Action(ActionType.END_SETUP) for at in available_action_types: if at not in {ActionType.END_SETUP, ActionType.PLACE_PLAYER}: return Action(at) return None
def path_to_move_actions(game: botbowl.Game, player: botbowl.Player, path: Path, do_assertions=True) -> List[Action]: """ This function converts a path into a list of actions corresponding to that path. If you provide a handoff, foul or blitz path, then you have to manally set action type. :param game: :param player: player to move :param path: a path as returned by the pathfinding algorithms :param do_assertions: if False, it turns off the validation, can be helpful when the GameState will change before this path is executed. :returns: List of actions corresponding to 'path'. """ if path.block_dice is not None: action_type = ActionType.BLOCK elif path.handoff_roll is not None: action_type = ActionType.HANDOFF elif path.foul_roll is not None: action_type = ActionType.FOUL else: action_type = ActionType.MOVE active_team = game.state.available_actions[0].team player_at_target = game.get_player_at(path.get_last_step()) if do_assertions: if action_type is ActionType.MOVE: assert player_at_target is None or player_at_target is game.get_active_player( ) elif action_type is ActionType.BLOCK: assert game.get_opp_team(active_team) is player_at_target.team assert player_at_target.state.up elif action_type is ActionType.FOUL: assert game.get_opp_team(active_team) is player_at_target.team assert not player_at_target.state.up elif action_type is ActionType.HANDOFF: assert active_team is player_at_target.team assert player_at_target.state.up else: raise Exception(f"Unregonized action type {action_type}") final_action = Action(action_type, position=path.get_last_step()) if game._is_action_allowed(final_action): return [final_action] else: actions = [] if not player.state.up and path.steps[0] == player.position: actions.append(Action(ActionType.STAND_UP, player=player)) actions.extend( Action(ActionType.MOVE, position=sq) for sq in path.steps[1:-1]) else: actions.extend( Action(ActionType.MOVE, position=sq) for sq in path.steps[:-1]) actions.append(final_action) return actions
def __init__(self, game: botbowl.Game, parent: Optional['Node']): self.step_nbr = game.get_step() self.parent = parent self.children = [] self.change_log = game.trajectory.action_log[ parent.step_nbr:] if parent is not None else [] self.top_proc = str(game.get_procedure()) assert parent is None or len(self.change_log) > 0
def hash_game_state(game: botbowl.Game) -> str: s = "" s += "h" if game.active_team is game.state.home_team else "a" s += str(game.state.round) s += str(game.active_team.state.turn) s += type(game.get_procedure()).__name__ s += f"{hash(game.get_ball_position())}-" s += " ".join( str(hash(p.position)) for p in game.get_players_on_pitch()) + "-" return s
def expand_throw_in(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker active_proc: procedures.ThrowIn = game.get_procedure() assert type(active_proc) is procedures.ThrowIn d6_fixes = [3, 4] if game.arena.height > 7 else [1, 2] with only_fixed_rolls(game, d3=[2], d6=d6_fixes): game.step() assert active_proc is not game.get_procedure() return expand_none_action(game, parent)
def expand_pickup(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker active_proc: procedures.Pickup = game.get_procedure() assert type(active_proc) is procedures.Pickup assert active_proc.roll is None probability_success = game.get_pickup_prob(active_proc.player, active_proc.ball.position) new_parent = ChanceNode(game, parent) # SUCCESS SCENARIO with only_fixed_rolls(game, d6=[6]): game.step() success_node = expand_none_action(game, new_parent, pickup_handled=True) new_parent.connect_child(success_node, probability_success) assert game.get_step() == new_parent.step_nbr # FAILURE SCENARIO assert not active_proc.player.has_skill( Skill.SURE_HANDS), 'cant handle sure hands yet' with only_fixed_rolls(game, d6=[1]): game.step() fail_node = expand_none_action(game, new_parent, pickup_handled=True) new_parent.connect_child(fail_node, 1 - probability_success) assert game.get_step() == new_parent.step_nbr return new_parent
def expand_none_action(game: botbowl.Game, parent: Node, moving_handled=False, pickup_handled=False) -> Node: """ :param game: the game state is changed during expansion but restored to state of argument 'parent' :param parent: shall represent the current state of argument 'game'. game state is restored to parent.step_nbr :param moving_handled: :param pickup_handled: :returns: A subclass of Node: - ChanceNode in a nestled structure with multiple ActionNode as leaf nodes. - ActionNode if only one possible outcome. param game is changed but restored to initial state af Called recursively. """ while len(game.state.available_actions) == 0: proc = game.get_procedure() expand_func = get_expanding_function(proc, moving_handled, pickup_handled) if expand_func is not None: assert len(botbowl.D6.FixedRolls) == 0 return_node = expand_func(game, parent) assert len(botbowl.D6.FixedRolls) == 0 game.revert(parent.step_nbr) return return_node with only_fixed_rolls(game): game.step() action_node = ActionNode(game, parent) game.revert(parent.step_nbr) assert parent.step_nbr == game.get_step() return action_node
def __call__( self, game: botbowl.Game ) -> Tuple[float, np.ndarray, List[botbowl.Action]]: action = scripted_action(game) if action is not None: return 0.0, np.array([1.0]), [action] actions: MockPolicy.ActionProbList = [] if self.end_setup: self.end_setup = False return 0.0, np.array([1.0]), [Action(ActionType.END_SETUP)] for action_choice in game.get_available_actions(): action_type = action_choice.action_type if action_type in self.convert_function: actions.extend(self.convert_function[action_type]( game, action_choice)) elif action_type in self.positional_types: if len(action_choice.positions) > 0: positions = action_choice.positions else: positions = [p.position for p in action_choice.players] for pos in positions: actions.append((Action(action_type, position=pos), 1)) elif action_choice.action_type in self.simple_types: actions.append((Action(action_type), 1)) elif type(game.get_procedure()) is Setup and action_type not in { ActionType.END_SETUP, ActionType.PLACE_PLAYER }: actions.append((Action(action_type), 1)) self.end_setup = True if len(actions) == 0 and ActionType.END_PLAYER_TURN in [ ac.action_type for ac in game.state.available_actions ]: actions.append((Action(ActionType.END_PLAYER_TURN), 1)) action_objects, probabilities = zip(*actions) probabilities = np.array(probabilities, dtype=np.float) probabilities += 0.0001 * np.random.random(len(probabilities)) probabilities /= sum(probabilities) return 0.0, probabilities, action_objects
def expand_injury(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker proc: procedures.Injury = game.get_procedure() assert not proc.foul if proc.in_crowd: with only_fixed_rolls(game, d6=[5, 4]): # straight to KO game.step() return expand_none_action(game, parent) p_removal = accumulated_prob_2d_roll[8] new_parent = ChanceNode(game, parent) expand_with_fixes(game, new_parent, p_removal, d6=[5, 4]) # KO expand_with_fixes(game, new_parent, 1 - p_removal, d6=[1, 1]) # Stun return new_parent
def a2c_scripted_actions(game: Game): proc_type = type(game.get_procedure()) if proc_type is procedure.Block: # noinspection PyTypeChecker return MyScriptedBot.block(self=None, game=game) return None
def start_handoff_actions(self, game: botbowl.Game, action_choice) -> ActionProbList: ball = game.get_ball() if ball is not None and ball.is_carried: if ball.position in action_choice.positions: return [(Action(ActionType.START_HANDOFF, position=[ball.position]), 1)] return []
def expand_action(game: botbowl.Game, action: botbowl.Action, parent: ActionNode) -> Node: """ :param game: game object used for calculations. Will be reverted to original state. :param action: action to be evaluated. :param parent: parent node :returns - list of tuples containing (Steps, probability) for each possible outcome. - probabilities sums to 1.0 Not called recursively """ # noinspection PyProtectedMember assert game._is_action_allowed(action) assert game.trajectory.enabled game.config.fast_mode = False with only_fixed_rolls(game): game.step(action) return expand_none_action(game, parent)
def get_filtered_available_actions(game: botbowl.Game): allowed_avail_actions = [] disallowed = { ActionType.START_FOUL, ActionType.START_PASS, ActionType.START_HANDOFF } for action_choice in game.get_available_actions(): if action_choice.action_type in disallowed: continue allowed_avail_actions.append(action_choice) return allowed_avail_actions
def expand_catch(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker proc: procedures.Catch = game.get_procedure() assert type(proc) is procedures.Catch p_catch = game.get_catch_prob(proc.player, accurate=proc.accurate, handoff=proc.handoff) new_parent = ChanceNode(game, parent) # Success scenario expand_with_fixes(game, new_parent, p_catch, d6=[6]) # Failure scenario if proc.player.has_skill(Skill.CATCH): expand_with_fixes(game, new_parent, 1 - p_catch, d6=[1, 1]) else: expand_with_fixes(game, new_parent, 1 - p_catch, d6=[1]) return new_parent
def expand_armor(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker proc: procedures.Armor = game.get_procedure() assert not proc.foul p_armorbreak = accumulated_prob_2d_roll[proc.player.get_av() + 1] new_parent = ChanceNode(game, parent) expand_with_fixes(game, new_parent, p_armorbreak, d6=[6, 6]) # Armor broken expand_with_fixes(game, new_parent, 1 - p_armorbreak, d6=[1, 1]) # Armor not broken return new_parent
def act(self, game: Game): if self.tree is None: self.tree = SearchTree(deepcopy(game)) else: self.tree.set_new_root(game) time_left = game.get_seconds_left() if time_left < 10: assert False # expand the tree start_time = perf_counter() while perf_counter() - start_time < 10: pass
def expand_bounce(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker active_proc: procedures.Bounce = game.get_procedure() assert type(active_proc) is procedures.Bounce new_parent = ChanceNode(game, parent) ball_pos = active_proc.piece.position active_player = game.get_active_player() adj_squares = game.get_adjacent_squares(ball_pos, occupied=False) sq_to_num_tz = { sq: game.num_tackle_zones_at(active_player, sq) for sq in adj_squares } num_tz_to_sq = {} for sq, num_tz in sq_to_num_tz.items(): num_tz_to_sq.setdefault(num_tz, []).append(sq) for num_tz, count in Counter(sq_to_num_tz.values()).items(): possible_squares = num_tz_to_sq[num_tz] square = np.random.choice(possible_squares, 1)[0] assert type(square) == botbowl.Square delta_x = square.x - ball_pos.x delta_y = square.y - ball_pos.y roll = botbowl.D8.d8_from_xy[(delta_x, delta_y)] with only_fixed_rolls(game, d8=[roll]): game.step() new_node = expand_none_action(game, new_parent) new_parent.connect_child(new_node, prob=count / 8) assert game.get_step() == new_parent.step_nbr sum_prob = sum(new_parent.child_probability) new_parent.child_probability = [ prob / sum_prob for prob in new_parent.child_probability ] assert sum(new_parent.child_probability) == approx(1.0, abs=1e-9) assert game.get_step() == new_parent.step_nbr return new_parent
def handle_ko_wakeup(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker active_proc: procedures.PreKickoff = game.get_procedure() assert type(active_proc) is procedures.PreKickoff d6_fixes = [1] * len(game.get_knocked_out(active_proc.team)) with only_fixed_rolls(game, d6=d6_fixes): while active_proc is game.get_procedure(): game.step() assert active_proc is not game.get_procedure() return expand_none_action(game, parent)
def get_heuristic(game: botbowl.Game) -> HeuristicVector: """ Heuristic based on game state, calculated from home teams perspective zero sum, meaning away team's heuristic is negative of home team's heuristic :returns: array with different heuristics, multiply it with """ score, tv_on_pitch, ball_position, ball_carried, ball_marked = 0.0, 0.0, 0.0, 0.0, 0.0 home = game.state.home_team away = game.state.away_team score += home.state.score - away.state.score tv_on_pitch += sum(p.role.cost for p in game.get_players_on_pitch(team=home)) tv_on_pitch -= sum(p.role.cost for p in game.get_players_on_pitch(team=away)) tv_on_pitch /= 50000.0 # normalized to cost of lineman ball = game.get_ball() if ball is not None and ball.position is not None: ball_position -= ball.position.x # negative because home team scores at x = 0 home_marking_ball = len( game.get_adjacent_players(ball.position, team=home, standing=True)) > 0 away_marking_ball = len( game.get_adjacent_players(ball.position, team=away, standing=True)) > 0 if ball.is_carried: ball_carrier = game.get_player_at(ball.position) if ball_carrier.team == home: ball_carried += 1 ball_marked -= away_marking_ball elif ball_carrier.team == away: ball_carried -= 1 ball_marked += home_marking_ball else: ball_marked += (home_marking_ball - away_marking_ball) return HeuristicVector(score=score, tv_on_pitch=tv_on_pitch, ball_position=ball_position, ball_carried=ball_carried, ball_marked=ball_marked)
def move_actions(self, game: botbowl.Game, action_choice) -> ActionProbList: if game.get_player_action_type( ) in self.player_actiontypes_without_move_actions: return [] action_probs = [] player = game.get_active_player() ball_carrier = game.get_ball_carrier() is_ball_carrier = player is game.get_ball_carrier() if player.state.moves > 0 and not is_ball_carrier: return [] elif is_ball_carrier and player.state.moves > 0: action_probs.append((Action(ActionType.END_PLAYER_TURN), 1)) ball = game.get_ball() ball_on_floor_pos = game.get_ball( ).position if not ball.is_carried else None opp_ball_carrier = ball_carrier if ball_carrier is not None and ball_carrier.team is not game.active_team else None is_home = player.team is game.state.home_team for pos in action_choice.positions: prob = 1 if ball_on_floor_pos is not None: if pos == ball_on_floor_pos: prob = 3 elif pos.distance(ball_on_floor_pos) == 1: prob = 2 elif opp_ball_carrier is not None and pos.distance( opp_ball_carrier.position): prob = 2 elif is_ball_carrier and game.arena.is_in_opp_endzone( pos, is_home): prob = 2 action = Action(ActionType.MOVE, position=pos) action_probs.append((action, prob)) return action_probs
def _make_plan(self, game: botbowl.Game, ball_carrier): #print("1. Stand up marked players") for player in self.my_team.players: if player.position is not None and not player.state.up and not player.state.stunned and not player.state.used: if game.num_tackle_zones_in(player) > 0: self.actions.append( Action(ActionType.START_MOVE, player=player)) self.actions.append(Action(ActionType.STAND_UP)) #print(f"Stand up marked player {player.role.name}") return #print("2. Move ball carrier to endzone") if ball_carrier is not None and ball_carrier.team == self.my_team and not ball_carrier.state.used: #print("2.1 Can ball carrier score with high probability") td_path = pf.get_safest_path_to_endzone(game, ball_carrier, allow_team_reroll=True) if td_path is not None and td_path.prob >= 0.7: self.actions.append( Action(ActionType.START_MOVE, player=ball_carrier)) self.actions.extend( path_to_move_actions(game, ball_carrier, td_path)) #print(f"Score with ball carrier, p={td_path.prob}") return #print("2.2 Hand-off action to scoring player") if game.is_handoff_available(): # Get players in scoring range unused_teammates = [] for player in self.my_team.players: if player.position is not None and player != ball_carrier and not player.state.used and player.state.up: unused_teammates.append(player) # Find other players in scoring range handoff_p = None handoff_path = None for player in unused_teammates: if game.get_distance_to_endzone( player) > player.num_moves_left(): continue td_path = pf.get_safest_path_to_endzone( game, player, allow_team_reroll=True) if td_path is None: continue handoff_path = pf.get_safest_path(game, ball_carrier, player.position, allow_team_reroll=True) if handoff_path is None: continue p_catch = game.get_catch_prob(player, handoff=True, allow_catch_reroll=True, allow_team_reroll=True) p = td_path.prob * handoff_path.prob * p_catch if handoff_p is None or p > handoff_p: handoff_p = p handoff_path = handoff_path # Hand-off if high probability or last turn if handoff_path is not None and (handoff_p >= 0.7 or self.my_team.state.turn == 8): self.actions.append( Action(ActionType.START_HANDOFF, player=ball_carrier)) self.actions.extend( path_to_move_actions(game, ball_carrier, handoff_path)) return #print("2.3 Move safely towards the endzone") if game.num_tackle_zones_in(ball_carrier) == 0: paths = pf.get_all_paths(game, ball_carrier) best_path = None best_distance = 100 target_x = game.get_opp_endzone_x(self.my_team) for path in paths: distance_to_endzone = abs(target_x - path.steps[-1].x) if path.prob == 1 and ( best_path is None or distance_to_endzone < best_distance) and game.num_tackle_zones_at( ball_carrier, path.get_last_step()) == 0: best_path = path best_distance = distance_to_endzone if best_path is not None: self.actions.append( Action(ActionType.START_MOVE, player=ball_carrier)) self.actions.extend( path_to_move_actions(game, ball_carrier, best_path)) #print(f"Move ball carrier {ball_carrier.role.name}") return #print("3. Safe blocks") attacker, defender, p_self_up, p_opp_down, block_p_fumble_self, block_p_fumble_opp = self._get_safest_block( game) if attacker is not None and p_self_up > 0.94 and block_p_fumble_self == 0: self.actions.append(Action(ActionType.START_BLOCK, player=attacker)) self.actions.append( Action(ActionType.BLOCK, position=defender.position)) #print(f"Safe block with {attacker.role.name} -> {defender.role.name}, p_self_up={p_self_up}, p_opp_down={p_opp_down}") return #print("4. Pickup ball") if game.get_ball_carrier() is None: pickup_p = None pickup_player = None pickup_path = None for player in self.my_team.players: if player.position is not None and not player.state.used: if player.position.distance( game.get_ball_position()) <= player.get_ma() + 2: path = pf.get_safest_path(game, player, game.get_ball_position()) if path is not None: p = path.prob if pickup_p is None or p > pickup_p: pickup_p = p pickup_player = player pickup_path = path if pickup_player is not None and pickup_p > 0.33: self.actions.append( Action(ActionType.START_MOVE, player=pickup_player)) self.actions.extend( path_to_move_actions(game, pickup_player, pickup_path)) #print(f"Pick up the ball with {pickup_player.role.name}, p={pickup_p}") # Find safest path towards endzone if game.num_tackle_zones_at( pickup_player, game.get_ball_position( )) == 0 and game.get_opp_endzone_x( self.my_team) != game.get_ball_position().x: paths = pf.get_all_paths( game, pickup_player, from_position=game.get_ball_position(), num_moves_used=len(pickup_path)) best_path = None best_distance = 100 target_x = game.get_opp_endzone_x(self.my_team) for path in paths: distance_to_endzone = abs(target_x - path.steps[-1].x) if path.prob == 1 and ( best_path is None or distance_to_endzone < best_distance) and game.num_tackle_zones_at( pickup_player, path.get_last_step()) == 0: best_path = path best_distance = distance_to_endzone if best_path is not None: self.actions.extend( path_to_move_actions(game, pickup_player, best_path, do_assertions=False)) #print(f"- Move ball carrier {pickup_player.role.name}") return # Scan for unused players that are not marked open_players = [] for player in self.my_team.players: if player.position is not None and not player.state.used and game.num_tackle_zones_in( player) == 0: open_players.append(player) #print("5. Move receivers into scoring distance if not already") for player in open_players: if player.has_skill(Skill.CATCH) and player != ball_carrier: if game.get_distance_to_endzone( player) > player.num_moves_left(): continue paths = pf.get_all_paths(game, player) best_path = None best_distance = 100 target_x = game.get_opp_endzone_x(self.my_team) for path in paths: distance_to_endzone = abs(target_x - path.steps[-1].x) if path.prob == 1 and (best_path is None or distance_to_endzone < best_distance ) and game.num_tackle_zones_at( player, path.get_last_step()): best_path = path best_distance = distance_to_endzone if best_path is not None: self.actions.append( Action(ActionType.START_MOVE, player=player)) self.actions.extend( path_to_move_actions(game, player, best_path)) #print(f"Move receiver {player.role.name}") return #print("6. Blitz with open block players") if game.is_blitz_available(): best_blitz_attacker = None best_blitz_defender = None best_blitz_score = None best_blitz_path = None for blitzer in open_players: if blitzer.position is not None and not blitzer.state.used and blitzer.has_skill( Skill.BLOCK): blitz_paths = pf.get_all_paths(game, blitzer, blitz=True) for path in blitz_paths: defender = game.get_player_at(path.get_last_step()) if defender is None: continue from_position = path.steps[-2] if len( path.steps) > 1 else blitzer.position p_self, p_opp, p_fumble_self, p_fumble_opp = game.get_blitz_probs( blitzer, from_position, defender) p_self_up = path.prob * (1 - p_self) p_opp = path.prob * p_opp p_fumble_opp = p_fumble_opp * path.prob if blitzer == game.get_ball_carrier(): p_fumble_self = path.prob + ( 1 - path.prob) * p_fumble_self score = p_self_up + p_opp + p_fumble_opp - p_fumble_self if best_blitz_score is None or score > best_blitz_score: best_blitz_attacker = blitzer best_blitz_defender = defender best_blitz_score = score best_blitz_path = path if best_blitz_attacker is not None and best_blitz_score >= 1.25: self.actions.append( Action(ActionType.START_BLITZ, player=best_blitz_attacker)) self.actions.extend( path_to_move_actions(game, best_blitz_attacker, best_blitz_path)) #print(f"Blitz with {best_blitz_attacker.role.name}, score={best_blitz_score}") return #print("7. Make cage around ball carrier") cage_positions = [ Square(game.get_ball_position().x - 1, game.get_ball_position().y - 1), Square(game.get_ball_position().x + 1, game.get_ball_position().y - 1), Square(game.get_ball_position().x - 1, game.get_ball_position().y + 1), Square(game.get_ball_position().x + 1, game.get_ball_position().y + 1) ] if ball_carrier is not None: for cage_position in cage_positions: if game.get_player_at( cage_position) is None and not game.is_out_of_bounds( cage_position): for player in open_players: if player == ball_carrier or player.position in cage_positions: continue if player.position.distance( cage_position) > player.num_moves_left(): continue if game.num_tackle_zones_in(player) > 0: continue path = pf.get_safest_path(game, player, cage_position) if path is not None and path.prob > 0.94: self.actions.append( Action(ActionType.START_MOVE, player=player)) self.actions.extend( path_to_move_actions(game, player, path)) #print(f"Make cage around towards ball carrier {player.role.name}") return # Scan for assist positions assist_positions = set() for player in game.get_opp_team(self.my_team).players: if player.position is None or not player.state.up: continue for opponent in game.get_adjacent_opponents(player, down=False): att_str, def_str = game.get_block_strengths(player, opponent) if def_str >= att_str: for open_position in game.get_adjacent_squares( player.position, occupied=False): if len( game.get_adjacent_players(open_position, team=self.opp_team, down=False)) == 1: assist_positions.add(open_position) #print("8. Move non-marked players to assist") for player in open_players: for path in pf.get_all_paths(game, player): if path.prob < 1.0 or path.get_last_step( ) not in assist_positions: continue self.actions.append( Action(ActionType.START_MOVE, player=player)) self.actions.extend(path_to_move_actions(game, player, path)) # print(f"Move assister {player.role.name} to {path.get_last_step().to_json}") return #print("9. Move towards the ball") for player in open_players: if player == ball_carrier or game.num_tackle_zones_in(player) > 0: continue shortest_distance = None path = None if ball_carrier is None: for p in pf.get_all_paths(game, player): distance = p.get_last_step().distance( game.get_ball_position()) if shortest_distance is None or ( p.prob == 1 and distance < shortest_distance): shortest_distance = distance path = p elif ball_carrier.team != self.my_team: for p in pf.get_all_paths(game, player): distance = p.get_last_step().distance( ball_carrier.position) if shortest_distance is None or ( p.prob == 1 and distance < shortest_distance): shortest_distance = distance path = p if path is not None: self.actions.append( Action(ActionType.START_MOVE, player=player)) self.actions.extend(path_to_move_actions(game, player, path)) #print(f"Move towards ball {player.role.name}") return #print("10. Risky blocks") attacker, defender, p_self_up, p_opp_down, block_p_fumble_self, block_p_fumble_opp = self._get_safest_block( game) if attacker is not None and (p_opp_down > (1 - p_self_up) or block_p_fumble_opp > 0): self.actions.append(Action(ActionType.START_BLOCK, player=attacker)) self.actions.append( Action(ActionType.BLOCK, position=defender.position)) #print(f"Block with {player.role.name} -> {defender.role.name}, p_self_up={p_self_up}, p_opp_down={p_opp_down}") return #print("11. End turn") self.actions.append(Action(ActionType.END_TURN))
def hash_game_state(game: botbowl.Game) -> int: s = "".join([f"{p.position.x}{p.position.y}{int(p.state.up)}" for p in game.get_players_on_pitch()]) s += "".join([f"{ball_pos.x}{ball_pos.y}" for ball_pos in game.get_ball_positions()]) return hash(s)
def expand_block(game: botbowl.Game, parent: Node) -> Node: proc: botbowl.Block = game.get_procedure() assert type(proc) is botbowl.Block assert not proc.gfi, "Can't handle GFI:s here =( " assert proc.roll is None attacker: botbowl.Player = proc.attacker defender: botbowl.Player = proc.defender dice = game.num_block_dice(attacker, defender) num_dice = abs(dice) # initialize as 1d block without skills dice_outcomes = np.array([2, 2, 1, 1], dtype=int) DEF_DOWN, NOONE_DOWN, ALL_DOWN, ATT_DOWN = (0, 1, 2, 3) die_results = ([BBDieResult.DEFENDER_DOWN, BBDieResult.DEFENDER_STUMBLES], [BBDieResult.PUSH], [BBDieResult.BOTH_DOWN], [BBDieResult.ATTACKER_DOWN]) who_has_block = (attacker.has_skill(Skill.BLOCK), defender.has_skill(Skill.BLOCK)) if any(who_has_block): dice_outcomes[ALL_DOWN] = 0 die_results[ALL_DOWN].clear() if who_has_block == (True, True): # both dice_outcomes[NOONE_DOWN] += 1 die_results[NOONE_DOWN].append(BBDieResult.BOTH_DOWN) elif who_has_block == (True, False): # only attacker dice_outcomes[DEF_DOWN] += 1 die_results[DEF_DOWN].append(BBDieResult.BOTH_DOWN) elif who_has_block == (False, True): # only defender dice_outcomes[ATT_DOWN] += 1 die_results[ATT_DOWN].append(BBDieResult.BOTH_DOWN) crowd_surf: bool = game.get_push_squares( attacker.position, defender.position)[0].out_of_bounds if crowd_surf: dice_outcomes[DEF_DOWN] += 2 dice_outcomes[NOONE_DOWN] -= 2 die_results[DEF_DOWN].append(BBDieResult.PUSH) die_results[NOONE_DOWN].remove(BBDieResult.PUSH) elif defender.has_skill( Skill.DODGE): # and not attacker.has_skill(Skill.TACKLE): dice_outcomes[DEF_DOWN] -= 1 dice_outcomes[NOONE_DOWN] += 1 die_results[DEF_DOWN].remove(BBDieResult.DEFENDER_STUMBLES) die_results[NOONE_DOWN].append(BBDieResult.DEFENDER_STUMBLES) prob = np.zeros(4) probability_left = 1.0 available_dice = 6 evaluation_order = [DEF_DOWN, NOONE_DOWN, ALL_DOWN, ATT_DOWN] if dice < 0: evaluation_order = reversed(evaluation_order) for i in evaluation_order: prob[i] = probability_left * ( 1 - (1 - dice_outcomes[i] / available_dice)**num_dice) available_dice -= dice_outcomes[i] probability_left -= prob[i] assert available_dice == 0 and probability_left == approx( 0) and prob.sum() == approx(1) new_parent = ChanceNode(game, parent) for prob, die_res in zip(prob, die_results): if prob == approx(0) or len(die_res) == 0: assert prob == approx(0) and len(die_res) == 0 continue expand_with_fixes(game, new_parent, prob, block_dice=np.random.choice(die_res, num_dice)) assert sum(new_parent.child_probability) == approx(1.0) return new_parent
def expand_moving(game: botbowl.Game, parent: Node) -> Node: # noinspection PyTypeChecker active_proc: Union[procedures.GFI, procedures.Dodge] = game.get_procedure() assert type(active_proc) is procedures.Dodge or type( active_proc) is procedures.GFI move_action_proc: procedures.MoveAction = first( proc for proc in reversed(game.state.stack.items) if isinstance(proc, procedures.MoveAction)) is_blitz = type(move_action_proc) is procedures.BlitzAction is_handoff = type(move_action_proc) is procedures.HandoffAction player = move_action_proc.player if move_action_proc.steps is not None: final_step = move_action_proc.steps[-1] else: if is_blitz: block_proc: procedures.Block = first( filter(lambda proc: type(proc) is procedures.Block, game.state.stack.items)) final_step = block_proc.defender.position elif is_handoff: raise ValueError() else: final_step = active_proc.position is_pickup = game.get_ball( ).position == final_step and not game.get_ball().is_carried path = move_action_proc.paths[final_step] """ This block of code sets two important variables: probability_success - probability of the remaining path rolls - list[int] - the remaining rolls of the path Normal case we just fetch this from the path object. If we're in a rerolled proc, it's nasty... """ if active_proc.roll is None: probability_success = path.prob rolls = list(collapse(path.rolls)) if is_pickup: # remove the pickup roll and probability rolls.pop() probability_success /= game.get_pickup_prob( active_proc.player, final_step) else: with only_fixed_rolls(game): game.step() new_proc = game.get_procedure() if type(new_proc) not in {procedures.GFI, procedures.Dodge}: assert not active_proc.reroll.use_reroll return expand_none_action(game, parent) # if we get here, it means that a reroll was used. assert new_proc is active_proc assert active_proc.roll is None assert active_proc.reroll is None current_step = active_proc.position try: assert player.position.distance( current_step) == 1 or is_pickup or is_blitz except AssertionError as e: raise e i = 0 while path.steps[i] != current_step: i += 1 remaining_current_step_rolls = path.rolls[i][:] if is_pickup and current_step == final_step: remaining_current_step_rolls.pop() num_current_step_remaining_rolls = len( list( filter(lambda x: type(x) in {procedures.GFI, procedures.Dodge}, game.state.stack.items))) assert num_current_step_remaining_rolls in [1, 2] remaining_current_step_rolls = remaining_current_step_rolls[ -num_current_step_remaining_rolls:] probability_success = reduce( operator.mul, map(lambda d: (7 - d) / 6, remaining_current_step_rolls), 1.0) rolls = list(collapse(remaining_current_step_rolls)) if current_step != final_step: step_count = game.get_step() if player.position != current_step: try: game.move(player, current_step) except AssertionError as e: raise e new_path = pf.get_safest_path(game, player, final_step, blitz=is_blitz) game.revert(step_count) #try: # # assert new_path.steps == path.steps[-len(new_path):] this assert can't be made because of small randomness in pathfinder # assert list(collapse(new_path.rolls)) == list(collapse(path.rolls[-len(new_path):])), f"{new_path.rolls} != {path.rolls[-len(new_path):]}" #except AssertionError as e: # raise e try: rolls.extend(collapse(new_path.rolls)) except AttributeError as e: raise e probability_success *= new_path.prob if is_pickup: # remove the pickup roll and probability rolls.pop() probability_success /= game.get_pickup_prob( active_proc.player, final_step) try: p = np.array(rolls) / sum(rolls) index_of_failure = np.random.choice(range(len(rolls)), 1, p=p)[0] except ValueError as e: raise e # STEP UNTIL FAILURE (possibly no steps at all) with only_fixed_rolls(game, d6=[6] * index_of_failure): while len(botbowl.D6.FixedRolls) > 0: if len(game.get_available_actions()) > 0: raise AttributeError("wrong") game.step() new_parent = ChanceNode(game, parent) debug_step_count = game.get_step() # SUCCESS SCENARIO with only_fixed_rolls(game, d6=[6] * (len(rolls) - index_of_failure)): while len(botbowl.D6.FixedRolls) > 0: if type(game.get_procedure()) not in { procedures.GFI, procedures.Block, procedures.Dodge, procedures.Move, procedures.MoveAction, procedures.BlitzAction }: raise AttributeError("wrong") if len(game.get_available_actions()) > 0: raise AttributeError("wrong") if type(game.get_procedure() ) is procedures.Block and not game.get_procedure().gfi: raise AttributeError("wrong") game.step() success_node = expand_none_action(game, new_parent, moving_handled=True) new_parent.connect_child(success_node, probability_success) assert debug_step_count == game.get_step() # FAILURE SCENARIO with only_fixed_rolls(game, d6=[1]): while len(botbowl.D6.FixedRolls) > 0: if len(game.get_available_actions()) > 0: raise AttributeError("wrong") game.step() fail_node = expand_none_action(game, new_parent, moving_handled=True) new_parent.connect_child(fail_node, 1 - probability_success) assert debug_step_count == game.get_step() return new_parent