예제 #1
0
def generate_description(
    permalink: Permalink,
    status_update: Optional[Callable[[str], None]],
    validate_after_generation: bool,
    timeout: Optional[int] = 600,
    attempts: int = 15,
) -> LayoutDescription:
    """
    Creates a LayoutDescription for the given Permalink.
    :param permalink:
    :param status_update:
    :param validate_after_generation:
    :param timeout: Abort generation after this many seconds.
    :param attempts: Attempt this many generations.
    :return:
    """
    if status_update is None:
        status_update = id

    try:
        result = _async_create_description(
            permalink=permalink,
            status_update=status_update,
            attempts=attempts,
        )
    except UnableToGenerate as e:
        raise GenerationFailure(
            "Could not generate a game with the given settings",
            permalink=permalink,
            source=e) from e

    if validate_after_generation and permalink.player_count == 1:
        with multiprocessing.dummy.Pool(1) as dummy_pool:
            resolve_params = {
                "configuration": permalink.presets[0].configuration,
                "patches": result.all_patches[0],
                "status_update": status_update,
            }
            final_state_async = dummy_pool.apply_async(func=resolver.resolve,
                                                       kwds=resolve_params)

            try:
                final_state_by_resolve = final_state_async.get(timeout)
            except multiprocessing.TimeoutError as e:
                raise GenerationFailure(
                    "Timeout reached when validating possibility",
                    permalink=permalink,
                    source=e) from e

            if final_state_by_resolve is None:
                # Why is final_state_by_distribution not OK?
                raise GenerationFailure(
                    "Generated game was considered impossible by the solver",
                    permalink=permalink,
                    source=ImpossibleForSolver())

    return result
예제 #2
0
async def generate_and_validate_description(
    generator_params: GeneratorParameters,
    status_update: Optional[Callable[[str], None]],
    validate_after_generation: bool,
    timeout: Optional[int] = 600,
    attempts: int = 15,
) -> LayoutDescription:
    """
    Creates a LayoutDescription for the given Permalink.
    :param generator_params:
    :param status_update:
    :param validate_after_generation:
    :param timeout: Abort generation after this many seconds.
    :param attempts: Attempt this many generations.
    :return:
    """
    if status_update is None:
        status_update = id

    try:
        result = await _create_description(
            generator_params=generator_params,
            status_update=status_update,
            attempts=attempts,
        )
    except UnableToGenerate as e:
        raise GenerationFailure(
            "Could not generate a game with the given settings",
            generator_params=generator_params,
            source=e) from e

    if validate_after_generation and generator_params.player_count == 1:
        final_state_async = resolver.resolve(
            configuration=generator_params.get_preset(0).configuration,
            patches=result.all_patches[0],
            status_update=status_update,
        )
        try:
            final_state_by_resolve = await asyncio.wait_for(
                final_state_async, timeout)
        except asyncio.TimeoutError as e:
            raise GenerationFailure(
                "Timeout reached when validating possibility",
                generator_params=generator_params,
                source=e) from e

        if final_state_by_resolve is None:
            raise GenerationFailure(
                "Generated game was considered impossible by the solver",
                generator_params=generator_params,
                source=ImpossibleForSolver())

    return result
예제 #3
0
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_resources.configuration == StartingResourcesConfiguration.CUSTOM:
        raise GenerationFailure("Custom StartingResources is unsupported",
                                permalink)

    patches = _create_base_patches(rng, game, permalink, 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))
