Ejemplo n.º 1
0
    def calculate_reach(cls, logic: Logic,
                        initial_state: State) -> "ResolverReach":

        checked_nodes = set()
        nodes_to_check: List[Node] = [initial_state.node]

        reach_nodes: List[Node] = []
        requirements_by_node: Dict[Node,
                                   Set[RequirementList]] = defaultdict(set)

        path_to_node: Dict[Node, Tuple[Node, ...]] = {}
        path_to_node[initial_state.node] = tuple()

        while nodes_to_check:
            node = nodes_to_check.pop()
            checked_nodes.add(node)

            if node != initial_state.node:
                reach_nodes.append(node)

            for target_node, requirements in logic.game.world_list.potential_nodes_from(
                    node, initial_state.patches):
                if target_node in checked_nodes or target_node in nodes_to_check:
                    continue

                # Check if the normal requirements to reach that node is satisfied
                satisfied = requirements.satisfied(
                    initial_state.resources, initial_state.resource_database)
                if satisfied:
                    # If it is, check if we additional requirements figured out by backtracking is satisfied
                    satisfied = logic.get_additional_requirements(
                        node).satisfied(initial_state.resources,
                                        initial_state.resource_database)

                if satisfied:
                    nodes_to_check.append(target_node)
                    path_to_node[target_node] = path_to_node[node] + (node, )

                elif target_node:
                    # If we can't go to this node, store the reason in order to build the satisfiable requirements.
                    # Note we ignore the 'additional requirements' here because it'll be added on the end.
                    requirements_by_node[target_node].update(
                        requirements.alternatives)

        # Discard satisfiable requirements of nodes reachable by other means
        for node in set(reach_nodes).intersection(requirements_by_node.keys()):
            requirements_by_node.pop(node)

        if requirements_by_node:
            satisfiable_requirements = frozenset.union(*[
                RequirementSet(requirements).union(
                    logic.get_additional_requirements(node)).alternatives
                for node, requirements in requirements_by_node.items()
            ])
        else:
            satisfiable_requirements = frozenset()

        return ResolverReach(reach_nodes, path_to_node,
                             satisfiable_requirements, logic)
Ejemplo n.º 2
0
def logic_bootstrap(
    configuration: LayoutConfiguration,
    game: GameDescription,
    patches: GamePatches,
) -> Tuple[Logic, State]:
    """
    Core code for starting a new Logic/State.
    :param configuration:
    :param game:
    :param patches:
    :return:
    """

    # global state for easy printing functions
    debug._gd = game

    game = copy.deepcopy(game)
    logic = Logic(game, configuration)
    starting_state = calculate_starting_state(logic, patches)

    if configuration.trick_level == LayoutTrickLevel.MINIMAL_RESTRICTIONS:
        _add_minimal_restrictions_initial_resources(starting_state.resources,
                                                    game.resource_database)

    difficulty_level, static_resources = static_resources_for_layout_logic(
        configuration.trick_level, game.resource_database)

    starting_state.resources = merge_resources(static_resources,
                                               starting_state.resources)
    starting_state.resources[
        game.resource_database.difficulty_resource] = difficulty_level

    game.simplify_connections(starting_state.resources)

    return logic, starting_state
