Clustered bias algorithm

Fixes for various other algorithms
This commit is contained in:
aerinon
2021-09-14 15:02:18 -06:00
parent ebf237cca3
commit 391db7b5c4
4 changed files with 344 additions and 191 deletions

View File

@@ -44,6 +44,7 @@ def link_doors(world, player):
reset_rooms(world, player) reset_rooms(world, player)
world.get_door("Skull Pinball WS", player).no_exit() world.get_door("Skull Pinball WS", player).no_exit()
world.swamp_patch_required[player] = orig_swamp_patch world.swamp_patch_required[player] = orig_swamp_patch
link_doors_prep(world, player)
def link_doors_prep(world, player): def link_doors_prep(world, player):

131
Fill.py
View File

@@ -6,7 +6,7 @@ import logging
from BaseClasses import CollectionState, FillError from BaseClasses import CollectionState, FillError
from Items import ItemFactory from Items import ItemFactory
from Regions import shop_to_location_table, retro_shops from Regions import shop_to_location_table, retro_shops
from source.item.BiasedFill import filter_locations, classify_major_items, split_pool from source.item.BiasedFill import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
def get_dungeon_item_pool(world): def get_dungeon_item_pool(world):
@@ -107,18 +107,17 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No
if spot_to_fill: if spot_to_fill:
break break
if spot_to_fill is None: if spot_to_fill is None:
# we filled all reachable spots. Maybe the game can be beaten anyway? spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state,
unplaced_items.insert(0, item_to_place) base_state, itempool, perform_access_check, item_locations,
if world.can_beat_game(): keys_in_itempool, single_player_placement)
if world.accessibility[item_to_place.player] != 'none':
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
f' (Could not place {item_to_place})')
continue
if world.algorithm in ['balanced', 'equitable']:
spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state,
base_state, itempool, keys_in_itempool,
single_player_placement)
if spot_to_fill is None: if spot_to_fill is None:
# we filled all reachable spots. Maybe the game can be beaten anyway?
unplaced_items.insert(0, item_to_place)
if world.can_beat_game():
if world.accessibility[item_to_place.player] != 'none':
logging.getLogger('').warning('Not all items placed. Game beatable anyway.'
f' (Could not place {item_to_place})')
continue
raise FillError('No more spots to place %s' % item_to_place) raise FillError('No more spots to place %s' % item_to_place)
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)
@@ -214,6 +213,55 @@ def is_dungeon_item(item, world):
or (item.map and not world.mapshuffle[item.player])) or (item.map and not world.mapshuffle[item.player]))
def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted,
keys_in_itempool=None, single_player_placement=False):
if world.algorithm in ['balanced', 'equitable']:
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool,
single_player_placement)
elif world.algorithm == 'vanilla_bias':
if item_to_place.type == 'Crystal':
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
keys_in_itempool, single_player_placement)
else:
i, config = 0, world.item_pool_config
tried = set(attempted)
if not item_to_place.is_inside_dungeon_item(world):
while i < len(config.location_groups[item_to_place.player]):
fallback_locations = config.location_groups[item_to_place.player][i].locations
other_locs = [x for x in locations if x.name in fallback_locations]
for location in other_locs:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
if spot_to_fill:
return spot_to_fill
i += 1
tried.update(other_locs)
else:
other_locations = vanilla_fallback(item_to_place, locations, world)
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
if spot_to_fill:
return spot_to_fill
tried.update(other_locations)
other_locations = [x for x in locations if x not in tried]
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
if spot_to_fill:
return spot_to_fill
return None
else:
other_locations = [x for x in locations if x not in attempted]
for location in other_locations:
spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement,
perform_access_check, itempool, keys_in_itempool, world)
if spot_to_fill:
return spot_to_fill
return None
def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool,
keys_in_itempool=None, single_player_placement=False): keys_in_itempool=None, single_player_placement=False):
def location_preference(loc): def location_preference(loc):
@@ -232,7 +280,12 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
possible_swaps = [x for x in state.locations_checked possible_swaps = [x for x in state.locations_checked
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] if x.item.type not in ['Event', 'Crystal'] and not x.forced_item]
swap_locations = sorted(possible_swaps, key=location_preference) swap_locations = sorted(possible_swaps, key=location_preference)
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
keys_in_itempool, single_player_placement)
def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
keys_in_itempool=None, single_player_placement=False):
for location in swap_locations: for location in swap_locations:
old_item = location.item old_item = location.item
new_pool = list(itempool) + [old_item] new_pool = list(itempool) + [old_item]
@@ -301,6 +354,9 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
prioitempool = [item for item in world.itempool if not item.advancement and item.priority] prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
restitempool = [item for item in world.itempool if not item.advancement and not item.priority] restitempool = [item for item in world.itempool if not item.advancement and not item.priority]
gftower_trash &= world.algorithm in ['balanced', 'equitable', 'dungeon_bias']
# dungeon bias may fill up the dungeon... and push items out into the overworld
# fill in gtower locations with trash first # fill in gtower locations with trash first
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']: if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
@@ -325,40 +381,36 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# todo: crossed # todo: crossed
progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0)
keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)} keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)}
if world.algorithm in ['balanced', 'equitable', 'vanilla_bias', 'dungeon_bias', 'entangled']:
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool) # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia
progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1)
fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool)
random.shuffle(fill_locations)
if world.algorithm == 'balanced':
fast_fill(world, prioitempool, fill_locations)
elif world.algorithm == 'vanilla_bias':
fast_vanilla_fill(world, prioitempool, fill_locations)
elif world.algorithm in ['major_bias', 'dungeon_bias', 'cluster_bias', 'entangled']:
filtered_fill(world, prioitempool, fill_locations)
else: # just need to ensure dungeon items still get placed in dungeons
fast_equitable_fill(world, prioitempool, fill_locations)
# placeholder work
if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias':
random.shuffle(fill_locations) random.shuffle(fill_locations)
if world.algorithm == 'balanced': placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)']
fast_fill(world, prioitempool, fill_locations) num_ph_items = len(placeholder_items)
elif world.algorithm == 'vanilla_bias': if num_ph_items > 0:
fast_vanilla_fill(world, prioitempool, fill_locations)
elif world.algorithm in ['dungeon_bias', 'entangled']:
filtered_fill(world, prioitempool, fill_locations)
else: # just need to ensure dungeon items still get placed in dungeons
fast_equitable_fill(world, prioitempool, fill_locations)
# placeholder work
if world.algorithm == 'entangled' and world.players > 1:
random.shuffle(fill_locations)
placeholder_locations = filter_locations('Placeholder', fill_locations, world) placeholder_locations = filter_locations('Placeholder', fill_locations, world)
placeholder_items = [item for item in world.itempool if item.name == 'Rupee (1)'] num_ph_locations = len(placeholder_locations)
if num_ph_items < num_ph_locations < len(fill_locations):
for _ in range(num_ph_locations - num_ph_items):
placeholder_items.append(replace_trash_item(restitempool, 'Rupee (1)'))
assert len(placeholder_items) == len(placeholder_locations)
for i in placeholder_items: for i in placeholder_items:
restitempool.remove(i) restitempool.remove(i)
for l in placeholder_locations: for l in placeholder_locations:
fill_locations.remove(l) fill_locations.remove(l)
filtered_fill(world, placeholder_items, placeholder_locations) filtered_fill(world, placeholder_items, placeholder_locations)
else:
primary, secondary = split_pool(progitempool, world)
fill_restrictive(world, world.state, fill_locations, primary, keys_in_pool, False, secondary)
random.shuffle(fill_locations)
tertiary, quaternary = split_pool(prioitempool, world)
prioitempool = []
filtered_equitable_fill(world, tertiary, fill_locations)
prioitempool += tertiary
random.shuffle(fill_locations)
fill_restrictive(world, world.state, fill_locations, secondary, keys_in_pool)
random.shuffle(fill_locations)
fast_equitable_fill(world, quaternary, fill_locations)
prioitempool += quaternary
if world.algorithm == 'vanilla_bias': if world.algorithm == 'vanilla_bias':
fast_vanilla_fill(world, restitempool, fill_locations) fast_vanilla_fill(world, restitempool, fill_locations)
@@ -389,6 +441,7 @@ def filtered_fill(world, item_pool, fill_locations):
# sweep once to pick up preplaced items # sweep once to pick up preplaced items
world.state.sweep_for_events() world.state.sweep_for_events()
def fast_vanilla_fill(world, item_pool, fill_locations): def fast_vanilla_fill(world, item_pool, fill_locations):
while item_pool and fill_locations: while item_pool and fill_locations:
item_to_place = item_pool.pop() item_to_place = item_pool.pop()

