def __init__(
        self,
        index: int,
        game: GameDescription,
        initial_state: State,
        pickups_left: list[PickupEntry],
        configuration: FillerConfiguration,
    ):
        self.index = index
        self.game = game

        self.reach = reach_lib.advance_reach_with_possible_unsafe_resources(
            reach_lib.reach_with_all_safe_resources(game, initial_state))
        self.pickups_left = pickups_left
        self.configuration = configuration

        self.pickup_index_considered_count = collections.defaultdict(int)
        self.hint_seen_count = collections.defaultdict(int)
        self.event_seen_count = collections.defaultdict(int)
        self.hint_initial_pickups = {}
        self.num_random_starting_items_placed = 0
        self.num_assigned_pickups = 0
        self.num_actions = 0
        self.indices_groups, self.all_indices = build_available_indices(
            game.world_list, configuration)
def test_database_collectable(preset_manager, game_enum: RandovaniaGame,
                              ignore_events: set[str],
                              ignore_pickups: set[int]):
    game, initial_state, permalink = run_bootstrap(
        preset_manager.default_preset_for_game(game_enum).get_preset())
    all_pickups = set(
        reach_lib.filter_pickup_nodes(game.world_list.iterate_nodes()))
    pool_results = pool_creator.calculate_pool_results(
        permalink.get_preset(0).configuration, game.resource_database)

    initial_state.resources.add_resource_gain(
        pool_results.initial_resources.as_resource_gain())
    for pickup in pool_results.pickups:
        add_pickup_to_state(initial_state, pickup)
    for pickup in pool_results.assignment.values():
        add_pickup_to_state(initial_state, pickup)
    for trick in game.resource_database.trick:
        initial_state.resources.set_resource(
            trick,
            LayoutTrickLevel.maximum().as_number)

    expected_events = sorted([
        event for event in game.resource_database.event
        if event.short_name not in ignore_events
    ],
                             key=lambda it: it.short_name)
    expected_pickups = sorted(it.pickup_index for it in all_pickups
                              if it.pickup_index.index not in ignore_pickups)

    reach = _create_reach_with_unsafe(game, initial_state.heal())
    while list(reach_lib.collectable_resource_nodes(reach.nodes, reach)):
        reach.act_on(
            next(iter(reach_lib.collectable_resource_nodes(reach.nodes,
                                                           reach))))
        reach = advance_reach_with_possible_unsafe_resources(reach)

    # print("\nCurrent reach:")
    # print(game.world_list.node_name(reach.state.node, with_world=True))
    # for world in game.world_list.worlds:
    #     print(f"\n>> {world.name}")
    #     for node in world.all_nodes:
    #         print("[{!s:>5}, {!s:>5}, {!s:>5}] {}".format(
    #             reach.is_reachable_node(node), reach.is_safe_node(node),
    #             reach.state.resources.get(node.resource(), 0) > 0
    #             if isinstance(node, ResourceNode) else "",
    #             game.world_list.node_name(node, with_world=True)))

    collected_indices = set(reach.state.collected_pickup_indices)
    collected_events = {
        resource
        for resource, quantity in reach.state.resources.as_resource_gain()
        if quantity > 0 and resource.resource_type == ResourceType.EVENT
    }
    assert list(reach_lib.collectable_resource_nodes(reach.nodes, reach)) == []
    assert sorted(collected_indices) == expected_pickups
    assert sorted(collected_events,
                  key=lambda it: it.short_name) == expected_events
Exemple #3
0
def retcon_playthrough_filler(
    rng: Random,
    player_states: list[PlayerState],
    status_update: Callable[[str], None],
) -> tuple[dict[PlayerState, GamePatches], tuple[str, ...]]:
    """
    Runs the retcon logic.
    :param rng:
    :param player_states:
    :param status_update:
    :return: A GamePatches for each player and a sequence of placed items.
    """
    debug.debug_print("{}\nRetcon filler started with major items:\n{}".format(
        "*" * 100, "\n".join("Player {}: {}".format(
            player_state.index,
            pprint.pformat({
                item.name: player_state.pickups_left.count(item)
                for item in sorted(set(player_state.pickups_left),
                                   key=lambda item: item.name)
            })) for player_state in player_states)))
    last_message = "Starting."

    def action_report(message: str):
        status_update("{} {}".format(last_message, message))

    for player_state in player_states:
        player_state.update_for_new_state()

    actions_log = []

    while True:
        all_locations_weighted = _calculate_all_pickup_indices_weight(
            player_states)
        current_player = _get_next_player(rng, player_states,
                                          all_locations_weighted)
        if current_player is None:
            break

        weighted_actions = weighted_potential_actions(current_player,
                                                      action_report,
                                                      all_locations_weighted)
        action = select_weighted_action(rng, weighted_actions)

        if isinstance(action, tuple):
            new_pickups: list[PickupEntry] = sorted(action)
            rng.shuffle(new_pickups)

            debug.debug_print(f"\n>>> Will place {len(new_pickups)} pickups")
            for new_pickup in new_pickups:
                log_entry = _assign_pickup_somewhere(new_pickup,
                                                     current_player,
                                                     player_states, rng,
                                                     all_locations_weighted)
                actions_log.append(log_entry)
                debug.debug_print(f"* {log_entry}")

                # TODO: this item is potentially dangerous and we should remove the invalidated paths
                current_player.pickups_left.remove(new_pickup)

            current_player.num_actions += 1
        else:
            debug_print_collect_event(action, current_player.game)
            # This action is potentially dangerous. Use `act_on` to remove invalid paths
            current_player.reach.act_on(action)

        last_message = "{} actions performed.".format(
            sum(player.num_actions for player in player_states))
        status_update(last_message)
        current_player.reach = reach_lib.advance_reach_with_possible_unsafe_resources(
            current_player.reach)
        current_player.update_for_new_state()

    all_patches = {
        player_state: player_state.reach.state.patches
        for player_state in player_states
    }
    return all_patches, tuple(actions_log)
def _create_reach_with_unsafe(game: GameDescription,
                              state: State) -> GeneratorReach:
    return reach_lib.advance_reach_with_possible_unsafe_resources(
        reach_lib.reach_with_all_safe_resources(game, state))