From d79adbf3c87c66f15345820ab4ceab0b5b2c0539 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Dec 2021 19:59:30 -0600 Subject: [PATCH] Fix deterministic issues in repeat seed generation --- BaseClasses.py | 6 ++++-- DoorShuffle.py | 4 ++-- EntranceShuffle.py | 34 +++++++++++++++++----------------- OverworldShuffle.py | 15 ++++++++------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6fd15ba7..0c2925e5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1658,7 +1658,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[follower_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_exit = None while len(mirror_map): @@ -1710,7 +1711,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[dest_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections.copy()[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]] # loop thru potential places to leave a mirror portal diff --git a/DoorShuffle.py b/DoorShuffle.py index 6e80d19d..7964755e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1820,7 +1820,7 @@ def find_inaccessible_regions(world, player): else: start_regions = ['Links House', 'Dark Sanctuary Hint'] regs = convert_regions(start_regions, world, player) - all_regions = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon]) + all_regions = [r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon] visited_regions = set() queue = deque(regs) while len(queue) > 0: @@ -1836,7 +1836,7 @@ def find_inaccessible_regions(world, player): if connect and connect not in queue and connect not in visited_regions: if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'): queue.append(connect) - world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)]) + world.inaccessible_regions[player].extend([r.name for r in all_regions if r not in visited_regions and valid_inaccessible_region(r)]) if (world.mode[player] == 'inverted') != (0x1b in world.owswaps[player][0] and world.owMixed[player]): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 1017e6d7..2b51eaaf 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,5 +1,5 @@ import logging -from collections import defaultdict +from collections import defaultdict, OrderedDict import RaceRandom as random from BaseClasses import CollectionState, RegionType from OverworldShuffle import build_accessible_region_list @@ -432,21 +432,21 @@ def link_entrances(world, player): connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (dw_entrances if not invFlag else lw_entrances)] connect_inaccessible_regions(world, [], connector_entrances, caves, player) if invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) - caves = list(set(Cave_Base) & set(caves)) + (lw_dungeons if not invFlag else dw_dungeons) + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + (lw_dungeons if not invFlag else dw_dungeons) connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (lw_entrances if not invFlag else dw_entrances)] connect_inaccessible_regions(world, connector_entrances, [], caves, player) if not invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else []) dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) - caves = list(set(Cave_Base) & set(caves)) + DW_Mid_Dungeon_Exits + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + DW_Mid_Dungeon_Exits # place old man, has limited options lw_entrances = [e for e in lw_entrances if e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] and e in entrance_pool] @@ -1429,10 +1429,10 @@ def place_blacksmith(world, links_house, player): if invFlag: dark_sanc = world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) elif world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3: sanc_region = world.get_entrance('Sanctuary Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) if world.shuffle[player] in ['lite', 'lean']: blacksmith_doors = [e for e in blacksmith_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]] @@ -1491,7 +1491,7 @@ def junk_fill_inaccessible(world, player): accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # get inaccessible entrances @@ -1527,7 +1527,7 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # split inaccessible into 2 lists for each world @@ -1665,12 +1665,12 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor new_regions.append(ledge) explored_regions.extend(new_regions) - entrances = set() + entrances = list() for region_name in explored_regions: region = base_world.get_region(region_name, player) for exit in region.exits: if exit.name in entrance_pool and (not exit_rules or exit.access_rule(blank_state)): - entrances.add(exit.name) + entrances.append(exit.name) return entrances @@ -1798,7 +1798,7 @@ Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cav Old_Man_House_Base = [('Old Man House Exit (Bottom)', 'Old Man House Exit (Top)')] -Entrance_Pool_Base = {'Links House', +Entrance_Pool_Base = ['Links House', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (East)', @@ -1936,9 +1936,9 @@ Entrance_Pool_Base = {'Links House', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (North)', - 'Pyramid Hole'} + 'Pyramid Hole'] -Exit_Pool_Base = {'Links House Exit', +Exit_Pool_Base = ['Links House Exit', 'Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', @@ -2076,7 +2076,7 @@ Exit_Pool_Base = {'Links House Exit', 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle', - 'Pyramid'} + 'Pyramid'] # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions mandatory_connections = [('Links House S&Q', 'Links House'), diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 62b7d6e4..55a063ec 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,4 +1,5 @@ import RaceRandom as random, logging, copy +from collections import OrderedDict from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel @@ -500,7 +501,7 @@ def shuffle_tiles(world, groups, result_list, player): exist_dw_regions.extend(dw_regions) # check whirlpool parity - valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(set(new_results[0]) & set({0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f})) % 2 == 0 + valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(OrderedDict.fromkeys(new_results[0] + [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f])) % 2 == 0 (exist_owids, exist_lw_regions, exist_dw_regions) = result_list exist_owids.extend(new_results[0]) @@ -809,7 +810,7 @@ def can_reach_smith(world, player): def explore_region(region_name, region=None): nonlocal found - explored_regions.add(region_name) + explored_regions.append(region_name) if not found: if not region: region = world.get_region(region_name, player) @@ -838,7 +839,7 @@ def can_reach_smith(world, player): blank_state.collect(ItemFactory('Titans Mitts', player), True) found = False - explored_regions = set() + explored_regions = list() explore_region('Links House') if not found: if not invFlag: @@ -867,7 +868,7 @@ def build_sectors(world, player): if (any(r in unique_regions for r in explored_regions)): for s in range(len(sectors)): if (any(r in sectors[s] for r in explored_regions)): - sectors[s] = set(list(sectors[s]) + list(explored_regions)) + sectors[s] = list(list(sectors[s]) + list(explored_regions)) break else: sectors.append(explored_regions) @@ -893,7 +894,7 @@ def build_sectors(world, player): if (any(r in unique_regions for r in explored_regions)): for s2 in range(len(sectors2)): if (any(r in sectors2[s2] for r in explored_regions)): - sectors2[s2] = set(list(sectors2[s2]) + list(explored_regions)) + sectors2[s2] = list(sectors2[s2] + explored_regions) break else: sectors2.append(explored_regions) @@ -907,7 +908,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Items import ItemFactory def explore_region(region_name, region=None): - explored_regions.add(region_name) + explored_regions.append(region_name) if not region: region = base_world.get_region(region_name, player) for exit in region.exits: @@ -936,7 +937,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F blank_state = CollectionState(base_world) if base_world.mode[player] == 'standard': blank_state.collect(ItemFactory('Zelda Delivered', player), True) - explored_regions = set() + explored_regions = list() explore_region(start_region) return explored_regions