View File

@@ -207,12 +207,10 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli","cli","placing.dungeon.items")) logger.info(world.fish.translate("cli","cli","placing.dungeon.items"))
if args.algorithm in ['balanced', 'dungeon_bias', 'entangled']: if args.algorithm != 'equitable':
shuffled_locations = world.get_unfilled_locations() shuffled_locations = world.get_unfilled_locations()
random.shuffle(shuffled_locations) random.shuffle(shuffled_locations)
fill_dungeons_restrictive(world, shuffled_locations) fill_dungeons_restrictive(world, shuffled_locations)
elif args.algorithm == 'equitable':
promote_dungeon_items(world)
else: else:
promote_dungeon_items(world) promote_dungeon_items(world)

View File

@@ -1,5 +1,6 @@
import RaceRandom as random import RaceRandom as random
import logging import logging
from math import ceil
from collections import defaultdict from collections import defaultdict
from DoorShuffle import validate_vanilla_reservation from DoorShuffle import validate_vanilla_reservation
@@ -29,7 +30,7 @@ class LocationGroup(object):
self.retro = False self.retro = False
def locs(self, locs): def locs(self, locs):
self.locations = locs self.locations = list(locs)
return self return self
def flags(self, k, d=False, s=False, r=False): def flags(self, k, d=False, s=False, r=False):
@@ -52,9 +53,10 @@ def create_item_pool_config(world):
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
config.reserved_locations[player].add(f'{d_name} - Boss') config.reserved_locations[player].add(f'{d_name} - Boss')
for dungeon in world.dungeons: for dungeon in world.dungeons:
for item in dungeon.all_items: if world.restrict_boss_items[dungeon.player] != 'none':
if item.map or item.compass: for item in dungeon.all_items:
item.advancement = True if item.map or item.compass:
item.advancement = True
if world.algorithm == 'vanilla_bias': if world.algorithm == 'vanilla_bias':
config.static_placement = {} config.static_placement = {}
config.location_groups = {} config.location_groups = {}
@@ -70,9 +72,12 @@ def create_item_pool_config(world):
# todo: retro (universal keys...) # todo: retro (universal keys...)
# retro + shops # retro + shops
config.location_groups[player] = [ config.location_groups[player] = [
LocationGroup('Major').locs(mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']),
LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']), LocationGroup('bkhp').locs(mode_grouping['Heart Pieces']),
LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']), LocationGroup('bktrash').locs(mode_grouping['Overworld Trash'] + mode_grouping['Dungeon Trash']),
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])] LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
config.reserved_locations[player].add(loc_name)
elif world.algorithm == 'major_bias': elif world.algorithm == 'major_bias':
config.location_groups = [ config.location_groups = [
LocationGroup('MajorItems'), LocationGroup('MajorItems'),
@@ -102,6 +107,7 @@ def create_item_pool_config(world):
pass # todo: 5 locations for single arrow representation? pass # todo: 5 locations for single arrow representation?
config.item_pool[player] = determine_major_items(world, player) config.item_pool[player] = determine_major_items(world, player)
config.location_groups[0].locations = set(groups.locations) config.location_groups[0].locations = set(groups.locations)
config.reserved_locations[player].add(groups.locations)
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops'] backup = (mode_grouping['Heart Pieces'] + mode_grouping['Dungeon Trash'] + mode_grouping['Shops']
+ mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops']) + mode_grouping['Overworld Trash'] + mode_grouping['GT Trash'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup) config.location_groups[1].locations = set(backup)
@@ -121,6 +127,36 @@ def create_item_pool_config(world):
backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major'] backup = (mode_grouping['Heart Pieces'] + mode_grouping['Overworld Major']
+ mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops']) + mode_grouping['Overworld Trash'] + mode_grouping['Shops'] + mode_grouping['RetroShops'])
config.location_groups[1].locations = set(backup) config.location_groups[1].locations = set(backup)
elif world.algorithm == 'cluster_bias':
config.location_groups = [
LocationGroup('Clusters'),
]
item_cnt = defaultdict(int)
config.item_pool = {}
for player in range(1, world.players + 1):
config.item_pool[player] = determine_major_items(world, player)
item_cnt[player] += count_major_items(world, player)
# set cluster choices
cluster_choices = figure_out_clustered_choices(world)
chosen_locations = defaultdict(set)
placeholder_cnt = 0
for player in range(1, world.players + 1):
number_of_clusters = ceil(item_cnt[player] / 13)
location_cnt = 0
while item_cnt[player] > location_cnt:
chosen_clusters = random.sample(cluster_choices[player], number_of_clusters)
for loc_group in chosen_clusters:
for location in loc_group.locations:
if not location_prefilled(location, world, player):
world.item_pool_config.reserved_locations[player].add(location)
chosen_locations[location].add(player)
location_cnt += 1
cluster_choices[player] = [x for x in cluster_choices[player] if x not in chosen_clusters]
number_of_clusters = 1
placeholder_cnt += location_cnt - item_cnt[player]
config.placeholders = placeholder_cnt
config.location_groups[0].locations = chosen_locations
elif world.algorithm == 'entangled' and world.players > 1: elif world.algorithm == 'entangled' and world.players > 1:
config.location_groups = [ config.location_groups = [
LocationGroup('Entangled'), LocationGroup('Entangled'),
@@ -169,6 +205,14 @@ def create_item_pool_config(world):
config.location_groups[0].locations = chosen_locations config.location_groups[0].locations = chosen_locations
def location_prefilled(location, world, player):
if world.swords[player] == 'vanilla':
return location in vanilla_swords
if world.goal[player] == 'pedestal':
return location == 'Master Sword Pedestal'
return False
def previously_reserved(location, world, player): def previously_reserved(location, world, player):
if '- Boss' in location.name: if '- Boss' in location.name:
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player] if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
@@ -188,7 +232,7 @@ def massage_item_pool(world):
player_pool[item.player].append(item) player_pool[item.player].append(item)
for dungeon in world.dungeons: for dungeon in world.dungeons:
for item in dungeon.all_items: for item in dungeon.all_items:
if item not in player_pool[item.player]: # filters out maps, compasses, etc if (not item.compass and not item.map) or item not in player_pool[item.player]:
player_pool[item.player].append(item) player_pool[item.player].append(item)
player_locations = defaultdict(list) player_locations = defaultdict(list)
for player in player_pool: for player in player_pool:
@@ -220,6 +264,9 @@ def massage_item_pool(world):
deleted = trash_options.pop() deleted = trash_options.pop()
world.itempool.remove(deleted) world.itempool.remove(deleted)
removed += 1 removed += 1
if world.item_pool_config.placeholders > len(single_rupees):
for _ in range(world.item_pool_config.placeholders-len(single_rupees)):
single_rupees.append(ItemFactory('Rupee (1)', random.randint(1, world.players)))
placeholders = random.sample(single_rupees, world.item_pool_config.placeholders) placeholders = random.sample(single_rupees, world.item_pool_config.placeholders)
world.itempool += placeholders world.itempool += placeholders
removed -= len(placeholders) removed -= len(placeholders)
@@ -227,6 +274,19 @@ def massage_item_pool(world):
world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players))) world.itempool.append(ItemFactory('Rupees (5)', random.randint(1, world.players)))
def replace_trash_item(item_pool, replacement):
trash_options = [x for x in item_pool if x.name in trash_items]
random.shuffle(trash_options)
trash_options = sorted(trash_options, key=lambda x: trash_items[x.name], reverse=True)
if len(trash_options) == 0:
logging.getLogger('').warning(f'Too many good items in pool, not enough room for placeholders')
deleted = trash_options.pop()
item_pool.remove(deleted)
replace_item = ItemFactory(replacement, deleted.player)
item_pool.append(replace_item)
return replace_item
def validate_reservation(location, dungeon, world, player): def validate_reservation(location, dungeon, world, player):
world.item_pool_config.reserved_locations[player].add(location.name) world.item_pool_config.reserved_locations[player].add(location.name)
if world.doorShuffle[player] != 'vanilla': if world.doorShuffle[player] != 'vanilla':
@@ -265,12 +325,22 @@ def count_major_items(world, player):
major_item_set += world.triforce_pool[player] major_item_set += world.triforce_pool[player]
if world.bombbag[player]: if world.bombbag[player]:
major_item_set += world.triforce_pool[player] major_item_set += world.triforce_pool[player]
# todo: vanilla, assured, swordless? if world.swords[player] != "random":
# if world.swords[player] != "random": if world.swords[player] == 'assured':
# if world.swords[player] == 'assured': major_item_set -= 1
# major_item_set -= 1 if world.swords[player] in ['vanilla', 'swordless']:
# if world.swords[player] in ['vanilla', 'swordless']: major_item_set -= 4
# major_item_set -= 4 if world.retro[player]:
if world.shopsanity[player]:
major_item_set -= 1 # sword in old man cave
if world.keyshuffle[player]:
major_item_set -= 29
# universal keys
major_item_set += 19 if world.difficulty[player] == 'normal' else 14
if world.mode[player] == 'standard' and world.doorShuffle[player] == 'vanilla':
major_item_set -= 1 # a key in escape
if world.doorShuffle[player] != 'vanilla':
major_item_set += 10 # tries to add up to 10 more universal keys for door rando
# todo: starting equipment? # todo: starting equipment?
return major_item_set return major_item_set
@@ -354,16 +424,68 @@ def classify_major_items(world):
item.priority = True item.priority = True
def split_pool(pool, world): def figure_out_clustered_choices(world):
# bias or entangled cluster_candidates = {}
config = world.item_pool_config for player in range(1, world.players + 1):
priority, secondary = [], [] cluster_candidates[player] = [LocationGroup(x.name).locs(x.locations) for x in clustered_groups]
for item in pool: backups = list(reversed(leftovers))
if item.name in config.item_pool[item.player]: if world.bigkeyshuffle[player]:
priority.append(item) bk_grp = LocationGroup('BigKeys').locs(mode_grouping['Big Keys'])
else: if world.keydropshuffle[player]:
secondary.append(item) bk_grp.locations.append(mode_grouping['Big Key Drops'])
return priority, secondary for i in range(13-len(bk_grp.locations)):
bk_grp.locations.append(backups.pop())
cluster_candidates[player].append(bk_grp)
if world.compassshuffle[player]:
cmp_grp = LocationGroup('Compasses').locs(mode_grouping['Compasses'])
if len(cmp_grp.locations) + len(backups) >= 13:
for i in range(13-len(cmp_grp.locations)):
cmp_grp.locations.append(backups.pop())
cluster_candidates[player].append(cmp_grp)
else:
backups.extend(reversed(cmp_grp.locations))
if world.mapshuffle[player]:
mp_grp = LocationGroup('Maps').locs(mode_grouping['Maps'])
if len(mp_grp.locations) + len(backups) >= 13:
for i in range(13-len(mp_grp.locations)):
mp_grp.locations.append(backups.pop())
cluster_candidates[player].append(mp_grp)
else:
backups.extend(reversed(mp_grp.locations))
if world.shopsanity[player]:
cluster_candidates[player].append(LocationGroup('Shopsanity1').locs(other_clusters['Shopsanity1']))
cluster_candidates[player].append(LocationGroup('Shopsanity2').locs(other_clusters['Shopsanity2']))
extras = list(other_clusters['ShopsanityLeft'])
if world.retro[player]:
extras.extend(mode_grouping['RetroShops'])
if len(extras)+len(backups) >= 13:
for i in range(13-len(extras)):
extras.append(backups.pop())
cluster_candidates[player].append(LocationGroup('ShopExtra').locs(extras))
else:
backups.extend(reversed(extras))
if world.keyshuffle[player] or world.retro[player]:
cluster_candidates[player].append(LocationGroup('SmallKey1').locs(other_clusters['SmallKey1']))
cluster_candidates[player].append(LocationGroup('SmallKey2').locs(other_clusters['SmallKey2']))
extras = list(other_clusters['SmallKeyLeft'])
if world.keydropshuffle[player]:
cluster_candidates[player].append(LocationGroup('KeyDrop1').locs(other_clusters['KeyDrop1']))
cluster_candidates[player].append(LocationGroup('KeyDrop2').locs(other_clusters['KeyDrop2']))
extras.extend(other_clusters['KeyDropLeft'])
if len(extras)+len(backups) >= 13:
for i in range(13-len(extras)):
extras.append(backups.pop())
cluster_candidates[player].append(LocationGroup('SmallKeyExtra').locs(extras))
else:
backups.extend(reversed(extras))
return cluster_candidates
def vanilla_fallback(item_to_place, locations, world):
if item_to_place.is_inside_dungeon_item(world):
return [x for x in locations if x.name in vanilla_fallback_dungeon_set
and x.parent_region.dungeon and x.parent_region.dungeon.name == item_to_place.dungeon]
return []
def filter_locations(item_to_place, locations, world): def filter_locations(item_to_place, locations, world):
@@ -391,7 +513,7 @@ def filter_locations(item_to_place, locations, world):
filtered = [l for l in locations if l.name in restricted] filtered = [l for l in locations if l.name in restricted]
# bias toward certain location in overflow? (thinking about this for major_bias) # bias toward certain location in overflow? (thinking about this for major_bias)
return filtered if len(filtered) > 0 else locations return filtered if len(filtered) > 0 else locations
if world.algorithm == 'entangled' and world.players > 1: if (world.algorithm == 'entangled' and world.players > 1) or world.algorithm == 'cluster_bias':
config = world.item_pool_config config = world.item_pool_config
if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]: if item_to_place == 'Placeholder' or item_to_place.name in config.item_pool[item_to_place.player]:
restricted = config.location_groups[0].locations restricted = config.location_groups[0].locations
@@ -417,7 +539,7 @@ vanilla_mapping = {
'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'], 'Crystal 5': ['Ice Palace - Prize', 'Misery Mire - Prize'],
'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'], 'Crystal 6': ['Ice Palace - Prize', 'Misery Mire - Prize'],
'Bow': ['Eastern Palace - Big Chest'], 'Bow': ['Eastern Palace - Big Chest'],
'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Left'], 'Progressive Bow': ['Eastern Palace - Big Chest', 'Pyramid Fairy - Right'],
'Book of Mudora': ['Library'], 'Book of Mudora': ['Library'],
'Hammer': ['Palace of Darkness - Big Chest'], 'Hammer': ['Palace of Darkness - Big Chest'],
'Hookshot': ['Swamp Palace - Big Chest'], 'Hookshot': ['Swamp Palace - Big Chest'],
@@ -443,10 +565,10 @@ vanilla_mapping = {
'Master Sword': ['Master Sword Pedestal'], 'Master Sword': ['Master Sword Pedestal'],
'Tempered Sword': ['Blacksmith'], 'Tempered Sword': ['Blacksmith'],
'Fighter Sword': ["Link's Uncle"], 'Fighter Sword': ["Link's Uncle"],
'Golden Sword': ['Pyramid Fairy - Right'], 'Golden Sword': ['Pyramid Fairy - Left'],
'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Right'], 'Progressive Sword': ["Link's Uncle", 'Blacksmith', 'Master Sword Pedestal', 'Pyramid Fairy - Left'],
'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"], 'Progressive Glove': ['Desert Palace - Big Chest', "Thieves' Town - Big Chest"],
'Silver Arrows': ['Pyramid Fairy - Left'], 'Silver Arrows': ['Pyramid Fairy - Right'],
'Single Arrow': ['Palace of Darkness - Dark Basement - Left'], 'Single Arrow': ['Palace of Darkness - Dark Basement - Left'],
'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right', 'Arrows (10)': ['Chicken House', 'Mini Moldorm Cave - Far Right', 'Sewers - Secret Room - Right',
'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left', 'Paradox Cave Upper - Right', 'Mire Shed - Right', 'Ganons Tower - Hope Room - Left',
@@ -702,6 +824,11 @@ mode_grouping = {
] ]
} }
vanilla_fallback_dungeon_set = set(mode_grouping['Dungeon Trash'] + mode_grouping['Big Keys'] +
mode_grouping['GT Trash'] + mode_grouping['Small Keys'] +
mode_grouping['Compasses'] + mode_grouping['Maps'] + mode_grouping['Key Drops'] +
mode_grouping['Big Key Drops'])
major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer',
'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel',
@@ -714,148 +841,122 @@ major_items = {'Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod
'Progressive Bow', 'Progressive Bow (Alt)'} 'Progressive Bow', 'Progressive Bow (Alt)'}
# todo: re-enter these
clustered_groups = [ clustered_groups = [
LocationGroup("MajorRoute1").locs([ LocationGroup("MajorRoute1").locs([
'Library', 'Master Sword Pedestal', 'Old Man', 'Flute Spot', 'Ice Rod Cave', 'Library', 'Old Man', 'Magic Bat', 'Ether Tablet', 'Hobo', 'Purple Chest', 'Spike Cave',
'Ether Tablet', 'Stumpy', 'Bombos Tablet', 'Mushroom', 'Bottle Merchant', 'Kakariko Tavern', 'Sahasrahla', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top',
'Sick Kid', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right' 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'
]), ]),
LocationGroup("MajorRoute2").locs([ LocationGroup("MajorRoute2").locs([
'King Zora', 'Sahasrahla', 'Ice Rod Cave', 'Catfish', 'Mushroom', 'Secret Passage', 'Bottle Merchant', 'Flute Spot', 'Catfish', 'Stumpy', 'Waterfall Fairy - Left',
'Purple Chest', 'Waterfall Fairy - Left', 'Waterfall Fairy - Right', 'Blacksmith', 'Waterfall Fairy - Right', 'Master Sword Pedestal', "Thieves' Town - Attic", 'Sewers - Secret Room - Right',
'Magic Bat', 'Hobo', 'Potion Shop', 'Spike Cave', "King's Tomb" 'Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle'
]), ]),
LocationGroup("BigChest").locs([ LocationGroup("MajorRoute3").locs([
'Sanctuary', 'Eastern Palace - Big Chest', 'Kakariko Tavern', 'Sick Kid', 'King Zora', 'Potion Shop', 'Bombos Tablet', "King's Tomb", 'Blacksmith',
'Desert Palace - Big Chest', 'Tower of Hera - Big Chest', 'Palace of Darkness - Big Chest', 'Pyramid Fairy - Left', 'Pyramid Fairy - Right', 'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left',
'Swamp Palace - Big Chest', 'Skull Woods - Big Chest', "Thieves' Town - Big Chest", 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'
'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest', 'Ice Palace - Big Chest',
'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest'
]), ]),
LocationGroup("BossUncle").locs([ LocationGroup("Dungeon Major").locs([
"Link's Uncle", "Link's House", 'Secret Passage', 'Eastern Palace - Boss', 'Eastern Palace - Big Chest', 'Desert Palace - Big Chest', 'Tower of Hera - Big Chest',
'Desert Palace - Boss', 'Tower of Hera - Boss', 'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Palace of Darkness - Big Chest', 'Swamp Palace - Big Chest', 'Skull Woods - Big Chest',
'Skull Woods - Boss', "Thieves' Town - Boss", 'Ice Palace - Boss', 'Misery Mire - Boss', "Thieves' Town - Big Chest", 'Misery Mire - Big Chest', 'Hyrule Castle - Boomerang Chest',
'Turtle Rock - Boss']), 'Ice Palace - Big Chest', 'Turtle Rock - Big Chest', 'Ganons Tower - Big Chest', "Link's Uncle"]),
LocationGroup("HeartPieces LW").locs([ LocationGroup("Dungeon Heart").locs([
'Lost Woods Hideout', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Maze Race', 'Sunken Treasure', 'Sanctuary', 'Eastern Palace - Boss', 'Desert Palace - Boss', 'Tower of Hera - Boss',
'Bonk Rock Cave', 'Desert Ledge', "Aginah's Cave", 'Spectacle Rock Cave', 'Spectacle Rock', 'Pyramid', 'Palace of Darkness - Boss', 'Swamp Palace - Boss', 'Skull Woods - Boss', "Thieves' Town - Boss",
'Lumberjack Tree', "Zora's Ledge"]), 'Ice Palace - Boss', 'Misery Mire - Boss', 'Turtle Rock - Boss', "Link's House",
LocationGroup("HeartPieces DW").locs([ 'Ganons Tower - Validation Chest']),
'Lake Hylia Island', 'Chest Game', 'Digging Game', 'Graveyard Cave', 'Mimic Cave', LocationGroup("HeartPieces1").locs([
'Cave 45', 'Peg Cave', 'Bumper Cave Ledge', 'Checkerboard Cave', 'Mire Shed - Right', 'Floating Island', 'Kakariko Well - Top', 'Lost Woods Hideout', 'Maze Race', 'Lumberjack Tree', 'Bonk Rock Cave', 'Graveyard Cave',
'Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right']), 'Checkerboard Cave', "Zora's Ledge", 'Digging Game', 'Desert Ledge', 'Bumper Cave Ledge', 'Floating Island',
LocationGroup("Minor Trash").locs([ 'Swamp Palace - Waterfall Room']),
'Ice Palace - Freezor Chest', 'Skull Woods - Pot Prison', 'Misery Mire - Bridge Chest', LocationGroup("HeartPieces2").locs([
'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Maze - Top', "Blind's Hideout - Top", 'Sunken Treasure', "Aginah's Cave", 'Mimic Cave', 'Spectacle Rock Cave', 'Cave 45',
'Palace of Darkness - Shooter Room', 'Palace of Darkness - The Arena - Bridge', 'Spectacle Rock', 'Lake Hylia Island', 'Chest Game', 'Mire Shed - Right', 'Pyramid', 'Peg Cave',
'Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right', 'Eastern Palace - Cannonball Chest']),
'Swamp Palace - Waterfall Room', 'Turtle Rock - Eye Bridge - Bottom Right', LocationGroup("BlindHope").locs([
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right']),
LocationGroup("CompassTT").locs([
"Thieves' Town - Ambush Chest", "Thieves' Town - Attic",
'Eastern Palace - Compass Chest', 'Desert Palace - Compass Chest', 'Tower of Hera - Compass Chest',
'Palace of Darkness - Compass Chest', 'Swamp Palace - Compass Chest', 'Skull Woods - Compass Chest',
"Thieves' Town - Compass Chest", 'Ice Palace - Compass Chest', 'Misery Mire - Compass Chest',
'Turtle Rock - Compass Chest', 'Ganons Tower - Compass Room - Top Left']),
LocationGroup("Early SKs").locs([
'Sewers - Dark Cross', 'Desert Palace - Torch', 'Tower of Hera - Basement Cage',
'Palace of Darkness - Stalfos Basement', 'Palace of Darkness - Dark Basement - Right',
'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Harmless Hellway',
"Thieves' Town - Blind's Cell", 'Eastern Palace - Cannonball Chest',
'Sewers - Secret Room - Right', 'Sewers - Secret Room - Left',
'Sewers - Secret Room - Middle', 'Floodgate Chest'
]),
LocationGroup("Late SKs").locs([
'Skull Woods - Bridge Room', 'Ice Palace - Spike Room', "Hyrule Castle - Zelda's Chest",
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Swamp Palace - West Chest',
'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room',
'Turtle Rock - Eye Bridge - Bottom Left', "Ganons Tower - Bob's Torch", 'Ganons Tower - Tile Room',
'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest',
]),
LocationGroup("Kak-LDM").locs([
"Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left", "Blind's Hideout - Left", "Blind's Hideout - Right", "Blind's Hideout - Far Left",
"Blind's Hideout - Far Right", 'Chicken House', 'Paradox Cave Lower - Far Left', "Blind's Hideout - Far Right", 'Floodgate Chest', 'Spiral Cave', 'Palace of Darkness - Dark Maze - Bottom',
'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Palace of Darkness - Dark Maze - Top', 'Swamp Palace - Flooded Room - Left',
'Paradox Cave Lower - Middle', 'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Swamp Palace - Flooded Room - Right', "Thieves' Town - Ambush Chest", 'Ganons Tower - Hope Room - Left',
'Ganons Tower - Hope Room - Right']),
LocationGroup('WellHype').locs([
'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right', 'Kakariko Well - Bottom',
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Hype Cave - Top', 'Hype Cave - Middle Right',
'Hype Cave - Middle Left', 'Hype Cave - Bottom', 'Hype Cave - Generous Guy',
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
]), ]),
LocationGroup("BK-Bunny").locs([ LocationGroup('MiniMoldormLasers').locs([
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy',
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest', 'Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Chicken House', 'Brewery',
'Swamp Palace - Big Key Chest', "Thieves' Town - Big Key Chest", 'Skull Woods - Big Key Chest', 'Palace of Darkness - Dark Basement - Left', 'Ice Palace - Freezor Chest', 'Swamp Palace - West Chest',
'Ice Palace - Big Key Chest', 'Misery Mire - Big Key Chest', 'Turtle Rock - Big Key Chest', 'Turtle Rock - Eye Bridge - Bottom Right', 'Turtle Rock - Eye Bridge - Top Left',
'Superbunny Cave - Top', 'Superbunny Cave - Bottom', 'Turtle Rock - Eye Bridge - Top Right',
]), ]),
LocationGroup("Early Drops").flags(True, True).locs([ LocationGroup('ParadoxCloset').locs([
"Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", "Sahasrahla's Hut - Middle",
'Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right',
'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle', "Hyrule Castle - Zelda's Chest",
'C-Shaped House', 'Mire Shed - Left', 'Ganons Tower - Compass Room - Bottom Right',
'Ganons Tower - Compass Room - Bottom Left',
])
]
other_clusters = {
'SmallKey1': [
'Sewers - Dark Cross', 'Tower of Hera - Basement Cage', 'Palace of Darkness - Shooter Room',
'Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement',
'Palace of Darkness - Dark Basement - Right', "Thieves' Town - Blind's Cell", 'Skull Woods - Bridge Room',
'Ice Palace - Iced T Room', 'Misery Mire - Main Lobby', 'Misery Mire - Bridge Chest',
'Misery Mire - Spike Chest', "Ganons Tower - Bob's Torch"],
'SmallKey2': [
'Desert Palace - Torch', 'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
'Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Harmless Hellway', 'Swamp Palace - Entrance',
'Skull Woods - Pot Prison', 'Skull Woods - Pinball Room', 'Ice Palace - Spike Room',
'Turtle Rock - Roller Room - Right', 'Turtle Rock - Chain Chomps', 'Turtle Rock - Crystaroller Room',
'Turtle Rock - Eye Bridge - Bottom Left'],
'SmallKeyLeft': [
'Ganons Tower - Tile Room', 'Ganons Tower - Firesnake Room', 'Ganons Tower - Pre-Moldorm Chest'],
'KeyDrop1': [
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop', 'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Square Pot Key', 'Hyrule Castle - Key Rat Key Drop', 'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
'Eastern Palace - Dark Eyegore Key Drop', 'Desert Palace - Desert Tiles 1 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop',
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop', 'Misery Mire - Spikes Pot Key',
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
'KeyDrop2': [
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
'Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key',
'Desert Palace - Desert Tiles 2 Pot Key', 'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
"Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key", 'Ice Palace - Hammer Block Key Drop',
'Ice Palace - Many Pots Pot Key', 'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
'KeyDropLeft': [
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
'Thieves\' Town - Hallway Pot Key', 'Thieves\' Town - Spike Switch Pot Key', 'Hyrule Castle - Big Key Drop', 'Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
]), 'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasuar Key Drop'],
LocationGroup("Late Drops").flags(True, True).locs([ 'Shopsanity1': [
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
'Skull Woods - Spike Corner Key Drop', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Ganons Tower - Conveyor Cross Pot Key',
'Ganons Tower - Double Switch Pot Key']),
LocationGroup("SS-Hype-Voo").locs([
'Mini Moldorm Cave - Left',
'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Mini Moldorm Cave - Far Left',
'Mini Moldorm Cave - Far Right', 'Hype Cave - Top', 'Hype Cave - Middle Right',
'Hype Cave - Middle Left', 'Hype Cave - Bottom', 'Hype Cave - Generous Guy', 'Brewery',
'C-Shaped House', 'Palace of Darkness - The Arena - Ledge',
]),
LocationGroup("DDM Hard").flags(True, True).locs([
'Hookshot Cave - Top Right', 'Hookshot Cave - Top Left',
'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left',
'Misery Mire - Spike Chest', 'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
'Turtle Rock - Pokey 2 Key Drop', 'Turtle Rock - Roller Room - Right',
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasaur Key Drop'
]),
LocationGroup("Kak Shop").flags(False, False, True).locs([
'Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right',
'Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right', 'Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right',
'Dark Lake Hylia Shop - Left', 'Dark Lake Hylia Shop - Middle', 'Dark Lake Hylia Shop - Right',
'Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right', 'Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right',
'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right', 'Kakariko Shop - Left', 'Kakariko Shop - Middle', 'Kakariko Shop - Right', 'Capacity Upgrade - Left'],
'Capacity Upgrade - Left']), 'Shopsanity2': [
LocationGroup("Hylia Shop").flags(False, False, True).locs([
'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right', 'Red Shield Shop - Left', 'Red Shield Shop - Middle', 'Red Shield Shop - Right',
'Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right', 'Village of Outcasts Shop - Left', 'Village of Outcasts Shop - Middle', 'Village of Outcasts Shop - Right',
'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right', 'Dark Potion Shop - Left', 'Dark Potion Shop - Middle', 'Dark Potion Shop - Right',
'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Lake Hylia Shop - Left', 'Lake Hylia Shop - Middle', 'Lake Hylia Shop - Right', 'Capacity Upgrade - Right',
'Capacity Upgrade - Right']), ],
LocationGroup("Map Validation").locs([ 'ShopsanityLeft': ['Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right']
'Hyrule Castle - Map Chest', }
'Eastern Palace - Map Chest', 'Desert Palace - Map Chest', 'Tower of Hera - Map Chest',
'Palace of Darkness - Map Chest', 'Swamp Palace - Map Chest', 'Skull Woods - Map Chest',
"Thieves' Town - Map Chest", 'Ice Palace - Map Chest', 'Misery Mire - Map Chest',
'Turtle Rock - Roller Room - Left', 'Ganons Tower - Map Chest', 'Ganons Tower - Validation Chest']),
LocationGroup("SahasWell+MireHopeDDMShop").flags(False, False, True).locs([
'Dark Death Mountain Shop - Left', 'Dark Death Mountain Shop - Middle', 'Dark Death Mountain Shop - Right',
'Kakariko Well - Bottom', 'Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
"Sahasrahla's Hut - Left", "Sahasrahla's Hut - Right", "Sahasrahla's Hut - Middle",
'Mire Shed - Left', 'Ganons Tower - Hope Room - Left', 'Ganons Tower - Hope Room - Right']),
LocationGroup("Tower Pain").flags(True).locs([
'Castle Tower - Room 03', 'Castle Tower - Dark Maze',
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
'Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right',
'Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Room - Left']),
LocationGroup("Retro Shops").flags(False, False, True, True).locs([
'Old Man Sword Cave Item 1', 'Take-Any #1 Item 1', 'Take-Any #1 Item 2',
'Take-Any #2 Item 1', 'Take-Any #2 Item 2', 'Take-Any #3 Item 1', 'Take-Any #3 Item 2',
'Take-Any #4 Item 1', 'Take-Any #4 Item 2', 'Swamp Palace - Entrance',
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Top Right',
'Ganons Tower - Compass Room - Bottom Right',
])
leftovers = [
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Randomizer Room - Top Left',
'Ganons Tower - Randomizer Room - Top Right',"Ganons Tower - Bob's Chest", 'Ganons Tower - Big Key Room - Left',
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Mini Helmasaur Room - Left',
'Ganons Tower - Mini Helmasaur Room - Right',
] ]
vanilla_swords = {"Link's Uncle", 'Master Sword Pedestal', 'Blacksmith', 'Pyramid Fairy - Left'}
trash_items = { trash_items = {
'Nothing': -1, 'Nothing': -1,