Add a paths section to spoiler log
This section will contain the first path found to each location listed in the spoiler log's walkthrough. Also implemented is a performance enhancement that more than cancels out any slowdown caused by the above code.
This commit is contained in:
parent
dd72bb7581
commit
c3bdef5c6c
|
@ -1,6 +1,7 @@
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
from itertools import zip_longest
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ class World(object):
|
||||||
location.item = item
|
location.item = item
|
||||||
item.location = location
|
item.location = location
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item, location.event)
|
self.state.collect(item, location.event, location)
|
||||||
|
|
||||||
logging.getLogger('').debug('Placed %s at %s', item, location)
|
logging.getLogger('').debug('Placed %s at %s', item, location)
|
||||||
else:
|
else:
|
||||||
|
@ -208,13 +209,17 @@ class World(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def can_beat_game(self, starting_state=None):
|
def can_beat_game(self, starting_state=None):
|
||||||
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event)]
|
|
||||||
|
|
||||||
if starting_state:
|
if starting_state:
|
||||||
state = starting_state.copy()
|
state = starting_state.copy()
|
||||||
else:
|
else:
|
||||||
state = CollectionState(self)
|
state = CollectionState(self)
|
||||||
treasure_pieces_collected = 0
|
|
||||||
|
if self.has_beaten_game(state):
|
||||||
|
return True
|
||||||
|
|
||||||
|
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
||||||
|
|
||||||
|
treasure_pieces_collected = state.item_count('Triforce Piece') + state.item_count('Power Star')
|
||||||
while prog_locations:
|
while prog_locations:
|
||||||
sphere = []
|
sphere = []
|
||||||
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
|
||||||
|
@ -234,7 +239,7 @@ class World(object):
|
||||||
|
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
prog_locations.remove(location)
|
prog_locations.remove(location)
|
||||||
state.collect(location.item, True)
|
state.collect(location.item, True, location)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -265,6 +270,9 @@ class CollectionState(object):
|
||||||
self.entrance_cache = {}
|
self.entrance_cache = {}
|
||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.events = []
|
self.events = []
|
||||||
|
self.path = {}
|
||||||
|
self.locations_checked = set()
|
||||||
|
|
||||||
|
|
||||||
def clear_cached_unreachable(self):
|
def clear_cached_unreachable(self):
|
||||||
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
|
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
|
||||||
|
@ -279,6 +287,8 @@ class CollectionState(object):
|
||||||
ret.location_cache = copy.copy(self.location_cache)
|
ret.location_cache = copy.copy(self.location_cache)
|
||||||
ret.entrance_cache = copy.copy(self.entrance_cache)
|
ret.entrance_cache = copy.copy(self.entrance_cache)
|
||||||
ret.events = copy.copy(self.events)
|
ret.events = copy.copy(self.events)
|
||||||
|
ret.path = copy.copy(self.path)
|
||||||
|
ret.locations_checked = copy.copy(self.locations_checked)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def can_reach(self, spot, resolution_hint=None):
|
def can_reach(self, spot, resolution_hint=None):
|
||||||
|
@ -334,7 +344,7 @@ class CollectionState(object):
|
||||||
for event in reachable_events:
|
for event in reachable_events:
|
||||||
if event.name not in self.events:
|
if event.name not in self.events:
|
||||||
self.events.append(event.name)
|
self.events.append(event.name)
|
||||||
self.collect(event.item, True)
|
self.collect(event.item, True, event)
|
||||||
new_locations = len(reachable_events) > checked_locations
|
new_locations = len(reachable_events) > checked_locations
|
||||||
checked_locations = len(reachable_events)
|
checked_locations = len(reachable_events)
|
||||||
|
|
||||||
|
@ -393,7 +403,9 @@ class CollectionState(object):
|
||||||
def has_turtle_rock_medallion(self):
|
def has_turtle_rock_medallion(self):
|
||||||
return self.has(self.world.required_medallions[1])
|
return self.has(self.world.required_medallions[1])
|
||||||
|
|
||||||
def collect(self, item, event=False):
|
def collect(self, item, event=False, location=None):
|
||||||
|
if location:
|
||||||
|
self.locations_checked.add(location)
|
||||||
changed = False
|
changed = False
|
||||||
if item.name.startswith('Progressive '):
|
if item.name.startswith('Progressive '):
|
||||||
if 'Sword' in item.name:
|
if 'Sword' in item.name:
|
||||||
|
@ -504,6 +516,8 @@ class Region(object):
|
||||||
def can_reach(self, state):
|
def can_reach(self, state):
|
||||||
for entrance in self.entrances:
|
for entrance in self.entrances:
|
||||||
if state.can_reach(entrance):
|
if state.can_reach(entrance):
|
||||||
|
if not self in state.path:
|
||||||
|
state.path[self] = (self.name, state.path.get(entrance, None))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -537,6 +551,8 @@ class Entrance(object):
|
||||||
|
|
||||||
def can_reach(self, state):
|
def can_reach(self, state):
|
||||||
if self.access_rule(state) and state.can_reach(self.parent_region):
|
if self.access_rule(state) and state.can_reach(self.parent_region):
|
||||||
|
if not self in state.path:
|
||||||
|
state.path[self] = (self.name, state.path.get(self.parent_region, (self.parent_region.name, None)))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -666,6 +682,7 @@ class Spoiler(object):
|
||||||
self.medallions = {}
|
self.medallions = {}
|
||||||
self.playthrough = {}
|
self.playthrough = {}
|
||||||
self.locations = {}
|
self.locations = {}
|
||||||
|
self.paths = {}
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
|
|
||||||
def set_entrance(self, entrance, exit, direction):
|
def set_entrance(self, entrance, exit, direction):
|
||||||
|
@ -699,6 +716,7 @@ class Spoiler(object):
|
||||||
out.update(self.locations)
|
out.update(self.locations)
|
||||||
out['medallions'] = self.medallions
|
out['medallions'] = self.medallions
|
||||||
out['playthrough'] = self.playthrough
|
out['playthrough'] = self.playthrough
|
||||||
|
out['paths'] = self.paths
|
||||||
out['meta'] = self.metadata
|
out['meta'] = self.metadata
|
||||||
return json.dumps(out)
|
return json.dumps(out)
|
||||||
|
|
||||||
|
@ -726,3 +744,19 @@ class Spoiler(object):
|
||||||
outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()]))
|
outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()]))
|
||||||
outfile.write('\n\nPlaythrough:\n\n')
|
outfile.write('\n\nPlaythrough:\n\n')
|
||||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||||
|
outfile.write('\n\nPaths:\n\n')
|
||||||
|
|
||||||
|
path_listings = []
|
||||||
|
for location, paths in self.paths.items():
|
||||||
|
path_lines = []
|
||||||
|
pathsiter = iter(paths)
|
||||||
|
pathpairs = zip_longest(pathsiter, pathsiter)
|
||||||
|
for region, exit in pathpairs:
|
||||||
|
if exit is not None:
|
||||||
|
path_lines.append("{} -> {}".format(region, exit))
|
||||||
|
else:
|
||||||
|
path_lines.append(region)
|
||||||
|
path_listings.append("{}\n {}".format(location, "\n => ".join(path_lines)))
|
||||||
|
|
||||||
|
#["%s: \n %s" % (location, "\n => ".join(zip_longest(*[iter(path)]*2))) for location, path in self.paths.items()]
|
||||||
|
outfile.write('\n'.join(path_listings))
|
||||||
|
|
18
Main.py
18
Main.py
|
@ -179,7 +179,7 @@ def create_playthrough(world):
|
||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
||||||
|
state_cache = [None]
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
sphere_candidates = list(prog_locations)
|
sphere_candidates = list(prog_locations)
|
||||||
|
@ -196,12 +196,13 @@ def create_playthrough(world):
|
||||||
|
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
sphere_candidates.remove(location)
|
sphere_candidates.remove(location)
|
||||||
state.collect(location.item, True)
|
state.collect(location.item, True, location)
|
||||||
|
|
||||||
collection_spheres.append(sphere)
|
collection_spheres.append(sphere)
|
||||||
|
|
||||||
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
|
state_cache.append(state.copy())
|
||||||
|
|
||||||
|
logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), len(prog_locations))
|
||||||
if not sphere:
|
if not sphere:
|
||||||
logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
|
logging.getLogger('').debug('The following items could not be reached: %s', ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates])
|
||||||
if not world.check_beatable_only:
|
if not world.check_beatable_only:
|
||||||
|
@ -210,7 +211,7 @@ def create_playthrough(world):
|
||||||
break
|
break
|
||||||
|
|
||||||
# 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
|
# 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):
|
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
||||||
to_delete = []
|
to_delete = []
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
# we remove the item at location and check if game is still beatable
|
# we remove the item at location and check if game is still beatable
|
||||||
|
@ -218,7 +219,7 @@ def create_playthrough(world):
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
location.item = None
|
location.item = None
|
||||||
state.remove(old_item)
|
state.remove(old_item)
|
||||||
if world.can_beat_game():
|
if world.can_beat_game(state_cache[num]):
|
||||||
to_delete.append(location)
|
to_delete.append(location)
|
||||||
else:
|
else:
|
||||||
# still required, got to keep it around
|
# still required, got to keep it around
|
||||||
|
@ -234,5 +235,12 @@ def create_playthrough(world):
|
||||||
# store the required locations for statistical analysis
|
# store the required locations for statistical analysis
|
||||||
old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere]
|
old_world.required_locations = [location.name for sphere in collection_spheres for location in sphere]
|
||||||
|
|
||||||
|
def flist_to_iter(node):
|
||||||
|
while node:
|
||||||
|
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}
|
||||||
|
|
||||||
# we can finally output our playthrough
|
# 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)])
|
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])
|
||||||
|
|
Loading…
Reference in New Issue