def from_json_dict(cls, json_dict: dict) -> "LayoutDescription": version = json_dict["info"]["version"] version_as_obj = StrictVersion(version) if version_as_obj < StrictVersion("0.21.0"): raise RuntimeError("Unsupported log file version '{}'.".format(version)) # TODO: add try/catch to throw convert potential errors in "seed from future version broke" permalink = Permalink.from_json_dict(json_dict["info"]["permalink"]) if not permalink.spoiler: raise ValueError("Unable to read details of seed log with spoiler disabled") game = data_reader.decode_data(permalink.layout_configuration.game_data) patches = GamePatches( _item_locations_to_pickup_assignment(game, json_dict["locations"]), _node_mapping_to_elevator_connection(game.world_list, json_dict["elevators"]), {}, {}, (), game.starting_location ) return LayoutDescription( version=version, permalink=permalink, patches=patches, solver_path=_playthrough_list_to_solver_path(json_dict["playthrough"]), )
def create_game_patches(self) -> GamePatches: elevator_connection = { node.teleporter_instance_id: node.default_connection for node in self.world_list.all_nodes if isinstance(node, TeleporterNode) and node.editable } return GamePatches({}, elevator_connection, {}, {}, {}, {}, self.starting_location, {})
def test_cs_item_pool_creator(default_cs_configuration: CSConfiguration, puppies, starting_area): game_description = default_database.game_description_for( default_cs_configuration.game) default_cs_configuration = dataclasses.replace(default_cs_configuration, puppies_anywhere=puppies) tricks = default_cs_configuration.trick_level.set_level_for_trick( game_description.resource_database.get_by_type_and_index( ResourceType.TRICK, "SNBubbler"), LayoutTrickLevel.HYPERMODE) tricks = tricks.set_level_for_trick( game_description.resource_database.get_by_type_and_index( ResourceType.TRICK, "SNMissiles"), LayoutTrickLevel.HYPERMODE) default_cs_configuration = dataclasses.replace(default_cs_configuration, trick_level=tricks) base_patches = GamePatches(0, default_cs_configuration, {}, {}, {}, {}, {}, {}, starting_area, {}) rng = Random() result = pool_creator.calculate_pool_results( default_cs_configuration, game_description.resource_database, base_patches, rng) # Puppies expected_puppies = {"Hajime", "Nene", "Mick", "Shinobu", "Kakeru"} names = {pickup.name for pickup in result.assignment.values()} assert puppies != names.issuperset(expected_puppies) # First Cave Weapon first_cave_assignment = [ pickup for index, pickup in result.assignment.items() if index in FIRST_CAVE_INDICES ] expected_first_cave_len = 1 if starting_area.area_name == "Start Point" else 0 assert len(first_cave_assignment) == expected_first_cave_len assert starting_area.area_name != "Start Point" or first_cave_assignment[ 0].broad_category.name in {"weapon", "missile_related"} # Camp weapon/life capsule camp_assignment = [ pickup for index, pickup in result.assignment.items() if index in CAMP_INDICES ] if starting_area.area_name != "Camp": assert len(camp_assignment) == 0 else: expected_names = set(STRONG_WEAPONS) expected_names.add("5HP Life Capsule") for pickup in camp_assignment: assert pickup.name in expected_names
def _add_elevator_connections_to_patches(permalink: Permalink, patches: GamePatches) -> GamePatches: assert patches.elevator_connection == {} if permalink.layout_configuration.elevators == LayoutRandomizedFlag.RANDOMIZED: return GamePatches( patches.pickup_assignment, claris_randomizer.elevator_connections_for_seed_number( permalink.seed_number), patches.dock_connection, patches.dock_weakness, patches.extra_initial_items, patches.starting_location, ) else: return patches
def test_round_trip_default(permalink: Permalink, item_locations: Dict[str, Dict[str, str]], solver_path: Tuple[SolverPath, ...]): game = data_reader.decode_data(permalink.layout_configuration.game_data) original = LayoutDescription( version=randovania.VERSION, permalink=permalink, patches=GamePatches( _item_locations_to_pickup_assignment(game, item_locations), claris_randomizer.elevator_connections_for_seed_number( permalink.seed_number), {}, {}, (), game.starting_location), solver_path=solver_path, ) # Run decoded = LayoutDescription.from_json_dict(original.as_json) # Assert assert decoded == original
def create_base_patches(self, configuration: BaseConfiguration, rng: Random, game: GameDescription, is_multiworld: bool, player_index: int, rng_required: bool = True ) -> GamePatches: """ """ patches = GamePatches(player_index, configuration, {}, game.get_default_elevator_connection(), {}, {}, {}, {}, game.starting_location, {}) # Elevators try: patches = self.add_elevator_connections_to_patches(configuration, rng, patches) except MissingRng as e: if rng_required: raise e # Configurable Nodes try: patches = patches.assign_node_configuration( self.configurable_node_assignment(configuration, game, rng) ) except MissingRng as e: if rng_required: raise e # Starting Location try: patches = patches.assign_starting_location( self.starting_location_for_configuration(configuration, game, rng)) except MissingRng as e: if rng_required: raise e return patches
def empty_patches() -> GamePatches: return GamePatches(0, {}, {}, {}, {}, {}, {}, None, {}, None)
def decode(game_modifications: dict, configuration: LayoutConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param game_modifications: :param configuration: :return: """ game = data_reader.decode_data(configuration.game_data) world_list = game.world_list # Starting Location starting_location = _area_name_to_area_location( world_list, game_modifications["starting_location"]) # Initial items starting_items = { find_resource_info_with_long_name(game.resource_database.item, resource_name): quantity for resource_name, quantity in game_modifications["starting_items"].items() } # Elevators elevator_connection = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].items() } # Pickups pickup_assignment = {} decoder = BitPackDecoder( base64.b64decode( game_modifications["_locations_internal"].encode("utf-8"), validate=True)) for world_name, world_data in game_modifications["locations"].items(): for area_node_name, pickup_name in world_data.items(): if pickup_name == "Nothing": continue node = world_list.node_from_name(f"{world_name}/{area_node_name}") assert isinstance(node, PickupNode) pickup = BitPackPickupEntry.bit_pack_unpack( decoder, pickup_name, game.resource_database) pickup_assignment[node.pickup_index] = pickup # Hints hints = {} for asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, )
def decode_single(player_index: int, num_players: int, game_modifications: dict, configuration: LayoutConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param game_modifications: :param player_index: :param num_players: :param configuration: :return: """ game = data_reader.decode_data(configuration.game_data) game_specific = dataclasses.replace( game.game_specific, energy_per_tank=configuration.energy_per_tank, beam_configurations=configuration.beam_configuration.create_game_specific(game.resource_database)) world_list = game.world_list # Starting Location starting_location = _area_name_to_area_location(world_list, game_modifications["starting_location"]) # Initial items starting_items = { find_resource_info_with_long_name(game.resource_database.item, resource_name): quantity for resource_name, quantity in game_modifications["starting_items"].items() } # Elevators elevator_connection = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].items() } # Pickups target_name_re = re.compile(r"(.*) for Player \d+") index_to_pickup_name = {} for world_name, world_data in game_modifications["locations"].items(): for area_node_name, target_name in world_data.items(): if target_name == _ETM_NAME: continue pickup_name_match = target_name_re.match(target_name) if pickup_name_match is not None: pickup_name = pickup_name_match.group(1) else: pickup_name = target_name node = world_list.node_from_name(f"{world_name}/{area_node_name}") assert isinstance(node, PickupNode) index_to_pickup_name[node.pickup_index] = pickup_name decoder = BitPackDecoder(base64.b64decode(game_modifications["_locations_internal"].encode("utf-8"), validate=True)) pickup_assignment = dict(BitPackPickupEntryList.bit_pack_unpack(decoder, { "index_mapping": index_to_pickup_name, "num_players": num_players, "database": game.resource_database, }).value) # Hints hints = {} for asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( player_index=player_index, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, game_specific=game_specific, )
def empty_patches() -> GamePatches: return GamePatches({}, {}, {}, {}, (), None)
def decode_single(player_index: int, all_pools: Dict[int, PoolResults], game: GameDescription, game_modifications: dict, configuration: LayoutConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param player_index: :param all_pools: :param game: :param game_modifications: :param configuration: :return: """ game_specific = base_patches_factory.create_game_specific( configuration, game) world_list = game.world_list initial_pickup_assignment = all_pools[player_index].assignment # Starting Location starting_location = _area_name_to_area_location( world_list, game_modifications["starting_location"]) # Initial items starting_items = { find_resource_info_with_long_name(game.resource_database.item, resource_name): quantity for resource_name, quantity in game_modifications["starting_items"].items() } # Elevators elevator_connection = {} for source_name, target_name in game_modifications["elevators"].items(): source_area = _area_name_to_area_location(world_list, source_name) target_area = _area_name_to_area_location(world_list, target_name) potential_source_nodes = [ node for node in world_list.area_by_area_location(source_area).nodes if isinstance(node, TeleporterNode) ] assert len(potential_source_nodes) == 1 source_node = potential_source_nodes[0] elevator_connection[source_node.teleporter_instance_id] = target_area # Translator Gates translator_gates = { _find_gate_with_name(gate_name): find_resource_info_with_long_name(game.resource_database.item, resource_name) for gate_name, resource_name in game_modifications["translators"].items() } # Pickups target_name_re = re.compile(r"(.*) for Player (\d+)") pickup_assignment: PickupAssignment = {} for world_name, world_data in game_modifications["locations"].items(): for area_node_name, target_name in world_data.items(): if target_name == _ETM_NAME: continue pickup_name_match = target_name_re.match(target_name) if pickup_name_match is not None: pickup_name = pickup_name_match.group(1) target_player = int(pickup_name_match.group(2)) else: pickup_name = target_name target_player = 0 node = world_list.node_from_name(f"{world_name}/{area_node_name}") assert isinstance(node, PickupNode) if node.pickup_index in initial_pickup_assignment: pickup = initial_pickup_assignment[node.pickup_index] if (pickup_name, target_player) != (pickup.name, player_index): raise ValueError( f"{area_node_name} should be vanilla based on configuration" ) elif pickup_name != _ETM_NAME: configuration_item_pool = all_pools[target_player].pickups pickup = _find_pickup_with_name(configuration_item_pool, pickup_name) configuration_item_pool.remove(pickup) else: pickup = None if pickup is not None: pickup_assignment[node.pickup_index] = PickupTarget( pickup, target_player) # Hints hints = {} for asset_id, hint in game_modifications["hints"].items(): hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint) return GamePatches( player_index=player_index, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # Dict[int, AreaLocation] dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness={}, # Dict[Tuple[int, int], DockWeakness] translator_gates=translator_gates, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaLocation hints=hints, game_specific=game_specific, )
def find_invalid_strongly_connected_components( game: GameDescription) -> Iterator[str]: import networkx graph = networkx.DiGraph() for node in game.world_list.all_nodes: if isinstance(node, DockLockNode): continue graph.add_node(node) context = NodeContext( patches=GamePatches( player_index=0, configuration=None, pickup_assignment={}, elevator_connection={}, dock_connection={}, dock_weakness={}, configurable_nodes={}, starting_items={}, starting_location=game.starting_location, hints={}, ), current_resources={}, database=game.resource_database, node_provider=game.world_list, ) for node in game.world_list.all_nodes: if node not in graph: continue for other, req in game.world_list.potential_nodes_from(node, context): if other not in graph: continue if req != Requirement.impossible(): graph.add_edge(node, other) starting_node = game.world_list.resolve_teleporter_connection( game.starting_location) for strong_comp in networkx.strongly_connected_components(graph): components: set[Node] = strong_comp # The starting location determines the default component if starting_node in components: continue if any( node.extra.get("different_strongly_connected_component", False) for node in components): continue if len(components) == 1: node = next(iter(components)) # If the component is a single node which is the default node of it's area, allow it area = game.world_list.nodes_to_area(node) if area.default_node == node.name: continue # We accept nodes that have no paths out or in. if not graph.in_edges(node) and not graph.edges(node): continue names = sorted( game.world_list.node_name(node, with_world=True) for node in strong_comp) yield "Unknown strongly connected component detected containing {} nodes:\n{}".format( len(names), names)
def empty_patches(preset_manager) -> GamePatches: configuration = preset_manager.default_preset_for_game(RandovaniaGame.BLANK).get_preset().configuration return GamePatches(0, configuration, {}, {}, {}, {}, {}, {}, None, {})
def decode_single(player_index: int, all_pools: Dict[int, PoolResults], game: GameDescription, game_modifications: dict, configuration: BaseConfiguration) -> GamePatches: """ Decodes a dict created by `serialize` back into a GamePatches. :param player_index: :param all_pools: :param game: :param game_modifications: :param configuration: :return: """ world_list = game.world_list weakness_db = game.dock_weakness_database if game_modifications["game"] != game.game.value: raise ValueError( f"Expected '{game.game.value}', got '{game_modifications['game']}'" ) initial_pickup_assignment = all_pools[player_index].assignment # Starting Location starting_location = AreaIdentifier.from_string( game_modifications["starting_location"]) # Initial items starting_items = { find_resource_info_with_long_name(game.resource_database.item, resource_name): quantity for resource_name, quantity in game_modifications["starting_items"].items() } # Elevators elevator_connection: ElevatorConnection = { NodeIdentifier.from_string(source_name): AreaIdentifier.from_string(target_name) for source_name, target_name in game_modifications["teleporters"].items() } # Dock Weakness dock_weakness: dict[NodeIdentifier, DockWeakness] = { NodeIdentifier.from_string(source_name): weakness_db.get_by_weakness( weakness_data["type"], weakness_data["name"], ) for source_name, weakness_data in game_modifications["dock_weakness"].items() } # Configurable Nodes configurable_nodes = { NodeIdentifier.from_string(identifier): data_reader.read_requirement(requirement, game.resource_database) for identifier, requirement in game_modifications["configurable_nodes"].items() } # Pickups target_name_re = re.compile(r"(.*) for Player (\d+)") pickup_assignment: PickupAssignment = {} for world_name, world_data in game_modifications["locations"].items(): for area_node_name, target_name in typing.cast(dict[str, str], world_data).items(): if target_name == _ETM_NAME: continue pickup_name_match = target_name_re.match(target_name) if pickup_name_match is not None: pickup_name = pickup_name_match.group(1) target_player = int(pickup_name_match.group(2)) - 1 else: pickup_name = target_name target_player = 0 node_identifier = NodeIdentifier.create( world_name, *area_node_name.split("/", 1)) node = world_list.node_by_identifier(node_identifier) assert isinstance(node, PickupNode) if node.pickup_index in initial_pickup_assignment: pickup = initial_pickup_assignment[node.pickup_index] if (pickup_name, target_player) != (pickup.name, player_index): raise ValueError( f"{area_node_name} should be vanilla based on configuration" ) elif pickup_name != _ETM_NAME: configuration_item_pool = all_pools[target_player].pickups pickup = _find_pickup_with_name(configuration_item_pool, pickup_name) configuration_item_pool.remove(pickup) else: pickup = None if pickup is not None: pickup_assignment[node.pickup_index] = PickupTarget( pickup, target_player) # Hints hints = {} for identifier_str, hint in game_modifications["hints"].items(): hints[NodeIdentifier.from_string(identifier_str)] = Hint.from_json( hint) return GamePatches( player_index=player_index, configuration=configuration, pickup_assignment=pickup_assignment, # PickupAssignment elevator_connection=elevator_connection, # ElevatorConnection dock_connection={}, # Dict[Tuple[int, int], DockConnection] dock_weakness=dock_weakness, configurable_nodes=configurable_nodes, starting_items=starting_items, # ResourceGainTuple starting_location=starting_location, # AreaIdentifier hints=hints, )