Ejemplo n.º 3
0
    def __init__(self, persistence_path: Path, layout_configuration: LayoutConfiguration):
        super().__init__()
        self.setupUi(self)
        set_default_window_icon(self)

        self._collected_pickups = {}
        self._widget_for_pickup = {}
        self._actions = []
        self._asset_id_to_item = {}
        self._node_to_item = {}
        self.layout_configuration = layout_configuration
        self.persistence_path = persistence_path

        player_index = 0  # FIXME
        try:
            player_pool = generator.create_player_pool(None, self.layout_configuration, player_index)
        except base_patches_factory.MissingRng as e:
            raise InvalidLayoutForTracker("Layout is configured to have random {}, "
                                          "but that isn't supported by the tracker.".format(e))

        pool_patches = player_pool.patches
        self.game_description, self._initial_state = logic_bootstrap(layout_configuration,
                                                                     player_pool.game,
                                                                     pool_patches)
        self.logic = Logic(self.game_description, layout_configuration)

        self._initial_state.resources["add_self_as_requirement_to_resources"] = 1

        self.menu_reset_action.triggered.connect(self._confirm_reset)
        self.resource_filter_check.stateChanged.connect(self.update_locations_tree_for_reachable_nodes)
        self.hide_collected_resources_check.stateChanged.connect(self.update_locations_tree_for_reachable_nodes)
        self.undo_last_action_button.clicked.connect(self._undo_last_action)

        self.configuration_label.setText("Trick Level: {}; Elevators: Vanilla; Starts with:\n{}".format(
            layout_configuration.trick_level_configuration.global_level.value,
            ", ".join(
                resource.short_name
                for resource in pool_patches.starting_items.keys()
            )
        ))

        self.setup_pickups_box(player_pool.pickups)
        self.setup_possible_locations_tree()

        self._starting_nodes = {
            node
            for node in self.game_description.world_list.all_nodes
            if node.is_resource_node and node.resource() in self._initial_state.resources
        }

        persistence_path.mkdir(parents=True, exist_ok=True)
        previous_state = _load_previous_state(persistence_path, layout_configuration)

        if not self.apply_previous_state(previous_state):
            with persistence_path.joinpath("layout_configuration.json").open("w") as layout_file:
                json.dump(layout_configuration.as_json, layout_file)
            self._add_new_action(self._initial_state.node)
Ejemplo n.º 4
0
    def __init__(self, persistence_path: Path,
                 layout_configuration: LayoutConfiguration):
        super().__init__()
        self.setupUi(self)
        set_default_window_icon(self)

        self._collected_pickups = {}
        self._widget_for_pickup = {}
        self._actions = []
        self._asset_id_to_item = {}
        self._node_to_item = {}
        self.layout_configuration = layout_configuration
        self.persistence_path = persistence_path

        player_pool = generator.create_player_pool(Random(0),
                                                   self.layout_configuration,
                                                   0)
        pool_patches = player_pool.patches
        self.game_description, self._initial_state = logic_bootstrap(
            layout_configuration, player_pool.game, pool_patches)
        self.logic = Logic(self.game_description, layout_configuration)

        self._initial_state.resources[
            "add_self_as_requirement_to_resources"] = 1

        self.menu_reset_action.triggered.connect(self._confirm_reset)
        self.resource_filter_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.hide_collected_resources_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.undo_last_action_button.clicked.connect(self._undo_last_action)

        self.configuration_label.setText(
            "Trick Level: {}; Starts with:\n{}".format(
                layout_configuration.trick_level_configuration.
                pretty_description,
                ", ".join(resource.short_name
                          for resource in pool_patches.starting_items.keys())))

        self.setup_pickups_box(player_pool.pickups)
        self.setup_possible_locations_tree()
        self.setup_elevators()
        self.setup_translator_gates()

        persistence_path.mkdir(parents=True, exist_ok=True)
        previous_state = _load_previous_state(persistence_path,
                                              layout_configuration)

        if not self.apply_previous_state(previous_state):
            self.setup_starting_location(None)

            with persistence_path.joinpath("layout_configuration.json").open(
                    "w") as layout_file:
                json.dump(layout_configuration.as_json, layout_file)
            self._add_new_action(self._initial_state.node)
Ejemplo n.º 5
0
def resolve(configuration: LayoutConfiguration,
            patches: GamePatches,
            status_update: Optional[Callable[[str], None]] = None
            ) -> Optional[State]:
    if status_update is None:
        status_update = _quiet_print

    game = data_reader.decode_data(configuration.game_data)
    event_pickup.replace_with_event_pickups(game)

    new_game, starting_state = logic_bootstrap(configuration, game, patches)
    logic = Logic(new_game, configuration)
    starting_state.resources["add_self_as_requirement_to_resources"] = 1
    debug.log_resolve_start()

    return advance_depth(starting_state, logic, status_update)
Ejemplo n.º 6
0
def setup_resolver(configuration: BaseConfiguration,
                   patches: GamePatches) -> Tuple[State, Logic]:
    set_attempts(0)

    game = filtered_database.game_description_for_layout(
        configuration).get_mutable()
    bootstrap = game.game.generator.bootstrap

    game.resource_database = bootstrap.patch_resource_database(
        game.resource_database, configuration)

    new_game, starting_state = bootstrap.logic_bootstrap(
        configuration, game, patches)
    logic = Logic(new_game, configuration)
    starting_state.resources.add_self_as_requirement_to_resources = True

    return starting_state, logic
