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')
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')
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)
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")
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)
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()
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')