Temporarily destroy the world, instead of copying it.

Not pretty, but faster. Unfortunately can't be threaded alongside rom/multidata creation, as otherwise locations may be empty at certain times.
This commit is contained in:
Fabian Dill 2021-03-17 10:53:40 +01:00
parent 275ac5be2b
commit 01ef29568a
3 changed files with 21 additions and 23 deletions

View File

@ -141,10 +141,6 @@ class MultiWorld():
#for i in range(players): #for i in range(players):
# self.worlds.append(worlds.alttp.ALTTPWorld({}, i)) # self.worlds.append(worlds.alttp.ALTTPWorld({}, i))
def copy(self) -> MultiWorld:
import copy
return copy.deepcopy(self)
def secure(self): def secure(self):
self.random = secrets.SystemRandom() self.random = secrets.SystemRandom()

34
Main.py
View File

@ -512,12 +512,12 @@ def main(args, seed=None):
raise Exception("Game appears as unbeatable. Aborting.") raise Exception("Game appears as unbeatable. Aborting.")
else: else:
logger.warning("Location Accessibility requirements not fulfilled.") logger.warning("Location Accessibility requirements not fulfilled.")
if not args.skip_playthrough:
logger.info('Calculating playthrough.')
create_playthrough(world)
if multidata_task: if multidata_task:
multidata_task.result() # retrieve exception if one exists multidata_task.result() # retrieve exception if one exists
pool.shutdown() # wait for all queued tasks to complete pool.shutdown() # wait for all queued tasks to complete
if not args.skip_playthrough:
logger.info('Calculating playthrough.')
create_playthrough(world)
if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
@ -673,10 +673,7 @@ def copy_dynamic_regions_and_locations(world, ret):
def create_playthrough(world): def create_playthrough(world):
# create a copy as we will modify it """Destructive to the world it is run on."""
old_world = world
world = world.copy()
# 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] state_cache = [None]
@ -708,9 +705,9 @@ def create_playthrough(world):
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
f'Something went terribly wrong here.') f'Something went terribly wrong here.')
else: else:
old_world.spoiler.unreachables = sphere_candidates.copy() world.spoiler.unreachables = sphere_candidates
break break
restore_later = {}
# 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 num, sphere in reversed(tuple(enumerate(collection_spheres))): for num, sphere in reversed(tuple(enumerate(collection_spheres))):
to_delete = set() to_delete = set()
@ -721,6 +718,7 @@ def create_playthrough(world):
location.item = None location.item = None
if world.can_beat_game(state_cache[num]): if world.can_beat_game(state_cache[num]):
to_delete.add(location) to_delete.add(location)
restore_later[location] = old_item
else: else:
# still required, got to keep it around # still required, got to keep it around
location.item = old_item location.item = old_item
@ -774,19 +772,23 @@ def create_playthrough(world):
pathpairs = zip_longest(pathsiter, pathsiter) pathpairs = zip_longest(pathsiter, pathsiter)
return list(pathpairs) return list(pathpairs)
old_world.spoiler.paths = dict() world.spoiler.paths = dict()
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
if player in world.alttp_player_ids: if player in world.alttp_player_ids:
for path in dict(old_world.spoiler.paths).values(): for path in dict(world.spoiler.paths).values():
if any(exit == 'Pyramid Fairy' for (_, exit) in path): if any(exit == 'Pyramid Fairy' for (_, exit) in path):
if world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player))
else: else:
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
# we can finally output our playthrough # we can finally output our playthrough
old_world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])}
for i, sphere in enumerate(collection_spheres): for i, sphere in enumerate(collection_spheres):
old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)} world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)}
# repair the world again
for location, item in restore_later.items():
location.item = item

View File

@ -228,11 +228,11 @@ def generate_itempool(world, player: int):
raise NotImplementedError(f"Diffulty {world.difficulty[player]}") raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
if world.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt', if world.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt',
'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}: 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}:
raise NotImplementedError(f"Goal {world.goal[player]}") raise NotImplementedError(f"Goal {world.goal[player]} for player {player}")
if world.mode[player] not in {'open', 'standard', 'inverted'}: if world.mode[player] not in {'open', 'standard', 'inverted'}:
raise NotImplementedError(f"Mode {world.mode[player]}") raise NotImplementedError(f"Mode {world.mode[player]} for player {player}")
if world.timer[player] not in {False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'}: if world.timer[player] not in {False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'}:
raise NotImplementedError(f"Timer {world.mode[player]}") raise NotImplementedError(f"Timer {world.mode[player]} for player {player}")
if world.timer[player] in ['ohko', 'timed-ohko']: if world.timer[player] in ['ohko', 'timed-ohko']:
world.can_take_damage[player] = False world.can_take_damage[player] = False