Significant performance upgrades in convoluted seeds.

This commit is contained in:
LLCoolDave 2017-05-26 09:55:49 +02:00
parent 2f10495248
commit 5207ccd8fd
1 changed files with 53 additions and 35 deletions

View File

@ -105,7 +105,12 @@ class World(object):
temp_state = self.state.copy() temp_state = self.state.copy()
temp_state._clear_cache() temp_state._clear_cache()
temp_state.collect(item) temp_state.collect(item)
return len(self.get_placeable_locations()) < len(self.get_placeable_locations(temp_state))
for location in self.get_unfilled_locations():
if temp_state.can_reach(location) and not self.state.can_reach(location):
return True
return False
def can_beat_game(self): def can_beat_game(self):
prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement] prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement]
@ -137,44 +142,32 @@ class CollectionState(object):
self.prog_items = [] self.prog_items = []
self.world = parent self.world = parent
self.has_everything = has_everything self.has_everything = has_everything
self.changed = False
self.region_cache = {} self.region_cache = {}
self.location_cache = {} self.location_cache = {}
self.entrance_cache = {} self.entrance_cache = {}
# use to avoid cycle dependencies during resolution self.recursion_count = 0
self.recursion_cache = []
def _clear_cache(self): def _clear_cache(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
self.region_cache = {k: v for k, v in self.region_cache.items() if v} self.region_cache = {k: v for k, v in self.region_cache.items() if v}
self.location_cache = {k: v for k, v in self.location_cache.items() if v} self.location_cache = {k: v for k, v in self.location_cache.items() if v}
self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if v} self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if v}
self.recursion_cache = []
self.changed = False
def copy(self): def copy(self):
ret = CollectionState(self.world, self.has_everything) ret = CollectionState(self.world, self.has_everything)
ret.prog_items = copy.copy(self.prog_items) ret.prog_items = copy.copy(self.prog_items)
ret.changed = self.changed
ret.region_cache = copy.copy(self.region_cache) ret.region_cache = copy.copy(self.region_cache)
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.recursion_cache = copy.copy(self.recursion_cache)
return ret return ret
def can_reach(self, spot, resolution_hint=None): def can_reach(self, spot, resolution_hint=None):
if self.changed:
self._clear_cache()
if spot in self.recursion_cache:
return False
try: try:
spot_type = spot.spot_type spot_type = spot.spot_type
if spot_type == 'Region': if spot_type == 'Location':
correct_cache = self.region_cache
elif spot_type == 'Location':
correct_cache = self.location_cache correct_cache = self.location_cache
elif spot_type == 'Region':
correct_cache = self.region_cache
elif spot_type == 'Entrance': elif spot_type == 'Entrance':
correct_cache = self.entrance_cache correct_cache = self.entrance_cache
else: else:
@ -192,15 +185,20 @@ class CollectionState(object):
spot = self.world.get_region(spot) spot = self.world.get_region(spot)
correct_cache = self.region_cache correct_cache = self.region_cache
if spot.recursion_count > 0:
return False
if spot not in correct_cache: if spot not in correct_cache:
# for the purpose of evaluating results, recursion is resolved by always denying recursive access (as that ia what we are trying to figure out right now in the first place # for the purpose of evaluating results, recursion is resolved by always denying recursive access (as that ia what we are trying to figure out right now in the first place
self.recursion_cache.append(spot) spot.recursion_count += 1
self.recursion_count += 1
can_reach = spot.can_reach(self) can_reach = spot.can_reach(self)
self.recursion_cache.pop() spot.recursion_count -= 1
self.recursion_count -= 1
# we only store qualified false results (i.e. ones not inside a hypothetical) # we only store qualified false results (i.e. ones not inside a hypothetical)
if not can_reach: if not can_reach:
if not self.recursion_cache: if self.recursion_count == 0:
correct_cache[spot] = can_reach correct_cache[spot] = can_reach
else: else:
correct_cache[spot] = can_reach correct_cache[spot] = can_reach
@ -221,10 +219,25 @@ class CollectionState(object):
self.world._item_cache[item] = cached self.world._item_cache[item] = cached
else: else:
# this should probably not happen, wonky item distribution? # this should probably not happen, wonky item distribution?
return len([location for location in candidates if self.can_reach(location)]) >= 1 return self._can_reach_n(self.world.find_items(item), 1)
return self.can_reach(cached) return self.can_reach(cached)
return len([location for location in self.world.find_items(item) if self.can_reach(location)]) >= count return self._can_reach_n(self.world.find_items(item), count)
def _can_reach_n(self, candidates, count):
maxfail = len(candidates) - count
fail = 0
success = 0
for candidate in candidates:
if self.can_reach(candidate):
success += 1
else:
fail += 1
if fail > maxfail:
return False
if success >= count:
return True
return False
def has(self, item): def has(self, item):
if self.has_everything: if self.has_everything:
@ -266,36 +279,39 @@ class CollectionState(object):
return self.has(self.world.required_medallions[1]) return self.has(self.world.required_medallions[1])
def collect(self, item): def collect(self, item):
changed = False
if item.name.startswith('Progressive '): if item.name.startswith('Progressive '):
if 'Sword' in item.name: if 'Sword' in item.name:
if self.has('Golden Sword'): if self.has('Golden Sword'):
return pass
elif self.has('Tempered Sword'): elif self.has('Tempered Sword'):
self.prog_items.append('Golden Sword') self.prog_items.append('Golden Sword')
self.changed = True changed = True
elif self.has('Master Sword'): elif self.has('Master Sword'):
self.prog_items.append('Tempered Sword') self.prog_items.append('Tempered Sword')
self.changed = True changed = True
elif self.has('Fighter Sword'): elif self.has('Fighter Sword'):
self.prog_items.append('Master Sword') self.prog_items.append('Master Sword')
self.changed = True changed = True
else: else:
self.prog_items.append('Fighter Sword') self.prog_items.append('Fighter Sword')
self.changed = True changed = True
elif 'Glove' in item.name: elif 'Glove' in item.name:
if self.has('Titans Mitts'): if self.has('Titans Mitts'):
return pass
elif self.has('Power Glove'): elif self.has('Power Glove'):
self.prog_items.append('Titans Mitts') self.prog_items.append('Titans Mitts')
self.changed = True changed = True
else: else:
self.prog_items.append('Power Glove') self.prog_items.append('Power Glove')
self.changed = True changed = True
return
if item.advancement: elif item.advancement:
self.prog_items.append(item.name) self.prog_items.append(item.name)
self.changed = True changed = True
if changed:
self._clear_cache()
def remove(self, item): def remove(self, item):
if item.advancement: if item.advancement:
@ -330,8 +346,7 @@ class CollectionState(object):
self.region_cache = {} self.region_cache = {}
self.location_cache = {} self.location_cache = {}
self.entrance_cache = {} self.entrance_cache = {}
self.recursion_cache = [] self.recursion_count = 0
self.changed = False
def __getattr__(self, item): def __getattr__(self, item):
if item.startswith('can_reach_'): if item.startswith('can_reach_'):
@ -351,6 +366,7 @@ class Region(object):
self.locations = [] self.locations = []
self.spot_type = 'Region' self.spot_type = 'Region'
self.hint_text = 'Hyrule' self.hint_text = 'Hyrule'
self.recursion_count = 0
def can_reach(self, state): def can_reach(self, state):
for entrance in self.entrances: for entrance in self.entrances:
@ -373,6 +389,7 @@ class Entrance(object):
self.connected_region = None self.connected_region = None
self.target = None self.target = None
self.spot_type = 'Entrance' self.spot_type = 'Entrance'
self.recursion_count = 0
def access_rule(self, state): def access_rule(self, state):
return True return True
@ -405,6 +422,7 @@ class Location(object):
self.address = address self.address = address
self.spot_type = 'Location' self.spot_type = 'Location'
self.hint_text = hint_text if hint_text is not None else 'Hyrule' self.hint_text = hint_text if hint_text is not None else 'Hyrule'
self.recursion_count = 0
def access_rule(self, state): def access_rule(self, state):
return True return True