Ejemplo n.º 7
0
async def resolve(
        configuration: EchoesConfiguration,
        patches: GamePatches,
        status_update: Optional[Callable[[str],
                                         None]] = None) -> Optional[State]:
    if status_update is None:
        status_update = _quiet_print

    game = copy.deepcopy(
        default_database.game_description_for(configuration.game))
    event_pickup.replace_with_event_pickups(game)

    new_game, starting_state = logic_bootstrap(configuration, game, patches)
    logic = Logic(new_game, configuration)
    starting_state.resources["add_self_as_requirement_to_resources"] = 1
    debug.log_resolve_start()

    return await advance_depth(starting_state, logic, status_update)
Ejemplo n.º 8
0
async def resolve(
        configuration: BaseConfiguration,
        patches: GamePatches,
        status_update: Optional[Callable[[str],
                                         None]] = None) -> Optional[State]:
    if status_update is None:
        status_update = _quiet_print

    game = filtered_database.game_description_for_layout(
        configuration).get_mutable()
    derived_nodes.create_derived_nodes(game)
    bootstrap = game.game.generator.bootstrap

    game.resource_database = bootstrap.patch_resource_database(
        game.resource_database, configuration)
    event_pickup.replace_with_event_pickups(game)

    new_game, starting_state = bootstrap.logic_bootstrap(
        configuration, game, patches)
    logic = Logic(new_game, configuration)
    starting_state.resources["add_self_as_requirement_to_resources"] = 1
    debug.log_resolve_start()

    return await advance_depth(starting_state, logic, status_update)
Ejemplo n.º 9
0
    def calculate_reach(cls,
                        logic: Logic,
                        initial_state: State) -> "ResolverReach":

        checked_nodes: Dict[Node, int] = {}

        # Keys: nodes to check
        # Value: how much energy was available when visiting that node
        nodes_to_check: Dict[Node, int] = {
            initial_state.node: initial_state.energy
        }

        reach_nodes: Dict[Node, int] = {}
        requirements_by_node: Dict[Node, Set[RequirementList]] = defaultdict(set)

        path_to_node: Dict[Node, Tuple[Node, ...]] = {}
        path_to_node[initial_state.node] = tuple()

        while nodes_to_check:
            node = next(iter(nodes_to_check))
            energy = nodes_to_check.pop(node)

            if node.heal:
                energy = initial_state.maximum_energy

            checked_nodes[node] = energy
            if node != initial_state.node:
                reach_nodes[node] = energy

            requirement_to_leave = node.requirement_to_leave(initial_state.patches, initial_state.resources)

            for target_node, requirement in logic.game.world_list.potential_nodes_from(node, initial_state.patches):
                if target_node is None:
                    continue

                if checked_nodes.get(target_node, math.inf) <= energy or nodes_to_check.get(target_node,
                                                                                            math.inf) <= energy:
                    continue

                if requirement_to_leave != Requirement.trivial():
                    requirement = RequirementAnd([requirement, requirement_to_leave])

                # Check if the normal requirements to reach that node is satisfied
                satisfied = requirement.satisfied(initial_state.resources, energy)
                if satisfied:
                    # If it is, check if we additional requirements figured out by backtracking is satisfied
                    satisfied = logic.get_additional_requirements(node).satisfied(initial_state.resources,
                                                                                  energy)

                if satisfied:
                    nodes_to_check[target_node] = energy - requirement.damage(initial_state.resources)
                    path_to_node[target_node] = path_to_node[node] + (node,)

                elif target_node:
                    # If we can't go to this node, store the reason in order to build the satisfiable requirements.
                    # Note we ignore the 'additional requirements' here because it'll be added on the end.
                    requirements_by_node[target_node].update(requirement.as_set.alternatives)

        # Discard satisfiable requirements of nodes reachable by other means
        for node in set(reach_nodes.keys()).intersection(requirements_by_node.keys()):
            requirements_by_node.pop(node)

        if requirements_by_node:
            satisfiable_requirements = frozenset.union(
                *[RequirementSet(requirements).union(logic.get_additional_requirements(node)).alternatives
                  for node, requirements in requirements_by_node.items()])
        else:
            satisfiable_requirements = frozenset()

        return ResolverReach(reach_nodes, path_to_node,
                             satisfiable_requirements,
                             logic)
