Merge pull request #8 from KevinCathcart/Dev

Update through Bomb Shop in paths
This commit is contained in:
AmazingAmpharos 2018-01-06 18:58:19 -06:00 committed by GitHub
commit 193b3323b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 41 deletions

View File

@ -1,7 +1,6 @@
import copy
import logging
import json
from itertools import zip_longest
from collections import OrderedDict
@ -368,6 +367,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')
@ -756,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:

View File

@ -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)]

40
Main.py
View File

@ -1,4 +1,5 @@
from collections import OrderedDict
from itertools import zip_longest
import json
import logging
import random
@ -232,8 +233,30 @@ 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]
@ -243,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)])

View File

@ -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
@ -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'))
@ -218,7 +226,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 +241,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 +251,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 +263,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 +272,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 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
@ -279,8 +287,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,24 +322,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, '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, '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']
@ -341,7 +337,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.
@ -507,7 +503,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