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 reroll(self, game): """ Select between USE_REROLL and DONT_USE_REROLL """ reroll_proc = game.get_procedure() context = reroll_proc.context if type(context) == botbowl.Dodge: return Action(ActionType.USE_REROLL) if type(context) == botbowl.Pickup: return Action(ActionType.USE_REROLL) if type(context) == botbowl.PassAttempt: return Action(ActionType.USE_REROLL) if type(context) == botbowl.Catch: return Action(ActionType.USE_REROLL) if type(context) == botbowl.GFI: return Action(ActionType.USE_REROLL) if type(context) == botbowl.BloodLust: return Action(ActionType.USE_REROLL) if type(context) == botbowl.Block: attacker = context.attacker attackers_down = 0 for die in context.roll.dice: if die.get_value() == BBDieResult.ATTACKER_DOWN: attackers_down += 1 elif die.get_value( ) == BBDieResult.BOTH_DOWN and not attacker.has_skill( Skill.BLOCK) and not attacker.has_skill(Skill.WRESTLE): attackers_down += 1 if attackers_down > 0 and context.favor != self.my_team: return Action(ActionType.USE_REROLL) if attackers_down == len( context.roll.dice) and context.favor != self.opp_team: return Action(ActionType.USE_REROLL) return Action(ActionType.DONT_USE_REROLL) return Action(ActionType.DONT_USE_REROLL)
def test_expand_move(data): move_target, outcome_probs = data assert sum(outcome_probs) == 1.0 game, (player, _, _) = get_custom_game_turn(player_positions=[(1, 1)], opp_player_positions=[(1, 3), (3, 1)], forward_model_enabled=True, pathfinding_enabled=True) game.step(Action(ActionType.START_MOVE, position=player.position)) action = Action(ActionType.MOVE, position=move_target) parent_action_node: ActionNode = ActionNode(game, parent=None) next_node: Node = expand_action(game, action, parent_action_node) assert next_node.parent is parent_action_node if len(outcome_probs) == 1: assert type(next_node) is ActionNode else: next_node: ChanceNode assert type(next_node) is ChanceNode assert sum(next_node.child_probability) == 1.0 assert all(y == approx(x, abs=1e-12) for x, y in zip( sorted(next_node.child_probability), sorted(outcome_probs)))
def test_expand_block(): game, (attacker, _, defender) = get_custom_game_turn(player_positions=[(5, 5), (7, 7)], opp_player_positions=[(6, 6)], forward_model_enabled=True) defender.extra_skills.append(Skill.DODGE) tree = SearchTree(game) next_node, = tree.expand_action_node( tree.root_node, Action(ActionType.START_BLOCK, player=attacker)) assert len(tree.all_action_nodes) == 2 next_node, *_ = tree.expand_action_node( next_node, Action(ActionType.BLOCK, position=defender.position)) assert len(tree.all_action_nodes) == 6 next_node, = tree.expand_action_node( next_node, Action(ActionType.SELECT_DEFENDER_DOWN)) assert len(tree.all_action_nodes) == 7 next_node, = tree.expand_action_node( next_node, Action(ActionType.PUSH, position=Square(7, 6))) assert len(tree.all_action_nodes) == 8 next_node, *_ = tree.expand_action_node( next_node, Action(ActionType.FOLLOW_UP, position=Square(6, 6))) assert len(tree.all_action_nodes) == 11
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 place_ball(self, game): """ Place the ball when kicking. """ left_center = Square(7, 8) right_center = Square(20, 8) if game.is_team_side(left_center, self.opp_team): return Action(ActionType.PLACE_BALL, position=left_center) return Action(ActionType.PLACE_BALL, position=right_center)
def interception(self, game): """ Select interceptor. """ for action in game.state.available_actions: if action.action_type == ActionType.SELECT_PLAYER: for player, rolls in zip(action.players, action.rolls): return Action(ActionType.SELECT_PLAYER, player=player) return Action(ActionType.SELECT_NONE)
def block(self, game): """ Select block die or reroll. """ # Get attacker and defender attacker = game.get_procedure().attacker defender = game.get_procedure().defender is_blitz = game.get_procedure().blitz dice = game.num_block_dice(attacker, defender, blitz=is_blitz) # Loop through available dice results actions = set() for action_choice in game.state.available_actions: actions.add(action_choice.action_type) # 1. DEFENDER DOWN if ActionType.SELECT_DEFENDER_DOWN in actions: return Action(ActionType.SELECT_DEFENDER_DOWN) if ActionType.SELECT_DEFENDER_STUMBLES in actions and not ( defender.has_skill(Skill.DODGE) and not attacker.has_skill(Skill.TACKLE)): return Action(ActionType.SELECT_DEFENDER_STUMBLES) if ActionType.SELECT_BOTH_DOWN in actions and not defender.has_skill( Skill.BLOCK) and attacker.has_skill(Skill.BLOCK): return Action(ActionType.SELECT_BOTH_DOWN) # 2. BOTH DOWN if opponent carries the ball and doesn't have block if ActionType.SELECT_BOTH_DOWN in actions and game.get_ball_carrier( ) == defender and not defender.has_skill(Skill.BLOCK): return Action(ActionType.SELECT_BOTH_DOWN) # 3. USE REROLL if defender carries the ball if ActionType.USE_REROLL in actions and game.get_ball_carrier( ) == defender: return Action(ActionType.USE_REROLL) # 4. PUSH if ActionType.SELECT_DEFENDER_STUMBLES in actions: return Action(ActionType.SELECT_DEFENDER_STUMBLES) if ActionType.SELECT_PUSH in actions: return Action(ActionType.SELECT_PUSH) # 5. BOTH DOWN if ActionType.SELECT_BOTH_DOWN in actions: return Action(ActionType.SELECT_BOTH_DOWN) # 6. USE REROLL to avoid attacker down unless a one-die block if ActionType.USE_REROLL in actions and dice > 1: return Action(ActionType.USE_REROLL) # 7. ATTACKER DOWN if ActionType.SELECT_ATTACKER_DOWN in actions: return Action(ActionType.SELECT_ATTACKER_DOWN)
def convert_to_actions( action_choice: botbowl.ActionChoice) -> Iterable[Action]: if len(action_choice.positions) > 0: return (Action(action_choice.action_type, position=sq) for sq in action_choice.positions) elif len(action_choice.players) > 0: return (Action(action_choice.action_type, player=p) for p in action_choice.players) else: return (Action(action_choice.action_type) for _ in range(1))
def touchback(self, game): """ Select player to give the ball to. """ p = None for player in game.get_players_on_pitch(self.my_team, up=True): if Skill.BLOCK in player.get_skills(): return Action(ActionType.SELECT_PLAYER, player=player) p = player return Action(ActionType.SELECT_PLAYER, player=p)
def high_kick(self, game): """ Select player to move under the ball. """ ball_pos = game.get_ball_position() if game.is_team_side(game.get_ball_position(), self.my_team) and \ game.get_player_at(game.get_ball_position()) is None: for player in game.get_players_on_pitch(self.my_team, up=True): if Skill.BLOCK in player.get_skills( ) and game.num_tackle_zones_in(player) == 0: return Action(ActionType.SELECT_PLAYER, player=player, position=ball_pos) return Action(ActionType.SELECT_NONE)
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 __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 push(self, game): """ Select square to push to. """ # Loop through available squares for position in game.state.available_actions[0].positions: return Action(ActionType.PUSH, position=position)
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 follow_up(self, game): """ Follow up or not. ActionType.FOLLOW_UP must be used together with a position. """ player = game.state.active_player for position in game.state.available_actions[0].positions: # Always follow up if player.position != position: return Action(ActionType.FOLLOW_UP, position=position)
def test_expand_throw_in(): game, (attacker, defender) = get_custom_game_turn(player_positions=[(5, 2)], opp_player_positions=[(5, 1)], ball_position=(5, 1), forward_model_enabled=True, pathfinding_enabled=True) with only_fixed_rolls(game, block_dice=[BBDieResult.DEFENDER_DOWN]): game.step(Action(ActionType.START_BLOCK, position=attacker.position)) game.step(Action(ActionType.BLOCK, position=defender.position)) game.step(Action(ActionType.SELECT_DEFENDER_DOWN)) action = Action(ActionType.PUSH, position=Square(5, 0)) tree = SearchTree(game) tree.expand_action_node(tree.root_node, action) assert len(tree.all_action_nodes) == 2
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 test_set_new_root(): game, (player1, player2, opp_player) = get_custom_game_turn(player_positions=[(5, 5), (6, 6)], opp_player_positions=[(4, 4)], ball_position=(5, 5), pathfinding_enabled=True, forward_model_enabled=True) action_p2_1 = Action(ActionType.START_BLITZ, position=player2.position) action_p2_2 = Action(ActionType.BLOCK, position=Square(4, 4)) action_p1_1 = Action(ActionType.START_MOVE, position=player1.position) action_p1_2 = Action(ActionType.MOVE, position=Square(1, 5)) tree = SearchTree(deepcopy(game)) assert tree.root_node.depth == 0 # Move player 2 new_node, = tree.expand_action_node(tree.root_node, action_p2_1) new_nodes = tree.expand_action_node(new_node, action_p2_2) assert new_node.depth == 1 assert new_nodes[0].depth == 2 assert len(tree.all_action_nodes) == 2 + 4 # Move player 1 new_node, = tree.expand_action_node(tree.root_node, action_p1_1) assert len(tree.all_action_nodes) == 2 + 4 + 1 new_nodes = tree.expand_action_node(new_node, action_p1_2) assert new_node.depth == 1 assert new_nodes[0].depth == 2 assert len(tree.all_action_nodes) == 2 + 4 + 1 + 7 game.step(action_p1_1) tree.set_new_root(game) assert len(tree.all_action_nodes) == 8 assert new_node is tree.root_node assert new_node.depth == 0 assert new_nodes[0].depth == 1 with only_fixed_rolls(game, d6=[6]): game.step(action_p1_2) tree.set_new_root(game) tree.expand_action_node(tree.root_node, Action(ActionType.SETUP_FORMATION_SPREAD)) assert new_nodes[0] is tree.root_node assert len(tree.all_action_nodes) == 2 game.step(Action(ActionType.SETUP_FORMATION_SPREAD)) game.step(Action(ActionType.END_SETUP)) tree.set_new_root(game) assert len(tree.all_action_nodes) == 1 assert len(tree.root_node.children) == 0
def setup(self, game): """ Use either a Wedge offensive formation or zone defensive formation. """ # Update teams self.my_team = game.get_team_by_id(self.my_team.team_id) self.opp_team = game.get_opp_team(self.my_team) if self.setup_actions: action = self.setup_actions.pop(0) return action else: if game.get_receiving_team() == self.my_team: self.setup_actions = self.off_formation.actions( game, self.my_team) self.setup_actions.append(Action(ActionType.END_SETUP)) else: self.setup_actions = self.def_formation.actions( game, self.my_team) self.setup_actions.append(Action(ActionType.END_SETUP)) action = self.setup_actions.pop(0) return action
def player_action(self, game): # Execute planned actions if any if len(self.actions) > 0: action = self._get_next_action() return action ball_carrier = game.get_ball_carrier() if ball_carrier == game.get_active_player(): td_path = pf.get_safest_path_to_endzone(game, ball_carrier) if td_path is not None and td_path.prob <= 0.9: self.actions.extend( path_to_move_actions(game, ball_carrier, td_path)) #print(f"Scoring with {ball_carrier.role.name}, p={td_path.prob}") return self._get_next_action() return Action(ActionType.END_PLAYER_TURN)
def turn(self, game): """ Start a new player action. """ # Update teams self.my_team = game.get_team_by_id(self.my_team.team_id) self.opp_team = game.get_opp_team(self.my_team) # Reset actions if new turn turn = game.get_agent_team(self).state.turn half = game.state.half if half > self.last_half or turn > self.last_turn: self.actions.clear() self.last_turn = turn self.last_half = half self.actions = [] #print(f"Half: {half}") #print(f"Turn: {turn}") # End turn if only action left if len(game.state.available_actions) == 1: if game.state.available_actions[ 0].action_type == ActionType.END_TURN: self.actions = [Action(ActionType.END_TURN)] # Execute planned actions if any while len(self.actions) > 0: action = self._get_next_action() if game._is_action_allowed(action): return action # Split logic depending on offense, defense, and loose ball - and plan actions ball_carrier = game.get_ball_carrier() self._make_plan(game, ball_carrier) action = self._get_next_action() return action
def coin_toss_kick_receive(self, game): """ Select heads/tails and/or kick/receive """ return Action(ActionType.RECEIVE)
def blood_lust_block_or_move(self, game): return Action(ActionType.START_BLOCK)
def eat_thrall(self, game): position = game.get_available_actions()[0].positions[0] return Action(ActionType.SELECT_PLAYER, position)
def use_bribe(self, game): return Action(ActionType.USE_BRIBE)
def use_pro(self, game): return Action(ActionType.USE_SKILL)
def use_stand_firm(self, game): return Action(ActionType.USE_SKILL)
def use_wrestle(self, game): return Action(ActionType.USE_SKILL)
def use_juggernaut(self, game): return Action(ActionType.USE_SKILL)