Ejemplo n.º 10
0
    async def configure(self):
        player_pool = await generator.create_player_pool(
            None, self.game_configuration, 0, 1, rng_required=False)
        pool_patches = player_pool.patches

        bootstrap = self.game_configuration.game.generator.bootstrap

        self.game_description, self._initial_state = bootstrap.logic_bootstrap(
            self.preset.configuration, player_pool.game, pool_patches)
        self.logic = Logic(self.game_description, self.preset.configuration)
        self.map_canvas.select_game(self.game_description.game)

        self._initial_state.resources.add_self_as_requirement_to_resources = True

        self.menu_reset_action.triggered.connect(self._confirm_reset)
        self.resource_filter_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.hide_collected_resources_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.undo_last_action_button.clicked.connect(self._undo_last_action)

        self.configuration_label.setText(
            "Trick Level: {}; Starts with:\n{}".format(
                self.preset.configuration.trick_level.pretty_description,
                ", ".join(resource.short_name for resource, _ in
                          pool_patches.starting_items.as_resource_gain())))

        self.setup_pickups_box(player_pool.pickups)
        self.setup_possible_locations_tree()
        self.setup_elevators()
        self.setup_translator_gates()

        # Map
        for world in sorted(self.game_description.world_list.worlds,
                            key=lambda x: x.name):
            self.map_world_combo.addItem(world.name, userData=world)

        self.on_map_world_combo(0)
        self.map_world_combo.currentIndexChanged.connect(
            self.on_map_world_combo)
        self.map_area_combo.currentIndexChanged.connect(self.on_map_area_combo)
        self.map_canvas.set_edit_mode(False)
        self.map_canvas.SelectAreaRequest.connect(self.focus_on_area)

        # Graph Map
        from randovania.gui.widgets.tracker_map import MatplotlibWidget
        self.matplot_widget = MatplotlibWidget(
            self.tab_graph_map, self.game_description.world_list)
        self.tab_graph_map_layout.addWidget(self.matplot_widget)
        self.map_tab_widget.currentChanged.connect(self._on_tab_changed)

        for world in self.game_description.world_list.worlds:
            self.graph_map_world_combo.addItem(world.name, world)
        self.graph_map_world_combo.currentIndexChanged.connect(
            self.on_graph_map_world_combo)

        self.persistence_path.mkdir(parents=True, exist_ok=True)
        previous_state = _load_previous_state(self.persistence_path,
                                              self.preset.configuration)

        if not self.apply_previous_state(previous_state):
            self.setup_starting_location(None)

            VersionedPreset.with_preset(self.preset).save_to_file(
                _persisted_preset_path(self.persistence_path))
            self._add_new_action(self._initial_state.node)
