def __init__(self, layout_configuration: LayoutConfiguration): super().__init__() self.setupUi(self) set_default_window_icon(self) self.layout_configuration = layout_configuration self.game_description = data_reader.decode_data( layout_configuration.game_data, True) self.logic, self._initial_state = logic_bootstrap( layout_configuration, self.game_description, GamePatches.with_game(self.game_description)) 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; Item Loss: {}".format( layout_configuration.trick_level.value, layout_configuration.starting_resources.configuration.value, )) self.setup_pickups_box() 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 } self._add_new_action(self._initial_state.node)
def test_reach_size_from_start(echoes_game_description, default_layout_configuration, minimal_logic, nodes, safe_nodes): # Setup specific_levels = { trick.short_name: LayoutTrickLevel.HYPERMODE for trick in echoes_game_description.resource_database.trick } layout_configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( minimal_logic=minimal_logic, specific_levels=specific_levels if not minimal_logic else {}), ) player_pool = generator.create_player_pool(Random(15000), layout_configuration, 0) game, state = logic_bootstrap(layout_configuration, player_pool.game, player_pool.patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) >= nodes assert len(list(reach.safe_nodes)) >= safe_nodes
def run_filler( configuration: LayoutConfiguration, game: GameDescription, item_pool: List[PickupEntry], patches: GamePatches, rng: Random, status_update: Callable[[str], None], ) -> Tuple[GamePatches, List[PickupEntry]]: """ Runs the filler logic for the given configuration and item pool. Returns a GamePatches with progression items and hints assigned, along with all items in the pool that weren't assigned. :param configuration: :param game: :param item_pool: :param patches: :param rng: :param status_update: :return: """ major_items, expansions = _split_expansions(item_pool) rng.shuffle(major_items) rng.shuffle(expansions) major_configuration = configuration.major_items_configuration new_game, state = bootstrap.logic_bootstrap(configuration, game, patches) new_game.patch_requirements(state.resources, configuration.damage_strictness.value) filler_patches = retcon_playthrough_filler( new_game, state, major_items, rng, configuration=FillerConfiguration( randomization_mode=configuration.available_locations. randomization_mode, minimum_random_starting_items=major_configuration. minimum_random_starting_items, maximum_random_starting_items=major_configuration. maximum_random_starting_items, indices_to_exclude=configuration.available_locations. excluded_indices, ), status_update=status_update) # Since we haven't added expansions yet, these hints will always be for items added by the filler. full_hints_patches = fill_unassigned_hints(filler_patches, game.world_list, rng) if configuration.hints.item_hints: result = add_hints_precision(full_hints_patches, rng) else: result = replace_hints_without_precision_with_jokes(full_hints_patches) return result, major_items + expansions
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, game: GameDescription, patches: GamePatches, status_update: Optional[Callable[[str], None]] = None) -> Optional[State]: if status_update is None: status_update = lambda s: None logic, starting_state = logic_bootstrap(configuration, game, patches) debug.log_resolve_start() return advance_depth(starting_state, logic, status_update)
def run_bootstrap(preset: Preset): game = data_reader.decode_data(preset.configuration.game_data) permalink = Permalink( seed_number=15000, spoiler=True, presets={0: preset}, ) patches = base_patches_factory.create_base_patches(preset.configuration, Random(15000), game, False) _, state = logic_bootstrap(preset.configuration, game, patches) return game, state, permalink
def run_bootstrap(preset: Preset): game = default_database.game_description_for(preset.game) permalink = Permalink( seed_number=15000, spoiler=True, presets={0: preset}, ) patches = base_patches_factory.create_base_patches(preset.configuration, Random(15000), game, False, player_index=0) _, state = logic_bootstrap(preset.configuration, game, patches) return game, state, permalink
def _test_data(default_preset): data = default_data.decode_default_prime2() game = data_reader.decode_data(data) permalink = Permalink( seed_number=15000, spoiler=True, presets={0: default_preset}, ) configuration = permalink.get_preset(0).layout_configuration patches = game.create_game_patches() patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, game.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, game, patches) return game, state, permalink
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 test_retcon_filler_integration(): layout_configuration = LayoutConfiguration.default() rng = Random("fixed-seed!") status_update = MagicMock() game = data_reader.decode_data(layout_configuration.game_data) patches = GamePatches.with_game(game) available_pickups = game.pickup_database.all_useful_pickups logic, state = logic_bootstrap(layout_configuration, game, patches) logic.game.simplify_connections(state.resources) filler_patches = retcon.retcon_playthrough_filler(logic, state, tuple(available_pickups), rng, status_update) assert filler_patches == patches
def _test_data(): data = default_data.decode_default_prime2() game = data_reader.decode_data(data) configuration = LayoutConfiguration.from_params() permalink = Permalink( seed_number=15000, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ) patches = GamePatches.with_game(game) patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, game.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, game, patches) return game, state, permalink
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)
def test_reach_size_from_start(echoes_game_description, default_layout_configuration): # Setup layout_configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) player_pool = generator.create_player_pool(Random(15000), layout_configuration, 0) game, state = logic_bootstrap(layout_configuration, player_pool.game, player_pool.patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 44 assert len(list(reach.safe_nodes)) == 5
def test_reach_size_from_start(echoes_game_description): # Setup configuration = LayoutConfiguration.from_params( trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) patches = GamePatches.with_game(echoes_game_description) patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, echoes_game_description.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, echoes_game_description, patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 26 assert len(list(reach.safe_nodes)) == 4
def _test_data(): data = default_data.decode_default_prime2() game = data_reader.decode_data(data, False) configuration = LayoutConfiguration.from_params( trick_level=LayoutTrickLevel.NO_TRICKS, sky_temple_keys=LayoutSkyTempleKeyMode.FULLY_RANDOM, elevators=LayoutRandomizedFlag.VANILLA, pickup_quantities={}, starting_location=StartingLocation.default(), starting_resources=StartingResources.default(), ) permalink = Permalink( seed_number=15000, spoiler=True, patcher_configuration=PatcherConfiguration.default(), layout_configuration=configuration, ) logic, state = logic_bootstrap(configuration, game, GamePatches.with_game(game)) return logic, state, permalink
def _create_patches( permalink: Permalink, game: GameDescription, status_update: Callable[[str], None], ) -> GamePatches: rng = Random(permalink.as_str) configuration = permalink.layout_configuration categories = { "translator", "major", "energy_tank", "sky_temple_key", "temple_key" } item_pool = tuple(sorted(calculate_item_pool(permalink, game))) available_pickups = list( shuffle(rng, calculate_available_pickups(item_pool, categories, None))) if configuration.starting_location.configuration != StartingLocationConfiguration.SHIP: raise GenerationFailure("The only supported StartingLocation is SHIP", permalink) if configuration.starting_resources.configuration == StartingResourcesConfiguration.CUSTOM: raise GenerationFailure("Custom StartingResources is unsupported", permalink) patches = GamePatches.with_game(game) patches = _add_elevator_connections_to_patches(permalink, patches) patches = _sky_temple_key_distribution_logic(permalink, patches, available_pickups) logic, state = logic_bootstrap(configuration, game, patches) logic.game.simplify_connections(state.resources) filler_patches = retcon_playthrough_filler(logic, state, tuple(available_pickups), rng, status_update) return filler_patches.assign_new_pickups( _indices_for_unassigned_pickups(rng, game, filler_patches.pickup_assignment, item_pool))
def test_reach_size_from_start(echoes_game_description, default_layout_configuration): # Setup configuration = dataclasses.replace( default_layout_configuration, trick_level_configuration=TrickLevelConfiguration( LayoutTrickLevel.HYPERMODE), ) patches = echoes_game_description.create_game_patches() patches = patches.assign_gate_assignment( base_patches_factory.gate_assignment_for_configuration( configuration, echoes_game_description.resource_database, Random(15000))) game, state = logic_bootstrap(configuration, echoes_game_description, patches) # Run reach = GeneratorReach.reach_from_state(game, state) # Assert assert len(list(reach.nodes)) == 25 assert len(list(reach.safe_nodes)) == 4
def test_retcon_filler_integration(default_layout_configuration): layout_configuration = default_layout_configuration rng = Random("fixed-seed!") status_update = MagicMock() game = default_database.game_description_for(layout_configuration.game) patches = game.create_game_patches() available_pickups = game.pickup_database.all_useful_pickups new_game, state = logic_bootstrap(layout_configuration, game, patches) new_game.patch_requirements(state.resources, layout_configuration.damage_strictness.value) filler_patches = retcon.retcon_playthrough_filler( new_game, state, tuple(available_pickups), rng, FillerConfiguration( randomization_mode=RandomizationMode.FULL, minimum_random_starting_items=0, maximum_random_starting_items=0, indices_to_exclude=frozenset(), ), status_update) assert filler_patches == patches
def run_filler(rng: Random, player_pools: Dict[int, PlayerPool], status_update: Callable[[str], None], ) -> FillerResults: """ Runs the filler logic for the given configuration and item pool. Returns a GamePatches with progression items and hints assigned, along with all items in the pool that weren't assigned. :param player_pools: :param rng: :param status_update: :return: """ player_states = [] player_expansions: Dict[int, List[PickupEntry]] = {} for index, pool in player_pools.items(): status_update(f"Creating state for player {index + 1}") major_items, player_expansions[index] = _split_expansions(pool.pickups) rng.shuffle(major_items) rng.shuffle(player_expansions[index]) new_game, state = bootstrap.logic_bootstrap(pool.configuration, pool.game, pool.patches) new_game.patch_requirements(state.resources, pool.configuration.damage_strictness.value) major_configuration = pool.configuration.major_items_configuration player_states.append(PlayerState( index=index, game=new_game, initial_state=state, pickups_left=major_items, configuration=FillerConfiguration( randomization_mode=pool.configuration.available_locations.randomization_mode, minimum_random_starting_items=major_configuration.minimum_random_starting_items, maximum_random_starting_items=major_configuration.maximum_random_starting_items, indices_to_exclude=pool.configuration.available_locations.excluded_indices, ), )) try: filler_result, actions_log = retcon_playthrough_filler(rng, player_states, status_update=status_update) except UnableToGenerate as e: message = "{}\n\n{}".format( str(e), "\n\n".join( "#### Player {}\n{}".format(player.index + 1, player.current_state_report()) for player in player_states ), ) debug.debug_print(message) raise UnableToGenerate(message) from e results = {} for player_state, patches in filler_result.items(): game = player_state.game if game.game == RandovaniaGame.PRIME2: # Since we haven't added expansions yet, these hints will always be for items added by the filler. full_hints_patches = fill_unassigned_hints(patches, game.world_list, rng, player_state.scan_asset_initial_pickups) if player_pools[player_state.index].configuration.hints.item_hints: result = add_hints_precision(player_state, full_hints_patches, rng) else: result = replace_hints_without_precision_with_jokes(full_hints_patches) else: result = patches results[player_state.index] = FillerPlayerResult( game=game, patches=result, unassigned_pickups=player_state.pickups_left + player_expansions[player_state.index], ) return FillerResults(results, actions_log)
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)
def run_filler( rng: Random, player_pools: Dict[int, PlayerPool], status_update: Callable[[str], None], ) -> FillerResults: """ Runs the filler logic for the given configuration and item pool. Returns a GamePatches with progression items and hints assigned, along with all items in the pool that weren't assigned. :param player_pools: :param rng: :param status_update: :return: """ player_states = {} player_expansions = {} for index, pool in player_pools.items(): major_items, player_expansions[index] = _split_expansions(pool.pickups) rng.shuffle(major_items) rng.shuffle(player_expansions[index]) new_game, state = bootstrap.logic_bootstrap(pool.configuration, pool.game, pool.patches) new_game.patch_requirements(state.resources, pool.configuration.damage_strictness.value) major_configuration = pool.configuration.major_items_configuration player_states[index] = PlayerState( game=new_game, initial_state=state, pickups_left=major_items, configuration=FillerConfiguration( randomization_mode=pool.configuration.available_locations. randomization_mode, minimum_random_starting_items=major_configuration. minimum_random_starting_items, maximum_random_starting_items=major_configuration. maximum_random_starting_items, indices_to_exclude=pool.configuration.available_locations. excluded_indices, ), ) filler_result, actions_log = retcon_playthrough_filler( rng, player_states, status_update=status_update) results = {} for index, patches in filler_result.items(): game = player_pools[index].game # Since we haven't added expansions yet, these hints will always be for items added by the filler. full_hints_patches = fill_unassigned_hints(patches, game.world_list, rng) if player_pools[index].configuration.hints.item_hints: result = add_hints_precision(full_hints_patches, rng) else: result = replace_hints_without_precision_with_jokes( full_hints_patches) results[index] = FillerPlayerResult( game=game, patches=result, unassigned_pickups=player_states[index].pickups_left + player_expansions[index], ) return FillerResults(results, actions_log)
def test_logic_bootstrap(default_preset, echoes_game_description): new_game, state = bootstrap.logic_bootstrap(default_preset.configuration, echoes_game_description, echoes_game_description.create_game_patches())