From 95f672a8420fe3d2beacb06a98968de5e5a1da2b Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Fri, 5 Jan 2018 20:21:24 -0500 Subject: [PATCH 1/5] POD key for Key Logic --- Rules.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/Rules.py b/Rules.py index bd20dcd6..7197e4e6 100644 --- a/Rules.py +++ b/Rules.py @@ -314,24 +314,12 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) - if world.keysanity: - set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) - # TODO: add an always_allow rule for this to permit key for a key - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) - # TODO: add an always_allow rule for this to permit key for a key - set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) - else: - set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'])) - # TODO: add an always_allow rule for this to permit key for a key - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 5) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'])) - # TODO: add an always_allow rule for this to permit key for a key - set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 5)) + set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 3))) + set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) - for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Helmasaur']: - forbid_item(world.get_location(location), 'Big Key (Palace of Darkness)') - - for location in ['Palace of Darkness - Big Chest', 'Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom']: - forbid_item(world.get_location(location), 'Small Key (Palace of Darkness)') + set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 4))) + set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) + set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) # these key rules are conservative, you might be able to get away with more lenient rules randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] From a0c892ab983a27c8e475effcfa2240ef6b59f5d1 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Fri, 5 Jan 2018 22:46:00 -0500 Subject: [PATCH 2/5] Make location item checks less verbose, and always access world via the state variable --- Rules.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Rules.py b/Rules.py index 7197e4e6..5f4a26ac 100644 --- a/Rules.py +++ b/Rules.py @@ -62,12 +62,12 @@ def forbid_item(location, item): def item_in_locations(state, item, locations): for location in locations: - loc = state.world.get_location(location) - if item_name(loc) == item: + if item_name(state, location) == item: return True return False -def item_name(location): +def item_name(state, location): + location = state.world.get_location(location) if location.item is None: return None return location.item.name @@ -218,7 +218,7 @@ def global_rules(world): for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']: forbid_item(world.get_location(location), 'Small Key (Desert Palace)') - set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)') or item_name(world.get_location('Tower of Hera - Big Key Chest')) == 'Small Key (Tower of Hera)') + set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)') set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)')) set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)')) set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source()) @@ -233,7 +233,7 @@ def global_rules(world): set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam')) set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)')) set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer')) - set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(world.get_location('Swamp Palace - Big Chest')) == 'Big Key (Swamp Palace)') + set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)') set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)') set_rule(world.get_entrance('Swamp Palace (North)'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Swamp Palace - Arrghus'), lambda state: state.has_blunt_weapon()) @@ -243,7 +243,7 @@ def global_rules(world): set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)')) set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna'))) - set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(world.get_location('Thieves\' Town - Big Chest')) == 'Small Key (Thieves Town)') and state.has('Hammer')) + set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer')) set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)') set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)')) for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']: @@ -255,7 +255,7 @@ def global_rules(world): set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has('Small Key (Skull Woods)')) set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has('Small Key (Skull Woods)', 2)) - set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(world.get_location('Skull Woods - Big Chest')) == 'Big Key (Skull Woods)') + set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)') set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)') set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain for location in ['Skull Woods - Mothula']: @@ -264,14 +264,14 @@ def global_rules(world): set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword())) set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)')) set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1)))) - set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)'))) and (world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) + set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']: forbid_item(world.get_location(location), 'Big Key (Ice Palace)') set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ... set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)')) - set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: world.can_take_damage or state.has('Cane of Byrna') or state.has('Cape')) + set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: state.world.can_take_damage or state.has('Cane of Byrna') or state.has('Cape')) set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)')) # you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ... # big key gives backdoor access to that from the teleporter in the north west @@ -279,8 +279,8 @@ def global_rules(world): # in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key... set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has('Small Key (Misery Mire)', 2) or state.has('Big Key (Misery Mire)')) # we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet - set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((item_name(state.world.get_location('Misery Mire - Compass Chest')) in ['Big Key (Misery Mire)']) or - (item_name(state.world.get_location('Misery Mire - Big Key Chest')) in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3)) + set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or + (item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3)) set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source()) set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source()) set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon())) @@ -314,10 +314,10 @@ def global_rules(world): set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) - set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Big Key Chest')) in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 3))) + set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 3))) set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) - set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state.world.get_location('Palace of Darkness - Harmless Hellway')) in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 4))) + set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 4))) set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) @@ -329,7 +329,7 @@ def global_rules(world): set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer')) - set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_name(state.world.get_location('Ganons Tower - Map Chest')) in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has('Small Key (Ganons Tower)', 3))) + set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has('Small Key (Ganons Tower)', 3))) set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3)) # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements. @@ -495,7 +495,7 @@ def set_trock_key_rules(world): # the most complicated one def tr_big_key_chest_keys_needed(state): - item = item_name(state.world.get_location('Turtle Rock - Big Key Chest')) + item = item_name(state, 'Turtle Rock - Big Key Chest') # handle key for a key situation in the usual way (by letting us logically open the door using the key locked inside it) if item in ['Small Key (Turtle Rock)']: return 3 From 9846f924d230a3b9d3745d424366c3c9b71649ee Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 6 Jan 2018 13:39:22 -0500 Subject: [PATCH 3/5] Add new 4 heart logic for spike cave and misery mire --- BaseClasses.py | 13 +++++++++++++ ItemList.py | 10 ++++++++++ Rules.py | 16 ++++++++++++---- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f0b4e184..912f9957 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -368,6 +368,19 @@ class CollectionState(object): def bottle_count(self): return len([pritem for pritem in self.prog_items if pritem.startswith('Bottle')]) + def has_hearts(self, count): + # Warning: This oncly considers items that are marked as advancement items + return self.heart_count() >= count + + def heart_count(self): + # Warning: This oncly considers items that are marked as advancement items + return ( + self.item_count('Boss Heart Container') + + self.item_count('Sanctuary Heart Container') + + self.item_count('Piece of Heart') // 4 + + 3 # starting hearts + ) + def can_lift_heavy_rocks(self): return self.has('Titans Mitts') diff --git a/ItemList.py b/ItemList.py index 877d332e..d2847613 100644 --- a/ItemList.py +++ b/ItemList.py @@ -228,6 +228,16 @@ def generate_itempool(world): if world.keysanity: world.itempool.extend(get_dungeon_item_pool(world)) + # logic has some branches where having 4 hearts is one possible requirement (of several alternatives) + # rather than making all hearts/heart pieces progression items (which slows down generation considerably) + # We mark one random heart container as an advancement item (or 4 heart peices in expert mode) + if world.difficulty in ['easy', 'normal', 'hard']: + [item for item in world.itempool if item.name == 'Boss Heart Container'][0].advancement = True + elif world.difficulty in ['expert']: + adv_heart_pieces = [item for item in world.itempool if item.name == 'Piece of Heart'][0:4] + for hp in adv_heart_pieces: + hp.advancement = True + # shuffle medallions mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] diff --git a/Rules.py b/Rules.py index 5f4a26ac..80f3dba9 100644 --- a/Rules.py +++ b/Rules.py @@ -185,9 +185,17 @@ def global_rules(world): set_rule(world.get_entrance('Fairy Ascension Mirror Spot'), lambda state: state.has_Mirror() and state.has_Pearl()) # need to lift flowers set_rule(world.get_entrance('Isolated Ledge Mirror Spot'), lambda state: state.has_Mirror()) set_rule(world.get_entrance('Superbunny Cave Exit (Bottom)'), lambda state: False) # Cannot get to bottom exit from top. Just exists for shuffling - set_rule(world.get_location('Spike Cave'), lambda state: state.has('Hammer') and state.can_lift_rocks() and (state.has('Cane of Byrna') or state.has('Cape')) and state.can_extend_magic()) - # TODO: Current-VT logic is: hammer and lift_rocks and ((cape and extend) or (byrna and (can-take-damage OR canextend))) - # Is that really good enough? Can you really get through with byrna, single magic w/o refills and only 3 hearts? (answer: probably but seems to requires tas-like timing.) + set_rule(world.get_location('Spike Cave'), lambda state: + state.has('Hammer') and state.can_lift_rocks() + and ((state.has('Cape') and state.can_extend_magic()) + or (state.has('Cane of Byrna') and state.can_extend_magic()) + or (state.world.can_take_damage + and state.has('Cane of Byrna') + and (state.has_Boots() or state.has_hearts(4)) + ) + ) + ) + set_rule(world.get_location('Hookshot Cave - Top Right'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Top Left'), lambda state: state.has('Hookshot')) set_rule(world.get_location('Hookshot Cave - Bottom Right'), lambda state: state.has('Hookshot') or state.has('Pegasus Boots')) @@ -271,7 +279,7 @@ def global_rules(world): set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ... set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)')) - set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: state.world.can_take_damage or state.has('Cane of Byrna') or state.has('Cape')) + set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape')) set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)')) # you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ... # big key gives backdoor access to that from the teleporter in the north west From a86573d529e62d751a0d43df5a10889f1ae7c4ac Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 6 Jan 2018 14:25:49 -0500 Subject: [PATCH 4/5] Add an extra pass to get correct spheres after culling For an example of a circumstance in which the existing code produces incorrect spheres, consider the following item locations in the vanilla entrance layout: * byrna in sphere 0 * hammer in sphere 0 * gloves in sphere 0 * bottle in sphere 0 * bow in spike cave (sphere 1) * cape in hype cave (sphere 1) * flippers on bumper cave Ledge (sphere 2) * lastly both Swamp and PoD are pendants. Well the culling phase will remove Byrna, because cape is good enough to still get the bow. But now the bow in sphere 1 depends on the cape also from sphere 1. --- Main.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Main.py b/Main.py index fbdc0e8e..c97f8637 100644 --- a/Main.py +++ b/Main.py @@ -232,8 +232,31 @@ def create_playthrough(world): for location in to_delete: sphere.remove(location) - # we are now down to just the required progress items in collection_spheres in a minimum number of spheres. As a cleanup, we right trim empty spheres (can happen if we have multiple triforces) - collection_spheres = [sphere for sphere in collection_spheres if sphere] + # we are now down to just the required progress items in collection_spheres. Unfortunately + # the previous pruning stage could potentially have made certain items dependant on others + # in the same or later sphere (because the location had 2 ways to access but the item originally + # used to access it was deemed not required.) So we need to do one final sphere collection pass + # to build up the correct spheres + + required_locations = [item for sphere in collection_spheres for item in sphere] + state = CollectionState(world) + collection_spheres = [] + while required_locations: + if not world.keysanity: + state.sweep_for_events(key_only=True) + + sphere = list(filter(state.can_reach, required_locations)) + + for location in sphere: + required_locations.remove(location) + state.collect(location.item, True, location) + + collection_spheres.append(sphere) + + logging.getLogger('').debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(required_locations)) + if not sphere: + raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') + # store the required locations for statistical analysis old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] From a155d455bb2d4b6b06f8be6b3cd753ab791bd789 Mon Sep 17 00:00:00 2001 From: Kevin Cathcart Date: Sat, 6 Jan 2018 16:25:14 -0500 Subject: [PATCH 5/5] Add Bomb Shop to paths output if Pyramid Fairy Entrance is needed Tweak internal handling of paths --- BaseClasses.py | 7 ++----- Main.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 912f9957..de0f3485 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,7 +1,6 @@ import copy import logging import json -from itertools import zip_longest from collections import OrderedDict @@ -769,11 +768,9 @@ class Spoiler(object): outfile.write('\n\nPaths:\n\n') path_listings = [] - for location, paths in self.paths.items(): + for location, path in self.paths.items(): path_lines = [] - pathsiter = iter(paths) - pathpairs = zip_longest(pathsiter, pathsiter) - for region, exit in pathpairs: + for region, exit in path: if exit is not None: path_lines.append("{} -> {}".format(region, exit)) else: diff --git a/Main.py b/Main.py index c97f8637..15a5e5be 100644 --- a/Main.py +++ b/Main.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from itertools import zip_longest import json import logging import random @@ -257,7 +258,6 @@ def create_playthrough(world): if not sphere: raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') - # store the required locations for statistical analysis old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere] @@ -266,7 +266,18 @@ def create_playthrough(world): value, node = node yield value - old_world.spoiler.paths = {location.name : list(reversed(list(map(str, flist_to_iter(state.path.get(location.parent_region, (location.parent_region, None))))))) for sphere in collection_spheres for location in sphere} + def get_path(state, region): + reversed_path_as_flist = state.path.get(region, (region, None)) + string_path_flat = reversed(list(map(str, flist_to_iter(reversed_path_as_flist)))) + # Now we combine the flat string list into (region, exit) pairs + pathsiter = iter(string_path_flat) + pathpairs = zip_longest(pathsiter, pathsiter) + return list(pathpairs) + + old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere} + if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path): + old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop')) + print(world.seed) # we can finally output our playthrough old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])