예제 #4
0
def _sky_temple_key_distribution_logic(
    permalink: Permalink,
    previous_patches: GamePatches,
    available_pickups: List[PickupEntry],
) -> GamePatches:

    mode = permalink.layout_configuration.sky_temple_keys
    new_assignments = {}

    if mode == LayoutSkyTempleKeyMode.VANILLA:
        locations_to_place = _FLYING_ING_CACHES[:]

    elif mode == LayoutSkyTempleKeyMode.ALL_BOSSES or mode == LayoutSkyTempleKeyMode.ALL_GUARDIANS:
        locations_to_place = _GUARDIAN_INDICES[:]
        if mode == LayoutSkyTempleKeyMode.ALL_BOSSES:
            locations_to_place += _SUB_GUARDIAN_INDICES

    elif mode == LayoutSkyTempleKeyMode.FULLY_RANDOM:
        locations_to_place = []

    else:
        raise GenerationFailure("Unknown Sky Temple Key mode: {}".format(mode),
                                permalink)

    for pickup in available_pickups[:]:
        if not locations_to_place:
            break

        if pickup.item_category == "sky_temple_key":
            available_pickups.remove(pickup)
            index = locations_to_place.pop(0)
            if index in previous_patches.pickup_assignment:
                raise GenerationFailure(
                    "Attempted to place '{}' in {}, but there's already '{}' there"
                    .format(pickup, index,
                            previous_patches.pickup_assignment[index]),
                    permalink)
            new_assignments[index] = pickup

    if locations_to_place:
        raise GenerationFailure(
            "Missing Sky Temple Keys in available_pickups to place in all requested boss places",
            permalink)

    return previous_patches.assign_pickup_assignment(new_assignments)
예제 #5
0
def calculate_item_pool(
    permalink: Permalink,
    game: GameDescription,
) -> List[PickupEntry]:

    item_pool: List[PickupEntry] = []
    pickup_quantities = permalink.layout_configuration.pickup_quantities

    try:
        pickup_quantities.validate_total_quantities()
    except ValueError as e:
        raise GenerationFailure(
            "Invalid configuration: {}".format(e),
            permalink=permalink,
        )

    quantities_pickups = set(pickup_quantities.pickups())
    database_pickups = set(game.pickup_database.all_useful_pickups)

    if quantities_pickups != database_pickups:
        raise GenerationFailure(
            "Diverging pickups in configuration.\nPickups in quantities: {}\nPickups in database: {}"
            .format(
                [pickup.name for pickup in quantities_pickups],
                [pickup.name for pickup in database_pickups],
            ),
            permalink=permalink,
        )

    for pickup, quantity in pickup_quantities.items():
        item_pool.extend([pickup] * quantity)

    quantity_delta = len(item_pool) - game.pickup_database.total_pickup_count
    if quantity_delta > 0:
        raise GenerationFailure(
            "Invalid configuration: requested {} more items than available slots ({})."
            .format(quantity_delta, game.pickup_database.total_pickup_count),
            permalink=permalink,
        )

    elif quantity_delta < 0:
        item_pool.extend([game.pickup_database.useless_pickup] *
                         -quantity_delta)

    return item_pool
예제 #6
0
def test_sky_temple_key_distribution_logic_vanilla_missing_pickup(dataclass_test_lib, empty_patches):
    # Setup
    permalink = dataclass_test_lib.mock_dataclass(Permalink)
    permalink.layout_configuration.sky_temple_keys = LayoutSkyTempleKeyMode.VANILLA
    available_pickups = []

    # Run
    with pytest.raises(GenerationFailure) as exp:
        generator._sky_temple_key_distribution_logic(permalink, empty_patches, available_pickups)

    assert exp.value == GenerationFailure(
        "Missing Sky Temple Keys in available_pickups to place in all requested boss places", permalink)
예제 #7
0
def test_sky_temple_key_distribution_logic_vanilla_used_location(dataclass_test_lib,
                                                                 sky_temple_keys,
                                                                 empty_patches):
    # Setup
    permalink = dataclass_test_lib.mock_dataclass(Permalink)
    permalink.layout_configuration.sky_temple_keys = LayoutSkyTempleKeyMode.VANILLA
    initial_pickup_assignment = {
        generator._FLYING_ING_CACHES[0]: PickupEntry("Other Item", tuple(), "other", 0)
    }
    patches = empty_patches.assign_new_pickups(initial_pickup_assignment.items())

    # Run
    with pytest.raises(GenerationFailure) as exp:
        generator._sky_temple_key_distribution_logic(permalink, patches, [sky_temple_keys[0]])

    assert exp.value == GenerationFailure(
        "Attempted to place '{}' in PickupIndex 45, but there's already 'Pickup Other Item' there".format(
            sky_temple_keys[0]
        ), permalink)
예제 #8
0
 def create_failure(message: str):
     return GenerationFailure(message, permalink=permalink)