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