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 test_pickup_score(): game, (player, ) = get_custom_game_turn(player_positions=[(5, 5)], ball_position=(3, 3), forward_model_enabled=True, pathfinding_enabled=True) player.role.ma = 4 game.set_available_actions() weights = HeuristicVector(score=1, ball_marked=0, ball_carried=0, ball_position=0, tv_on_pitch=0) tree = SearchTree(game) policy = MockPolicy() def search_select_step(): for i in range(40): do_mcts_branch(tree, policy, weights, exploration_coeff=0.5) info: MCTS_Info = tree.root_node.info print(" ") print_node(tree.root_node, weights) return info.actions[np.argmax(info.visits)] a = search_select_step() assert a.action_type == ActionType.START_MOVE and a.position == player.position with only_fixed_rolls(game): game.step(a) tree.set_new_root(game) setup_node: ActionNode = first( filter(lambda n: n.simple_hash.find('Setup') > 0, tree.all_action_nodes)) assert setup_node.get_accum_prob() == approx(2 / 3) a = search_select_step() assert a.action_type == ActionType.MOVE and a.position == Square(3, 3) with only_fixed_rolls(game, d6=[3]): game.step(a) tree.set_new_root(game) a = search_select_step() assert a.action_type == ActionType.MOVE and a.position.x == 1 with only_fixed_rolls(game): game.step(a) tree.set_new_root(game)
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 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 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 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 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 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 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 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 expand_with_fixes(game, parent, probability, **fixes): with only_fixed_rolls(game, **fixes): game.step() new_node = expand_none_action(game, parent) parent.connect_child(new_node, probability)
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