Ejemplo n.º 1
0
def fill_songs(window, worlds, locations, songpool, itempool, attempts=15):
    # get the song locations for each world

    # look for preplaced items
    placed_prizes = [loc.item.name for loc in locations if loc.item is not None]
    unplaced_prizes = [song for song in songpool if song.name not in placed_prizes]
    empty_song_locations = [loc for loc in locations if loc.item is None]

    # List of states with all items
    all_state_base_list = CollectionState.get_states_with_items([world.state for world in worlds], itempool)

    while attempts:
        attempts -= 1
        try:
            prizepool = list(unplaced_prizes)
            prize_locs = list(empty_song_locations)
            random.shuffle(prizepool)
            random.shuffle(prize_locs)
            fill_restrictive(window, worlds, all_state_base_list, prize_locs, prizepool)
            logging.getLogger('').info("Songs placed")
        except FillError as e:
            logging.getLogger('').info("Failed to place songs. Will retry %s more times", attempts)
            for location in empty_song_locations:
                location.item = None
            logging.getLogger('').info('\t%s' % str(e))
            continue
        break
    else:
        raise FillError('Unable to place songs')
Ejemplo n.º 2
0
def fill_shops(window, worlds, locations, shoppool, itempool, attempts=15):
    # List of states with all items
    all_state_base_list = CollectionState.get_states_with_items(
        [world.state for world in worlds], itempool)

    while attempts:
        attempts -= 1
        try:
            prizepool = list(shoppool)
            prize_locs = list(locations)
            random.shuffle(prizepool)
            random.shuffle(prize_locs)
            fill_restrictive(window, worlds, all_state_base_list, prize_locs,
                             prizepool)
            logging.getLogger('').info("Shop items placed")
        except FillError as e:
            logging.getLogger('').info(
                "Failed to place shop items. Will retry %s more times",
                attempts)
            for location in locations:
                location.item = None
            logging.getLogger('').info('\t%s' % str(e))
            continue
        break
    else:
        raise FillError('Unable to place shops')
Ejemplo n.º 3
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)        
Ejemplo n.º 4
0
def fill_dungeon_unique_item(window, worlds, fill_locations, itempool):
    # We should make sure that we don't count event items, shop items,
    # token items, or dungeon items as a major item. itempool at this
    # point should only be able to have tokens of those restrictions
    # since the rest are already placed.
    major_items = [item for item in itempool if item.majoritem]
    minor_items = [item for item in itempool if not item.majoritem]

    dungeons = [dungeon for world in worlds for dungeon in world.dungeons]
    double_dungeons = []
    for dungeon in dungeons:
        # we will count spirit temple twice so that it gets 2 items to match vanilla
        if dungeon.name == 'Spirit Temple':
            double_dungeons.append(dungeon)
    dungeons.extend(double_dungeons)

    random.shuffle(dungeons)
    random.shuffle(itempool)

    all_other_item_state = CollectionState.get_states_with_items([world.state for world in worlds], minor_items)
    all_dungeon_locations = []

    # iterate of all the dungeons in a random order, placing the item there
    for dungeon in dungeons:
        dungeon_locations = [location for region in dungeon.regions for location in region.locations if location in fill_locations]
        if dungeon.name == 'Spirit Temple':
            # spirit temple is weird and includes a couple locations outside of the dungeon
            dungeon_locations.extend(filter(lambda location: location in fill_locations, [dungeon.world.get_location(location) for location in ['Mirror Shield Chest', 'Silver Gauntlets Chest']]))

        # cache this list to flag afterwards
        all_dungeon_locations.extend(dungeon_locations)

        # place 1 item into the dungeon
        random.shuffle(dungeon_locations)
        fill_restrictive(window, worlds, all_other_item_state, dungeon_locations, major_items, 1)

        # update the location and item pool, removing any placed items and filled locations
        # the fact that you can remove items from a list you're iterating over is python magic
        for item in itempool:
            if item.location != None:
                fill_locations.remove(item.location)
                itempool.remove(item)

    # flag locations to not place further major items. it's important we do it on the 
    # locations instead of the dungeon because some locations are not in the dungeon
    for location in all_dungeon_locations:
        location.minor_only = True

    logging.getLogger('').info("Unique dungeon items placed")
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
def fill_dungeons_restrictive(window, worlds, shuffled_locations, dungeon_items, itempool):
    # List of states with all non-key items
    all_state_base_list = CollectionState.get_states_with_items([world.state for world in worlds], itempool)

    # shuffle this list to avoid placement bias
    random.shuffle(dungeon_items)

    # sort in the order Boss Key, Small Key, Other before placing dungeon items
    # python sort is stable, so the ordering is still random within groups
    sort_order = {"BossKey": 3, "SmallKey": 2}
    dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))

    # place dungeon items
    fill_restrictive(window, worlds, all_state_base_list, shuffled_locations, dungeon_items)

    for world in worlds:
        world.state.clear_cached_unreachable()
Ejemplo n.º 7
0
def fill_dungeon_unique_item(window,
                             worlds,
                             fill_locations,
                             itempool,
                             attempts=15):
    # We should make sure that we don't count event items, shop items,
    # token items, or dungeon items as a major item. itempool at this
    # point should only be able to have tokens of those restrictions
    # since the rest are already placed.
    major_items = [item for item in itempool if item.type != 'Token']
    token_items = [item for item in itempool if item.type == 'Token']

    while attempts:
        attempts -= 1
        try:
            # choose a random set of items and locations
            dungeon_locations = []
            for dungeon in [
                    dungeon for world in worlds for dungeon in world.dungeons
            ]:
                dungeon_locations.append(
                    random.choice([
                        location for region in dungeon.regions
                        for location in region.locations
                        if location in fill_locations
                    ]))
            dungeon_items = random.sample(major_items, len(dungeon_locations))

            new_dungeon_locations = list(dungeon_locations)
            new_dungeon_items = list(dungeon_items)
            non_dungeon_items = [
                item for item in major_items if item not in dungeon_items
            ]
            all_other_item_state = CollectionState.get_states_with_items(
                [world.state for world in worlds],
                token_items + non_dungeon_items)

            # attempt to place the items into the locations
            random.shuffle(new_dungeon_locations)
            random.shuffle(new_dungeon_items)
            fill_restrictive(window, worlds, all_other_item_state,
                             new_dungeon_locations, new_dungeon_items)
            if len(new_dungeon_locations) > 0:
                raise FillError('Not all items were placed successfully')

            logging.getLogger('').info("Unique dungeon items placed")

            # remove the placed items from the fill_location and itempool
            for location in dungeon_locations:
                fill_locations.remove(location)
            for item in dungeon_items:
                itempool.remove(item)

        except FillError as e:
            logging.getLogger('').info(
                "Failed to place unique dungeon items. Will retry %s more times",
                attempts)
            for location in dungeon_locations:
                location.item = None
            for dungeon in [
                    dungeon for world in worlds for dungeon in world.dungeons
            ]:
                dungeon.major_items = 0
            logging.getLogger('').info('\t%s' % str(e))
            continue
        break
    else:
        raise FillError('Unable to place unique dungeon items')