Fixed remaining key logic, correctly implemented sewers. Added support for other game modes, create playthrough.
This commit is contained in:
parent
57483052e4
commit
f374c637c3
|
@ -1,9 +1,15 @@
|
||||||
import copy
|
import copy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, shuffle, logic, mode, difficulty, goal):
|
||||||
|
self.shuffle = shuffle
|
||||||
|
self.logic = logic
|
||||||
|
self.mode = mode
|
||||||
|
self.difficulty = difficulty
|
||||||
|
self.goal = goal
|
||||||
self.regions = []
|
self.regions = []
|
||||||
self.itempool = []
|
self.itempool = []
|
||||||
self.state = CollectionState(self)
|
self.state = CollectionState(self)
|
||||||
|
@ -66,7 +72,7 @@ class World(object):
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item)
|
self.state.collect(item)
|
||||||
|
|
||||||
print('Placed %s at %s' % (item, location))
|
logging.getLogger('').debug('Placed %s at %s' % (item, location))
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
|
raise RuntimeError('Cannot assign item %s to location %s.' % (item, location))
|
||||||
|
|
||||||
|
@ -96,11 +102,34 @@ class World(object):
|
||||||
temp_state.collect(item)
|
temp_state.collect(item)
|
||||||
return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state))
|
return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state))
|
||||||
|
|
||||||
|
def can_beat_game(self):
|
||||||
|
prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement]
|
||||||
|
|
||||||
|
state = CollectionState(self)
|
||||||
|
while prog_locations:
|
||||||
|
sphere = []
|
||||||
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
|
for location in prog_locations:
|
||||||
|
if state.can_reach(location):
|
||||||
|
if location.item.name == 'Triforce':
|
||||||
|
return True
|
||||||
|
sphere.append(location)
|
||||||
|
|
||||||
|
if not sphere:
|
||||||
|
# ran out of places and did not find triforce yet, quit
|
||||||
|
return False
|
||||||
|
|
||||||
|
for location in sphere:
|
||||||
|
prog_locations.remove(location)
|
||||||
|
state.collect(location.item)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CollectionState(object):
|
class CollectionState(object):
|
||||||
|
|
||||||
def __init__(self, parent, has_everything=False):
|
def __init__(self, parent, has_everything=False):
|
||||||
self.prog_items = set()
|
self.prog_items = []
|
||||||
self.world = parent
|
self.world = parent
|
||||||
self.has_everything = has_everything
|
self.has_everything = has_everything
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
@ -233,32 +262,68 @@ class CollectionState(object):
|
||||||
if self.has('Golden Sword'):
|
if self.has('Golden Sword'):
|
||||||
return
|
return
|
||||||
elif self.has('Tempered Sword'):
|
elif self.has('Tempered Sword'):
|
||||||
self.prog_items.add('Golden Sword')
|
self.prog_items.append('Golden Sword')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif self.has('Master Sword'):
|
elif self.has('Master Sword'):
|
||||||
self.prog_items.add('Tempered Sword')
|
self.prog_items.append('Tempered Sword')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif self.has('Fighter Sword'):
|
elif self.has('Fighter Sword'):
|
||||||
self.prog_items.add('Master Sword')
|
self.prog_items.append('Master Sword')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
else:
|
else:
|
||||||
self.prog_items.add('Fighter Sword')
|
self.prog_items.append('Fighter Sword')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif 'Glove' in item.name:
|
elif 'Glove' in item.name:
|
||||||
if self.has('Titans Mitts'):
|
if self.has('Titans Mitts'):
|
||||||
return
|
return
|
||||||
elif self.has('Power Glove'):
|
elif self.has('Power Glove'):
|
||||||
self.prog_items.add('Titans Mitts')
|
self.prog_items.append('Titans Mitts')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
else:
|
else:
|
||||||
self.prog_items.add('Power Glove')
|
self.prog_items.append('Power Glove')
|
||||||
self.changed = True
|
self.changed = True
|
||||||
return
|
return
|
||||||
|
|
||||||
if item.advancement:
|
if item.advancement:
|
||||||
self.prog_items.add(item.name)
|
self.prog_items.append(item.name)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
|
def remove(self, item):
|
||||||
|
if item.advancement:
|
||||||
|
to_remove = item.name
|
||||||
|
if to_remove.startswith('Progressive '):
|
||||||
|
if 'Sword' in to_remove:
|
||||||
|
if self.has('Golden Sword'):
|
||||||
|
to_remove = 'Golden Sword'
|
||||||
|
elif self.has('Tempered Sword'):
|
||||||
|
to_remove = 'Tempered Sword'
|
||||||
|
elif self.has('Master Sword'):
|
||||||
|
to_remove = 'Master Sword'
|
||||||
|
elif self.has('Fighter Sword'):
|
||||||
|
to_remove = 'Fighter Sword'
|
||||||
|
else:
|
||||||
|
to_remove = None
|
||||||
|
elif 'Glove' in item.name:
|
||||||
|
if self.has('Titans Mitts'):
|
||||||
|
to_remove = 'Titans Mitts'
|
||||||
|
elif self.has('Power Glove'):
|
||||||
|
to_remove = 'Power Glove'
|
||||||
|
else:
|
||||||
|
to_remove = None
|
||||||
|
|
||||||
|
if to_remove is not None:
|
||||||
|
try:
|
||||||
|
self.prog_items.remove(to_remove)
|
||||||
|
except IndexError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# invalidate caches, nothing can be trusted anymore now
|
||||||
|
self.region_cache = {}
|
||||||
|
self.location_cache = {}
|
||||||
|
self.entrance_cache = {}
|
||||||
|
self.recursion_cache = []
|
||||||
|
self.changed = False
|
||||||
|
|
||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
if item.startswith('can_reach_'):
|
if item.startswith('can_reach_'):
|
||||||
return self.can_reach(item[10])
|
return self.can_reach(item[10])
|
||||||
|
@ -340,10 +405,7 @@ class Location(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def item_rule(self, item):
|
def item_rule(self, item):
|
||||||
if item.name != 'Triforce':
|
return True
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def can_reach(self, state):
|
def can_reach(self, state):
|
||||||
if self.parent_region:
|
if self.parent_region:
|
||||||
|
@ -372,4 +434,3 @@ class Item(object):
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
def link_entrances(world, shuffle):
|
def link_entrances(world):
|
||||||
# setup mandatory connections
|
# setup mandatory connections
|
||||||
for exitname, regionname in mandatory_connections:
|
for exitname, regionname in mandatory_connections:
|
||||||
connect(world, exitname, regionname)
|
connect(world, exitname, regionname)
|
||||||
|
|
||||||
# if we do not shuffle, set default connections
|
# if we do not shuffle, set default connections
|
||||||
if shuffle=='Default':
|
if world.shuffle == 'Default':
|
||||||
for exitname, regionname in default_connections:
|
for exitname, regionname in default_connections:
|
||||||
connect(world, exitname, regionname)
|
connect(world, exitname, regionname)
|
||||||
return
|
return
|
||||||
|
@ -27,6 +27,11 @@ mandatory_connections = [('Zoras River', 'Zoras River'),
|
||||||
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
|
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
|
||||||
('Desert Palace Stairs', 'Desert Palace Stairs'),
|
('Desert Palace Stairs', 'Desert Palace Stairs'),
|
||||||
('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'),
|
('Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (North) Spot'),
|
||||||
|
('Throne Room', 'Sewers (Dark)'),
|
||||||
|
('Sewers Door', 'Sewers'),
|
||||||
|
('Sanctuary Push Door', 'Sanctuary'),
|
||||||
|
('Sewer Drop', 'Sewers'),
|
||||||
|
('Sewers Back Door', 'Sewers (Dark)'),
|
||||||
('Aghanim 1', 'Aghanim 1'),
|
('Aghanim 1', 'Aghanim 1'),
|
||||||
('Flute Spot 1', 'Death Mountain'),
|
('Flute Spot 1', 'Death Mountain'),
|
||||||
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
|
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
|
||||||
|
@ -174,8 +179,9 @@ default_connections = [("Thiefs Hut", "Thiefs Hut"),
|
||||||
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
|
('Hyrule Castle Exit (West)', 'Hyrule Castle Ledge'),
|
||||||
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
|
('Hyrule Castle Exit (East)', 'Hyrule Castle Ledge'),
|
||||||
('Aghanims Tower', 'Aghanims Tower'),
|
('Aghanims Tower', 'Aghanims Tower'),
|
||||||
('Sanctuary', 'Hyrule Castle'), # this set of two exits can be randomized together!
|
('Sanctuary', 'Sanctuary'),
|
||||||
('Sanctuary Grave', 'Hyrule Castle'),
|
('Sanctuary Grave', 'Sewer Drop'),
|
||||||
|
('Sanctuary Exit', 'Light World'),
|
||||||
|
|
||||||
('Old Man Cave (West)', 'Old Man Cave'),
|
('Old Man Cave (West)', 'Old Man Cave'),
|
||||||
('Old Man Cave (East)', 'Old Man Cave'),
|
('Old Man Cave (East)', 'Old Man Cave'),
|
||||||
|
|
193
Main.py
193
Main.py
|
@ -1,4 +1,4 @@
|
||||||
from BaseClasses import World
|
from BaseClasses import World, CollectionState
|
||||||
from Regions import create_regions
|
from Regions import create_regions
|
||||||
from EntranceShuffle import link_entrances
|
from EntranceShuffle import link_entrances
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
|
@ -7,19 +7,21 @@ from Items import *
|
||||||
import random
|
import random
|
||||||
import cProfile
|
import cProfile
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
def main(seed=None, shuffle='Default', logic='no-glitches', mode='standard', difficulty='normal', goal='defeat ganon'):
|
def main(seed=None, shuffle='Default', logic='no-glitches', mode='standard', difficulty='normal', goal='defeat ganon'):
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World()
|
world = World(shuffle, logic, mode, difficulty, goal)
|
||||||
create_regions(world)
|
create_regions(world)
|
||||||
|
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
|
|
||||||
link_entrances(world, shuffle)
|
link_entrances(world)
|
||||||
set_rules(world, logic, mode)
|
set_rules(world)
|
||||||
generate_itempool(world, difficulty, goal)
|
generate_itempool(world)
|
||||||
distribute_items(world)
|
distribute_items(world)
|
||||||
|
# flood_items(world) # different algo, biased towards early game progress items
|
||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,6 +58,10 @@ def distribute_items(world):
|
||||||
if candidate_item_to_place is not None:
|
if candidate_item_to_place is not None:
|
||||||
item_to_place = candidate_item_to_place
|
item_to_place = candidate_item_to_place
|
||||||
else:
|
else:
|
||||||
|
# we placed all available progress items. Maybe the game can be beaten anyway?
|
||||||
|
if world.can_beat_game():
|
||||||
|
logging.getLogger('').warning('Not all locations reachable. Game beatable anyway.')
|
||||||
|
break
|
||||||
raise RuntimeError('No more progress items left to place.')
|
raise RuntimeError('No more progress items left to place.')
|
||||||
|
|
||||||
spot_to_fill = None
|
spot_to_fill = None
|
||||||
|
@ -65,21 +71,80 @@ def distribute_items(world):
|
||||||
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?
|
||||||
|
if world.can_beat_game():
|
||||||
|
logging.getLogger('').warning('Not all items placed. Game beatable anyway.')
|
||||||
|
break
|
||||||
raise RuntimeError('No more spots to place %s' % item_to_place)
|
raise RuntimeError('No more spots to place %s' % item_to_place)
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, True)
|
world.push_item(spot_to_fill, item_to_place, True)
|
||||||
itempool.remove(item_to_place)
|
itempool.remove(item_to_place)
|
||||||
fill_locations.remove(spot_to_fill)
|
fill_locations.remove(spot_to_fill)
|
||||||
|
|
||||||
print('Unplaced items: %s - Unfilled Locations: %s' % (itempool, fill_locations))
|
logging.getLogger('').debug('Unplaced items: %s - Unfilled Locations: %s' % (itempool, fill_locations))
|
||||||
|
|
||||||
|
|
||||||
def generate_itempool(world, difficulty, goal):
|
def flood_items(world):
|
||||||
if difficulty != 'normal' or goal != 'defeat ganon':
|
# get items to distribute
|
||||||
|
random.shuffle(world.itempool)
|
||||||
|
itempool = world.itempool
|
||||||
|
progress_done = False
|
||||||
|
|
||||||
|
# fill world from top of itempool while we can
|
||||||
|
while not progress_done:
|
||||||
|
location_list = world.get_unfilled_locations()
|
||||||
|
random.shuffle(location_list)
|
||||||
|
spot_to_fill = None
|
||||||
|
for location in location_list:
|
||||||
|
if world.state.can_reach(location):
|
||||||
|
spot_to_fill = location
|
||||||
|
break
|
||||||
|
|
||||||
|
if spot_to_fill:
|
||||||
|
item = itempool.pop(0)
|
||||||
|
world.push_item(spot_to_fill, item, True)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ran out of spots, check if we need to step in and correct things
|
||||||
|
if len(world.get_reachable_locations()) == len(world.get_locations()):
|
||||||
|
progress_done = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# need to place a progress item instead of an already placed item, find candidate
|
||||||
|
item_to_place = None
|
||||||
|
candidate_item_to_place = None
|
||||||
|
for item in itempool:
|
||||||
|
if item.advancement:
|
||||||
|
candidate_item_to_place = item
|
||||||
|
if world.unlocks_new_location(item):
|
||||||
|
item_to_place = item
|
||||||
|
break
|
||||||
|
|
||||||
|
# we might be in a situation where all new locations require multiple items to reach. If that is the case, just place any advancement item we've found and continue trying
|
||||||
|
if item_to_place is None:
|
||||||
|
if candidate_item_to_place is not None:
|
||||||
|
item_to_place = candidate_item_to_place
|
||||||
|
else:
|
||||||
|
raise RuntimeError('No more progress items left to place.')
|
||||||
|
|
||||||
|
# find item to replace with progress item
|
||||||
|
location_list = world.get_reachable_locations()
|
||||||
|
random.shuffle(location_list)
|
||||||
|
for location in location_list:
|
||||||
|
if location.item is not None and not location.item.advancement and not location.item.key and 'Map' not in location.item.name and 'Compass' not in location.item.name:
|
||||||
|
# safe to replace
|
||||||
|
replace_item = location.item
|
||||||
|
replace_item.location = None
|
||||||
|
itempool.append(replace_item)
|
||||||
|
world.push_item(location, item_to_place, True)
|
||||||
|
itempool.remove(item_to_place)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def generate_itempool(world):
|
||||||
|
if world.difficulty != 'normal' or world.goal not in ['defeat ganon', 'pedestal', 'all dungeons'] or world.mode not in ['open', 'standard']:
|
||||||
raise NotImplementedError('Not supported yet')
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
# Push the two fixed items
|
|
||||||
world.push_item('Uncle', ProgressiveSword())
|
|
||||||
world.push_item('Ganon', Triforce(), False)
|
world.push_item('Ganon', Triforce(), False)
|
||||||
|
|
||||||
# set up item pool
|
# set up item pool
|
||||||
|
@ -134,6 +199,22 @@ def generate_itempool(world, difficulty, goal):
|
||||||
Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20()
|
Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20(), Rupees20()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if world.mode == 'standard':
|
||||||
|
world.push_item('Uncle', ProgressiveSword())
|
||||||
|
else:
|
||||||
|
world.itempool.append(ProgressiveSword())
|
||||||
|
|
||||||
|
if world.goal == 'pedestal':
|
||||||
|
world.push_item('Altar', Triforce())
|
||||||
|
items = list(world.itempool)
|
||||||
|
random.shuffle(items)
|
||||||
|
for item in items:
|
||||||
|
if not item.advancement:
|
||||||
|
# save to remove
|
||||||
|
world.itempool.remove(item)
|
||||||
|
break
|
||||||
|
# ToDo what to do if EVERYTHING is a progress item?
|
||||||
|
|
||||||
if random.randint(0, 3) == 0:
|
if random.randint(0, 3) == 0:
|
||||||
world.itempool.append(QuarterMagic())
|
world.itempool.append(QuarterMagic())
|
||||||
else:
|
else:
|
||||||
|
@ -156,15 +237,95 @@ def generate_itempool(world, difficulty, goal):
|
||||||
# push dungeon items
|
# push dungeon items
|
||||||
fill_dungeons(world)
|
fill_dungeons(world)
|
||||||
|
|
||||||
#profiler = cProfile.Profile()
|
|
||||||
|
|
||||||
#profiler.enable()
|
def copy_world(world):
|
||||||
|
# ToDo: Not good yet
|
||||||
|
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.goal)
|
||||||
|
ret.required_medallions = list(world.required_medallions)
|
||||||
|
create_regions(ret)
|
||||||
|
set_rules(ret)
|
||||||
|
|
||||||
|
# connect copied world
|
||||||
|
for region in world.regions:
|
||||||
|
for entrance in region.entrances:
|
||||||
|
ret.get_entrance(entrance.name).connect(ret.get_region(region.name))
|
||||||
|
|
||||||
|
# fill locations
|
||||||
|
for location in world.get_locations():
|
||||||
|
if location.item is not None:
|
||||||
|
item = Item(location.item.name, location.item.advancement, location.item.key)
|
||||||
|
ret.get_location(location.name).item = item
|
||||||
|
item.location = ret.get_location(location.name)
|
||||||
|
|
||||||
|
# copy remaining itempool. No item in itempool should have an assigned location
|
||||||
|
for item in world.itempool:
|
||||||
|
ret.itempool.append(Item(item.name, item.advancement, item.key))
|
||||||
|
|
||||||
|
# copy progress items in state
|
||||||
|
ret.state.prog_items = list(world.state.prog_items)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def create_playthrough(world):
|
||||||
|
# create a copy as we will modify it
|
||||||
|
world = copy_world(world)
|
||||||
|
|
||||||
|
# get locations containing progress items
|
||||||
|
prog_locations = [location for location in world.get_locations() if location.item is not None and location.item.advancement]
|
||||||
|
|
||||||
|
collection_spheres = []
|
||||||
|
state = CollectionState(world)
|
||||||
|
sphere_candidates = list(prog_locations)
|
||||||
|
while sphere_candidates:
|
||||||
|
sphere = []
|
||||||
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
|
for location in sphere_candidates:
|
||||||
|
if state.can_reach(location):
|
||||||
|
sphere.append(location)
|
||||||
|
|
||||||
|
for location in sphere:
|
||||||
|
sphere_candidates.remove(location)
|
||||||
|
state.collect(location.item)
|
||||||
|
|
||||||
|
collection_spheres.append(sphere)
|
||||||
|
|
||||||
|
# in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it
|
||||||
|
for sphere in reversed(collection_spheres):
|
||||||
|
to_delete = []
|
||||||
|
for location in sphere:
|
||||||
|
# we remove the item at location and check if game is still beatable
|
||||||
|
old_item = location.item
|
||||||
|
location.item = None
|
||||||
|
state.remove(old_item)
|
||||||
|
world._item_cache = {} # need to invalidate
|
||||||
|
if world.can_beat_game():
|
||||||
|
to_delete.append(location)
|
||||||
|
else:
|
||||||
|
# still required, got to keep it around
|
||||||
|
location.item = old_item
|
||||||
|
|
||||||
|
# cull entries in spheres for spoiler walkthrough at end
|
||||||
|
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 can finally output our playthrough
|
||||||
|
return ''.join(['%s: {\n%s}\n' % (i + 1, ''.join([' %s: %s\n' % (location, location.item) for location in sphere])) for i, sphere in enumerate(collection_spheres)])
|
||||||
|
|
||||||
|
|
||||||
|
profiler = cProfile.Profile()
|
||||||
|
|
||||||
|
profiler.enable()
|
||||||
tally = {}
|
tally = {}
|
||||||
iterations = 300
|
iterations = 10
|
||||||
start = time.clock()
|
start = time.clock()
|
||||||
for i in range(iterations):
|
for i in range(iterations):
|
||||||
print('Seed %s\n\n' % i)
|
print('Seed %s\n\n' % i)
|
||||||
w = main()
|
w = main(mode='open')
|
||||||
|
print(create_playthrough(w))
|
||||||
for location in w.get_locations():
|
for location in w.get_locations():
|
||||||
if location.item is not None:
|
if location.item is not None:
|
||||||
old_sk, old_bk, old_prog = tally.get(location.name, (0, 0, 0))
|
old_sk, old_bk, old_prog = tally.get(location.name, (0, 0, 0))
|
||||||
|
@ -183,6 +344,6 @@ print('\n\n\n')
|
||||||
|
|
||||||
for location, stats in tally.items():
|
for location, stats in tally.items():
|
||||||
print('%s, %s, %s, %s, %s, %s, %s, %s' % (location, stats[0], stats[0]/float(iterations), stats[1], stats[1]/float(iterations), stats[2], stats[2]/float(iterations), 0 if iterations - stats[0] - stats[1] == 0 else stats[2]/float(iterations - stats[0] - stats[1])))
|
print('%s, %s, %s, %s, %s, %s, %s, %s' % (location, stats[0], stats[0]/float(iterations), stats[1], stats[1]/float(iterations), stats[2], stats[2]/float(iterations), 0 if iterations - stats[0] - stats[1] == 0 else stats[2]/float(iterations - stats[0] - stats[1])))
|
||||||
#profiler.disable()
|
|
||||||
|
|
||||||
#profiler.print_stats()
|
profiler.disable()
|
||||||
|
profiler.print_stats()
|
||||||
|
|
11
Regions.py
11
Regions.py
|
@ -56,10 +56,13 @@ def create_regions(world):
|
||||||
'[dungeon-L1-1F] Eastern Palace - Big Key Room', '[dungeon-L1-1F] Eastern Palace - Map Room', 'Armos - Heart Container', 'Armos - Pendant']),
|
'[dungeon-L1-1F] Eastern Palace - Big Key Room', '[dungeon-L1-1F] Eastern Palace - Map Room', 'Armos - Heart Container', 'Armos - Pendant']),
|
||||||
create_region('Master Sword Meadow', ['Altar']),
|
create_region('Master Sword Meadow', ['Altar']),
|
||||||
create_region('Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Aghanims Tower']),
|
create_region('Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Aghanims Tower']),
|
||||||
create_region('Hyrule Castle', ['[dungeon-C-B1] Hyrule Castle - Boomerang Room', '[dungeon-C-B1] Hyrule Castle - Map Room', '[dungeon-C-B1] Hyrule Castle - Next To Zelda',
|
create_region('Hyrule Castle', ['[dungeon-C-B1] Hyrule Castle - Boomerang Room', '[dungeon-C-B1] Hyrule Castle - Map Room', '[dungeon-C-B1] Hyrule Castle - Next To Zelda'],
|
||||||
'[dungeon-C-B1] Escape - First B1 Room', '[dungeon-C-B1] Escape - Final Basement Room [left chest]', '[dungeon-C-B1] Escape - Final Basement Room [middle chest]',
|
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']),
|
||||||
'[dungeon-C-B1] Escape - Final Basement Room [right chest]', '[dungeon-C-1F] Sanctuary'],
|
create_region('Sewer Drop', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)']),
|
create_region('Sewers (Dark)', ['[dungeon-C-B1] Escape - First B1 Room'], ['Sewers Door']),
|
||||||
|
create_region('Sewers', ['[dungeon-C-B1] Escape - Final Basement Room [left chest]', '[dungeon-C-B1] Escape - Final Basement Room [middle chest]',
|
||||||
|
'[dungeon-C-B1] Escape - Final Basement Room [right chest]'], ['Sanctuary Push Door', 'Sewers Back Door']),
|
||||||
|
create_region('Sanctuary', ['[dungeon-C-1F] Sanctuary'], ['Sanctuary Exit']),
|
||||||
create_region('Aghanims Tower', ['[dungeon-A1-2F] Hyrule Castle Tower - 2 Knife Guys Room', '[dungeon-A1-3F] Hyrule Castle Tower - Maze Room'], ['Aghanim 1']),
|
create_region('Aghanims Tower', ['[dungeon-A1-2F] Hyrule Castle Tower - 2 Knife Guys Room', '[dungeon-A1-3F] Hyrule Castle Tower - Maze Room'], ['Aghanim 1']),
|
||||||
create_region('Aghanim 1', None, ['Top of Pyramid']),
|
create_region('Aghanim 1', None, ['Top of Pyramid']),
|
||||||
create_region('Old Man Cave', ['Old Mountain Man'], ['Old Man Cave Exit']),
|
create_region('Old Man Cave', ['Old Mountain Man'], ['Old Man Cave Exit']),
|
||||||
|
|
65
Rules.py
65
Rules.py
|
@ -1,11 +1,22 @@
|
||||||
def set_rules(world, logic, mode):
|
def set_rules(world):
|
||||||
global_rules(world)
|
global_rules(world)
|
||||||
|
|
||||||
if logic == 'no-glitches' and mode in ['open', 'standard']:
|
if world.logic == 'no-glitches':
|
||||||
no_glitches_rules(world, mode)
|
no_glitches_rules(world)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Not implemented yet')
|
raise NotImplementedError('Not implemented yet')
|
||||||
|
|
||||||
|
if world.mode == 'open':
|
||||||
|
open_rules(world)
|
||||||
|
elif world.mode == 'standard':
|
||||||
|
standard_rules(world)
|
||||||
|
else:
|
||||||
|
raise NotImplementedError('Not implemented yet')
|
||||||
|
|
||||||
|
if world.goal == 'all dungeons':
|
||||||
|
# require altar for ganon to enforce getting everything
|
||||||
|
add_rule(world.get_location('Ganon'), lambda state: state.can_reach('Altar', 'Location'))
|
||||||
|
|
||||||
|
|
||||||
def set_rule(spot, rule):
|
def set_rule(spot, rule):
|
||||||
spot.access_rule = rule
|
spot.access_rule = rule
|
||||||
|
@ -111,9 +122,8 @@ def global_rules(world):
|
||||||
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion()) # sword required to cast magic (!)
|
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion()) # sword required to cast magic (!)
|
||||||
set_rule(world.get_location('[cave-013] Mimic Cave'), lambda state: state.has('Hammer'))
|
set_rule(world.get_location('[cave-013] Mimic Cave'), lambda state: state.has('Hammer'))
|
||||||
|
|
||||||
set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_lift_rocks()) # ToDo fix this up for shuffling, need access to drop into escape
|
set_rule(world.get_entrance('Sewers Door'), lambda state: state.can_collect('Small Key (Escape)'))
|
||||||
set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_lift_rocks())
|
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.can_collect('Small Key (Escape)'))
|
||||||
set_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_lift_rocks())
|
|
||||||
|
|
||||||
set_rule(world.get_location('[dungeon-L1-1F] Eastern Palace - Big Chest'), lambda state: state.can_collect('Big Key (Eastern Palace)'))
|
set_rule(world.get_location('[dungeon-L1-1F] Eastern Palace - Big Chest'), lambda state: state.can_collect('Big Key (Eastern Palace)'))
|
||||||
set_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Bow') and state.can_collect('Big Key (Eastern Palace)'))
|
set_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Bow') and state.can_collect('Big Key (Eastern Palace)'))
|
||||||
|
@ -151,24 +161,25 @@ def global_rules(world):
|
||||||
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.can_collect('Big Key (Thieves Town)'))
|
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.can_collect('Big Key (Thieves Town)'))
|
||||||
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has_blunt_weapon() or state.has('Cane of Somaria'))
|
set_rule(world.get_entrance('Blind Fight'), lambda state: state.can_collect('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria')))
|
||||||
set_rule(world.get_location('[dungeon-D4-B2] Thieves Town - Big Chest'), lambda state: state.can_collect('Small Key (Thieves Town)') and state.has('Hammer'))
|
set_rule(world.get_location('[dungeon-D4-B2] Thieves Town - Big Chest'), lambda state: state.can_collect('Small Key (Thieves Town)') and state.has('Hammer'))
|
||||||
set_rule(world.get_location('[dungeon-D4-1F] Thieves Town - Room above Boss'), lambda state: state.can_collect('Small Key (Thieves Town)'))
|
set_rule(world.get_location('[dungeon-D4-1F] Thieves Town - Room above Boss'), lambda state: state.can_collect('Small Key (Thieves Town)'))
|
||||||
for location in ['[dungeon-D4-1F] Thieves Town - Room above Boss', '[dungeon-D4-B2] Thieves Town - Big Chest', '[dungeon-D4-B2] Thieves Town - Chest next to Blind', 'Blind - Heart Container']:
|
for location in ['[dungeon-D4-1F] Thieves Town - Room above Boss', '[dungeon-D4-B2] Thieves Town - Big Chest', '[dungeon-D4-B2] Thieves Town - Chest next to Blind', 'Blind - Heart Container']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
|
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
|
||||||
|
|
||||||
set_rule(world.get_location('[dungeon-D3-B1] Skull Woods - Big Chest'), lambda state: state.can_collect('Big Key (Skull Woods)'))
|
set_rule(world.get_location('[dungeon-D3-B1] Skull Woods - Big Chest'), lambda state: state.can_collect('Big Key (Skull Woods)'))
|
||||||
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.can_collect('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and
|
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.can_collect('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
|
||||||
(state.has_blunt_weapon() or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic')))
|
|
||||||
for location in ['[dungeon-D3-B1] Skull Woods - Big Chest']:
|
for location in ['[dungeon-D3-B1] Skull Woods - Big Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Skull Woods)')
|
forbid_item(world.get_location(location), 'Big Key (Skull Woods)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) # ToDo Rework key logic for Ice Palace for non-bombjump routes, will impose more restrictions
|
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos'))
|
||||||
set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.can_collect('Big Key (Ice Palace)'))
|
set_rule(world.get_location('[dungeon-D5-B5] Ice Palace - Big Chest'), lambda state: state.can_collect('Big Key (Ice Palace)'))
|
||||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.can_collect('Big Key (Ice Palace)') and state.can_collect('Small Key (Ice Palace)', 2))
|
||||||
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot')) # as one can be stupid and waste all keys but the guaranteed one on the East Wing, The Small Key Door access can never be required
|
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: state.has('Hookshot') or (state.can_collect('Small Key(Ice Palace)', 1) and ((state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item is not None and state.world.get_location('[dungeon-D5-B3] Ice Palace - Spike Room').item.name in ['Big Key (Ice Palace)']) or
|
||||||
|
(state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item is not None and state.world.get_location('[dungeon-D5-B1] Ice Palace - Big Key Room').item.name in ['Big Key (Ice Palace)']) or
|
||||||
|
(state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item is not None and state.world.get_location('[dungeon-D5-B2] Ice Palace - Map Room').item.name in ['Big Key (Ice Palace)'])))) # if you do ipbj and waste SKs in the basement, you have to BJ over the hookshot room to fix your mess potentially. This seems fair
|
||||||
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
||||||
for location in ['[dungeon-D5-B5] Ice Palace - Big Chest']:
|
for location in ['[dungeon-D5-B5] Ice Palace - Big Chest', 'Kholdstare - Heart Container']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
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'))
|
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: state.has_Boots() or state.has('Hookshot'))
|
||||||
|
@ -201,7 +212,6 @@ def global_rules(world):
|
||||||
'[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]']: # ToDo Big Key can be elsewhere if we have an entrance shuffle
|
'[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]', '[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]']: # ToDo Big Key can be elsewhere if we have an entrance shuffle
|
||||||
forbid_item(world.get_location(location), 'Big Key (Turtle Rock)')
|
forbid_item(world.get_location(location), 'Big Key (Turtle Rock)')
|
||||||
|
|
||||||
# this key logic sucks ToDo
|
|
||||||
set_rule(world.get_entrance('Dark Palace Bonk Wall'), lambda state: state.has('Bow'))
|
set_rule(world.get_entrance('Dark Palace Bonk Wall'), lambda state: state.has('Bow'))
|
||||||
set_rule(world.get_entrance('Dark Palace Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
set_rule(world.get_entrance('Dark Palace Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
||||||
set_rule(world.get_entrance('Dark Palace Bridge Room'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
set_rule(world.get_entrance('Dark Palace Bridge Room'), lambda state: state.can_collect('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
||||||
|
@ -238,8 +248,7 @@ def global_rules(world):
|
||||||
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and (state.has('Tempered Sword') or state.has('Golden Sword') or state.has('Silver Arrows') or state.has('Lamp') or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) # need to light torch a sufficient amount of times
|
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and (state.has('Tempered Sword') or state.has('Golden Sword') or state.has('Silver Arrows') or state.has('Lamp') or state.has('Bottle') or state.has('Half Magic') or state.has('Quarter Magic'))) # need to light torch a sufficient amount of times
|
||||||
|
|
||||||
|
|
||||||
def no_glitches_rules(world, mode):
|
def no_glitches_rules(world):
|
||||||
# overworld requirements
|
|
||||||
set_rule(world.get_entrance('Zoras River'), lambda state: state.has('Flippers') or state.can_lift_rocks())
|
set_rule(world.get_entrance('Zoras River'), lambda state: state.has('Flippers') or state.can_lift_rocks())
|
||||||
set_rule(world.get_entrance('Hobo Bridge'), lambda state: state.has('Flippers'))
|
set_rule(world.get_entrance('Hobo Bridge'), lambda state: state.has('Flippers'))
|
||||||
add_rule(world.get_entrance('Ice Palace'), lambda state: state.has_Pearl() and state.has('Flippers'))
|
add_rule(world.get_entrance('Ice Palace'), lambda state: state.has_Pearl() and state.has('Flippers'))
|
||||||
|
@ -253,7 +262,7 @@ def no_glitches_rules(world, mode):
|
||||||
set_rule(world.get_location('[dungeon-D1-B1] Dark Palace - Dark Room [right chest]'), lambda state: state.has('Lamp'))
|
set_rule(world.get_location('[dungeon-D1-B1] Dark Palace - Dark Room [right chest]'), lambda state: state.has('Lamp'))
|
||||||
add_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hookshot'))
|
add_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hookshot'))
|
||||||
|
|
||||||
if mode == 'open':
|
if world.mode == 'open':
|
||||||
add_rule(world.get_entrance('Aghanim 1'), lambda state: state.has('Lamp'))
|
add_rule(world.get_entrance('Aghanim 1'), lambda state: state.has('Lamp'))
|
||||||
set_rule(world.get_location('Old Mountain Man'), lambda state: state.has('Lamp'))
|
set_rule(world.get_location('Old Mountain Man'), lambda state: state.has('Lamp'))
|
||||||
set_rule(world.get_entrance('Old Man Cave Exit'), lambda state: state.has('Lamp'))
|
set_rule(world.get_entrance('Old Man Cave Exit'), lambda state: state.has('Lamp'))
|
||||||
|
@ -261,8 +270,20 @@ def no_glitches_rules(world, mode):
|
||||||
add_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Lamp'))
|
add_rule(world.get_location('Armos - Heart Container'), lambda state: state.has('Lamp'))
|
||||||
add_rule(world.get_location('Armos - Pendant'), lambda state: state.has('Lamp'))
|
add_rule(world.get_location('Armos - Pendant'), lambda state: state.has('Lamp'))
|
||||||
add_rule(world.get_location('[dungeon-C-B1] Escape - First B1 Room'), lambda state: state.has('Lamp'))
|
add_rule(world.get_location('[dungeon-C-B1] Escape - First B1 Room'), lambda state: state.has('Lamp'))
|
||||||
elif mode == 'standard':
|
|
||||||
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_collect('Small Key (Escape)'))
|
|
||||||
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_collect('Small Key (Escape)'))
|
def open_rules(world):
|
||||||
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_collect('Small Key (Escape)'))
|
pass
|
||||||
add_rule(world.get_location('[dungeon-C-1F] Sanctuary'), lambda state: state.can_collect('Small Key (Escape)'))
|
|
||||||
|
|
||||||
|
def standard_rules(world):
|
||||||
|
# easiest way to enforce key placement not relevant for open
|
||||||
|
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), 'Small Key (Escape)')
|
||||||
|
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), 'Small Key (Escape)')
|
||||||
|
forbid_item(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), 'Small Key (Escape)')
|
||||||
|
forbid_item(world.get_location('[dungeon-C-1F] Sanctuary'), 'Small Key (Escape)')
|
||||||
|
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [left chest]'), lambda state: state.can_reach('Sewer Drop'))
|
||||||
|
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [middle chest]'), lambda state: state.can_reach('Sewer Drop'))
|
||||||
|
add_rule(world.get_location('[dungeon-C-B1] Escape - Final Basement Room [right chest]'), lambda state: state.can_reach('Sewer Drop'))
|
||||||
|
add_rule(world.get_location('[dungeon-C-B1] Escape - First B1 Room'), lambda state: state.can_reach('Sewer Drop') or (state.world.get_location('[dungeon-C-B1] Escape - First B1 Room').item is not None and state.world.get_location('[dungeon-C-B1] Escape - First B1 Room').item.name in ['Small Key (Escape)'])) # you could skip this chest and be unable to go back until you can drop into escape
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue