示例#1
0
    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"]),
        )
示例#2
0
    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, {})
示例#3
0
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
示例#4
0
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
示例#6
0
    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
示例#7
0
def empty_patches() -> GamePatches:
    return GamePatches(0, {}, {}, {}, {}, {}, {}, None, {}, None)
示例#8
0
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,
    )
示例#9
0
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,
    )
示例#10
0
def empty_patches() -> GamePatches:
    return GamePatches({}, {}, {}, {}, (), None)
示例#11
0
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,
    )
示例#12
0
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)
示例#13
0
def empty_patches(preset_manager) -> GamePatches:
    configuration = preset_manager.default_preset_for_game(RandovaniaGame.BLANK).get_preset().configuration
    return GamePatches(0, configuration, {}, {}, {}, {}, {}, {}, None, {})
示例#14
0
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,
    )