Beispiel #1
0
    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
Beispiel #2
0
    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)
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
    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
Beispiel #6
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
Beispiel #7
0
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)
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
    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
Beispiel #11
0
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
Beispiel #12
0
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
Beispiel #13
0
 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 []
Beispiel #14
0
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)
Beispiel #15
0
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
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
    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
Beispiel #19
0
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
Beispiel #20
0
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)
Beispiel #21
0
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)
Beispiel #22
0
    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
Beispiel #23
0
    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))
Beispiel #24
0
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)
Beispiel #25
0
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
Beispiel #26
0
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