Ejemplo n.º 11
0
async def _inner_advance_depth(
    state: State,
    logic: Logic,
    status_update: Callable[[str], None],
    *,
    reach: Optional[ResolverReach] = None,
) -> Tuple[Optional[State], bool]:
    """

    :param state:
    :param logic:
    :param status_update:
    :param reach: A precalculated reach for the given state
    :return:
    """

    if logic.game.victory_condition.satisfied(state.resources, state.energy):
        return state, True

    # Yield back to the asyncio runner, so cancel can do something
    await asyncio.sleep(0)

    if reach is None:
        reach = ResolverReach.calculate_reach(logic, state)

    debug.log_new_advance(state, reach)
    status_update("Resolving... {} total resources".format(len(
        state.resources)))

    for action, energy in reach.possible_actions(state):
        if _should_check_if_action_is_safe(state, action,
                                           logic.game.dangerous_resources,
                                           logic.game.world_list.all_nodes):

            potential_state = state.act_on_node(
                action, path=reach.path_to_node[action], new_energy=energy)
            potential_reach = ResolverReach.calculate_reach(
                logic, potential_state)

            # If we can go back to where we were, it's a simple safe node
            if state.node in potential_reach.nodes:
                new_result = await _inner_advance_depth(
                    state=potential_state,
                    logic=logic,
                    status_update=status_update,
                    reach=potential_reach,
                )

                if not new_result[1]:
                    debug.log_rollback(state, True, True)

                # If a safe node was a dead end, we're certainly a dead end as well
                return new_result

    debug.log_checking_satisfiable_actions()
    has_action = False
    for action, energy in reach.satisfiable_actions(
            state, logic.game.victory_condition):
        new_result = await _inner_advance_depth(
            state=state.act_on_node(action,
                                    path=reach.path_to_node[action],
                                    new_energy=energy),
            logic=logic,
            status_update=status_update,
        )

        # We got a positive result. Send it back up
        if new_result[0] is not None:
            return new_result
        else:
            has_action = True

    debug.log_rollback(state, has_action, False)
    additional_requirements = reach.satisfiable_as_requirement_set

    if has_action:
        additional = set()
        for resource_node in reach.collectable_resource_nodes(state):
            additional |= logic.get_additional_requirements(
                resource_node).alternatives

        additional_requirements = additional_requirements.union(
            RequirementSet(additional))

    logic.additional_requirements[
        state.node] = _simplify_additional_requirement_set(
            additional_requirements, state, logic.game.dangerous_resources)
    return None, has_action
Ejemplo n.º 12
0
    def __init__(self, persistence_path: Path,
                 layout_configuration: LayoutConfiguration):
        super().__init__()
        self.setupUi(self)
        set_default_window_icon(self)

        self.menu_reset_action = QAction("Reset", self)
        self.menu_reset_action.triggered.connect(self._confirm_reset)
        self.menu_bar.addAction(self.menu_reset_action)

        self._collected_pickups = {}
        self._widget_for_pickup = {}
        self._actions = []
        self._asset_id_to_item = {}
        self._node_to_item = {}
        self.layout_configuration = layout_configuration
        self.persistence_path = persistence_path
        self.game_description = data_reader.decode_data(
            layout_configuration.game_data)

        try:
            base_patches = base_patches_factory.create_base_patches(
                self.layout_configuration, None, self.game_description)
        except base_patches_factory.MissingRng as e:
            raise InvalidLayoutForTracker(
                "Layout is configured to have random {}, "
                "but that isn't supported by the tracker.".format(e))

        pool_patches, item_pool = pool_creator.calculate_item_pool(
            self.layout_configuration, self.game_description.resource_database,
            base_patches)

        self.game_description, self._initial_state = logic_bootstrap(
            layout_configuration, self.game_description, pool_patches)
        self.logic = Logic(self.game_description, layout_configuration)

        self._initial_state.resources[
            "add_self_as_requirement_to_resources"] = 1

        self.resource_filter_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.hide_collected_resources_check.stateChanged.connect(
            self.update_locations_tree_for_reachable_nodes)
        self.undo_last_action_button.clicked.connect(self._undo_last_action)

        self.configuration_label.setText(
            "Trick Level: {}; Elevators: Vanilla; Starts with:\n{}".format(
                layout_configuration.trick_level_configuration.global_level.
                value,
                ", ".join(resource.short_name
                          for resource in pool_patches.starting_items.keys())))

        self.setup_pickups_box(item_pool)
        self.setup_possible_locations_tree()

        self._starting_nodes = {
            node
            for node in self.game_description.world_list.all_nodes
            if node.is_resource_node
            and node.resource() in self._initial_state.resources
        }

        persistence_path.mkdir(parents=True, exist_ok=True)

        previous_state = _load_previous_state(persistence_path,
                                              layout_configuration)
        if previous_state is not None:
            pickup_name_to_pickup = {
                pickup.name: pickup
                for pickup in self._collected_pickups.keys()
            }
            self.bulk_change_quantity({
                pickup_name_to_pickup[pickup_name]: quantity
                for pickup_name, quantity in
                previous_state["collected_pickups"].items()
            })
            self._add_new_actions([
                self.game_description.world_list.all_nodes[index]
                for index in previous_state["actions"]
            ])
        else:
            with persistence_path.joinpath("layout_configuration.json").open(
                    "w") as layout_file:
                json.dump(layout_configuration.as_json, layout_file)
            self._add_new_action(self._initial_state.node)