예제 #1
0
def fill_restrictive(worlds, base_state_list, locations, itempool):
    # loop until there are no items or locations
    while itempool and locations:
        # get and item and remove it from the itempool
        item_to_place = itempool.pop()

        # generate the max states that include every remaining item
        # this will allow us to place this item in a reachable location
        maximum_exploration_state_list = CollectionState.get_states_with_items(
            base_state_list, itempool)

        # perform_access_check checks location reachability
        perform_access_check = True
        if worlds[0].check_beatable_only:
            # if any world can not longer be beatable with the remaining items
            # then we must check for reachability no matter what.
            # This way the reachability test is monotonic. If we were to later
            # stop checking, then we could place an item needed in one world
            # in an unreachable place in another world
            perform_access_check = not CollectionState.can_beat_game(
                maximum_exploration_state_list)

        # find a location that the item can be places. It must be a valid location
        # in the world we are placing it (possibly checking for reachability)
        spot_to_fill = None
        for location in locations:
            if location.can_fill(
                    maximum_exploration_state_list[location.world.id],
                    item_to_place, perform_access_check):
                spot_to_fill = location
                break

        # if we failed to find a suitable location, then stop placing items
        if spot_to_fill is None:
            # Maybe the game can be beaten anyway?
            if not CollectionState.can_beat_game(
                    maximum_exploration_state_list):
                raise FillError(
                    'Game unbeatable: No more spots to place %s [World %d]' %
                    (item_to_place, item_to_place.world.id))

            if not worlds[0].check_beatable_only:
                logging.getLogger('').warning(
                    'Not all items placed. Game beatable anyway.')
            break

        # Place the item in the world and continue
        spot_to_fill.world.push_item(spot_to_fill, item_to_place)
        locations.remove(spot_to_fill)
예제 #2
0
def fill_restrictive(window, worlds, base_state_list, locations, itempool, count=-1):
    unplaced_items = []

    # loop until there are no items or locations
    while itempool and locations:
        # if remaining count is 0, return. Negative means unbounded.
        if count == 0:
            break

        # get and item and remove it from the itempool
        item_to_place = itempool.pop()

        # generate the max states that include every remaining item
        # this will allow us to place this item in a reachable location
        maximum_exploration_state_list = CollectionState.get_states_with_items(base_state_list, itempool + unplaced_items)     

        # perform_access_check checks location reachability
        perform_access_check = True
        if worlds[0].check_beatable_only:
            # if any world can not longer be beatable with the remaining items
            # then we must check for reachability no matter what.
            # This way the reachability test is monotonic. If we were to later
            # stop checking, then we could place an item needed in one world
            # in an unreachable place in another world
            perform_access_check = not CollectionState.can_beat_game(maximum_exploration_state_list)

        # find a location that the item can be places. It must be a valid location
        # in the world we are placing it (possibly checking for reachability)
        spot_to_fill = None
        for location in locations:
            if location.can_fill(maximum_exploration_state_list[location.world.id], item_to_place, perform_access_check):
                spot_to_fill = location
                break

        # if we failed to find a suitable location
        if spot_to_fill is None:
            # if we specify a count, then we only want to place a subset, so a miss might be ok
            if count > 0:
                # don't decrement count, we didn't place anything
                unplaced_items.append(item_to_place)
                continue                
            else:
                # we expect all items to be placed
                raise FillError('Game unbeatable: No more spots to place %s [World %d]' % (item_to_place, item_to_place.world.id))
            
        # Place the item in the world and continue
        spot_to_fill.world.push_item(spot_to_fill, item_to_place)
        locations.remove(spot_to_fill)
        window.fillcount += 1
        window.update_progress(5 + ((window.fillcount / window.locationcount) * 30))

        # decrement count
        count -= 1

    # assert that the specified number of items were placed
    if count > 0:
        raise FillError('Could not place the specified number of item. %d remaining to be placed.' % count)
    # re-add unplaced items that were skipped
    itempool.extend(unplaced_items)        
예제 #3
0
def create_playthrough(worlds):
    if worlds[0].check_beatable_only and not CollectionState.can_beat_game(
        [world.state for world in worlds]):
        raise RuntimeError('Uncopied is broken too.')
    # create a copy as we will modify it
    old_worlds = worlds
    worlds = [world.copy() for world in worlds]

    # if we only check for beatable, we can do this sanity check first before writing down spheres
    if worlds[0].check_beatable_only and not CollectionState.can_beat_game(
        [world.state for world in worlds]):
        raise RuntimeError(
            'Cannot beat game. Something went terribly wrong here!')

    state_list = [CollectionState(world) for world in worlds]

    # Get all item locations in the worlds
    collection_spheres = []
    item_locations = [
        location for state in state_list
        for location in state.world.get_filled_locations()
        if location.item.advancement
    ]

    # in the first phase, we create the generous spheres. Collecting every item in a sphere will
    # mean that every item in the next sphere is collectable. Will contain every reachable item
    logging.getLogger('').debug('Building up collection spheres.')

    # will loop if there is more items opened up in the previous iteration. Always run once
    reachable_items_locations = True
    while reachable_items_locations:
        # get reachable new items locations
        reachable_items_locations = [
            location for location in item_locations
            if location.name not in state_list[
                location.world.id].collected_locations
            and state_list[location.world.id].can_reach(location)
        ]
        for location in reachable_items_locations:
            # Mark the location collected in the state world it exists in
            state_list[location.world.id].collected_locations.append(
                location.name)
            # Collect the item for the state world it is for
            state_list[location.item.world.id].collect(location.item)
        if reachable_items_locations:
            collection_spheres.append(reachable_items_locations)

    # in the second phase, we cull each sphere such that the game is still beatable, reducing each
    # range of influence to the bare minimum required inside it. Effectively creates a min play
    for num, sphere in reversed(list(enumerate(collection_spheres))):
        to_delete = []
        for location in sphere:
            # we remove the item at location and check if game is still beatable
            logging.getLogger('').debug(
                'Checking if %s is required to beat the game.',
                location.item.name)
            old_item = location.item
            old_state_list = [state.copy() for state in state_list]

            location.item = None
            state_list[old_item.world.id].remove(old_item)
            CollectionState.remove_locations(state_list)
            if CollectionState.can_beat_game(state_list, False):
                to_delete.append(location)
            else:
                # still required, got to keep it around
                state_list = old_state_list
                location.item = old_item

        # cull entries in spheres for spoiler walkthrough at end
        for location in to_delete:
            sphere.remove(location)
    collection_spheres = [sphere for sphere in collection_spheres if sphere]

    # we can finally output our playthrough
    for world in old_worlds:
        world.spoiler.playthrough = OrderedDict([
            (str(i + 1), {location: location.item
                          for location in sphere})
            for i, sphere in enumerate(collection_spheres)
        ])