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 _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))