Performance optimizations
This commit is contained in:
@@ -434,8 +434,12 @@ class World(object):
|
|||||||
|
|
||||||
def can_beat_game(self, starting_state=None):
|
def can_beat_game(self, starting_state=None):
|
||||||
if starting_state:
|
if starting_state:
|
||||||
|
if self.has_beaten_game(starting_state):
|
||||||
|
return True
|
||||||
state = starting_state.copy()
|
state = starting_state.copy()
|
||||||
else:
|
else:
|
||||||
|
if self.has_beaten_game(self.state):
|
||||||
|
return True
|
||||||
state = CollectionState(self)
|
state = CollectionState(self)
|
||||||
|
|
||||||
if self.has_beaten_game(state):
|
if self.has_beaten_game(state):
|
||||||
|
|||||||
@@ -2120,10 +2120,11 @@ def connect_doors(world, doors, targets, player):
|
|||||||
"""This works inplace"""
|
"""This works inplace"""
|
||||||
random.shuffle(doors)
|
random.shuffle(doors)
|
||||||
random.shuffle(targets)
|
random.shuffle(targets)
|
||||||
while doors:
|
placing = min(len(doors), len(targets))
|
||||||
door = doors.pop()
|
for door, target in zip(doors, targets):
|
||||||
target = targets.pop()
|
|
||||||
connect_entrance(world, door, target, player)
|
connect_entrance(world, door, target, player)
|
||||||
|
doors[:] = doors[placing:]
|
||||||
|
targets[:] = targets[placing:]
|
||||||
|
|
||||||
|
|
||||||
def skull_woods_shuffle(world, player):
|
def skull_woods_shuffle(world, player):
|
||||||
|
|||||||
65
Fill.py
65
Fill.py
@@ -1,4 +1,6 @@
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
|
import collections
|
||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState
|
||||||
@@ -491,9 +493,8 @@ def sell_keys(world, player):
|
|||||||
|
|
||||||
def balance_multiworld_progression(world):
|
def balance_multiworld_progression(world):
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
checked_locations = []
|
checked_locations = set()
|
||||||
unchecked_locations = world.get_locations().copy()
|
unchecked_locations = set(world.get_locations())
|
||||||
random.shuffle(unchecked_locations)
|
|
||||||
|
|
||||||
reachable_locations_count = {}
|
reachable_locations_count = {}
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
@@ -501,7 +502,7 @@ def balance_multiworld_progression(world):
|
|||||||
|
|
||||||
def get_sphere_locations(sphere_state, locations):
|
def get_sphere_locations(sphere_state, locations):
|
||||||
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
sphere_state.sweep_for_events(key_only=True, locations=locations)
|
||||||
return [loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)]
|
return {loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)}
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sphere_locations = get_sphere_locations(state, unchecked_locations)
|
sphere_locations = get_sphere_locations(state, unchecked_locations)
|
||||||
@@ -512,38 +513,42 @@ def balance_multiworld_progression(world):
|
|||||||
if checked_locations:
|
if checked_locations:
|
||||||
threshold = max(reachable_locations_count.values()) - 20
|
threshold = max(reachable_locations_count.values()) - 20
|
||||||
|
|
||||||
balancing_players = [player for player, reachables in reachable_locations_count.items() if reachables < threshold]
|
balancing_players = {player for player, reachables in reachable_locations_count.items() if reachables < threshold}
|
||||||
if balancing_players is not None and len(balancing_players) > 0:
|
if balancing_players:
|
||||||
balancing_state = state.copy()
|
balancing_state = state.copy()
|
||||||
balancing_unchecked_locations = unchecked_locations.copy()
|
balancing_unchecked_locations = unchecked_locations.copy()
|
||||||
balancing_reachables = reachable_locations_count.copy()
|
balancing_reachables = reachable_locations_count.copy()
|
||||||
balancing_sphere = sphere_locations.copy()
|
balancing_sphere = sphere_locations.copy()
|
||||||
candidate_items = []
|
candidate_items = collections.defaultdict(set)
|
||||||
while True:
|
while True:
|
||||||
for location in balancing_sphere:
|
for location in balancing_sphere:
|
||||||
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
||||||
balancing_state.collect(location.item, True, location)
|
balancing_state.collect(location.item, True, location)
|
||||||
if location.item.player in balancing_players and not location.locked:
|
player = location.item.player
|
||||||
candidate_items.append(location)
|
if player in balancing_players and not location.locked and location.player != player:
|
||||||
|
candidate_items[player].add(location)
|
||||||
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
balancing_sphere = get_sphere_locations(balancing_state, balancing_unchecked_locations)
|
||||||
for location in balancing_sphere:
|
for location in balancing_sphere:
|
||||||
balancing_unchecked_locations.remove(location)
|
balancing_unchecked_locations.remove(location)
|
||||||
balancing_reachables[location.player] += 1
|
balancing_reachables[location.player] += 1
|
||||||
if world.has_beaten_game(balancing_state) or all([reachables >= threshold for reachables in balancing_reachables.values()]):
|
if world.has_beaten_game(balancing_state) or all(reachables >= threshold for reachables in balancing_reachables.values()):
|
||||||
break
|
break
|
||||||
elif not balancing_sphere:
|
elif not balancing_sphere:
|
||||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||||
|
|
||||||
unlocked_locations = [l for l in unchecked_locations if l not in balancing_unchecked_locations]
|
unlocked_locations = collections.defaultdict(set)
|
||||||
|
for l in unchecked_locations:
|
||||||
|
if l not in balancing_unchecked_locations:
|
||||||
|
unlocked_locations[l.player].add(l)
|
||||||
items_to_replace = []
|
items_to_replace = []
|
||||||
for player in balancing_players:
|
for player in balancing_players:
|
||||||
locations_to_test = [l for l in unlocked_locations if l.player == player]
|
locations_to_test = unlocked_locations[player]
|
||||||
# only replace items that end up in another player's world
|
items_to_test = candidate_items[player]
|
||||||
items_to_test = [l for l in candidate_items if l.item.player == player and l.player != player]
|
|
||||||
while items_to_test:
|
while items_to_test:
|
||||||
testing = items_to_test.pop()
|
testing = items_to_test.pop()
|
||||||
reducing_state = state.copy()
|
reducing_state = state.copy()
|
||||||
for location in [*[l for l in items_to_replace if l.item.player == player], *items_to_test]:
|
for location in itertools.chain((l for l in items_to_replace if l.item.player == player),
|
||||||
|
items_to_test):
|
||||||
reducing_state.collect(location.item, True, location)
|
reducing_state.collect(location.item, True, location)
|
||||||
|
|
||||||
reducing_state.sweep_for_events(locations=locations_to_test)
|
reducing_state.sweep_for_events(locations=locations_to_test)
|
||||||
@@ -557,33 +562,43 @@ def balance_multiworld_progression(world):
|
|||||||
items_to_replace.append(testing)
|
items_to_replace.append(testing)
|
||||||
|
|
||||||
replaced_items = False
|
replaced_items = False
|
||||||
replacement_locations = [l for l in checked_locations if not l.event and not l.locked]
|
# sort then shuffle to maintain deterministic behaviour,
|
||||||
|
# while allowing use of set for better algorithm growth behaviour elsewhere
|
||||||
|
replacement_locations = sorted(l for l in checked_locations if not l.event and not l.locked)
|
||||||
|
world.random.shuffle(replacement_locations)
|
||||||
|
items_to_replace.sort()
|
||||||
|
world.random.shuffle(items_to_replace)
|
||||||
while replacement_locations and items_to_replace:
|
while replacement_locations and items_to_replace:
|
||||||
new_location = replacement_locations.pop()
|
|
||||||
old_location = items_to_replace.pop()
|
old_location = items_to_replace.pop()
|
||||||
|
for new_location in replacement_locations:
|
||||||
while not new_location.can_fill(state, old_location.item, False) or (new_location.item and not old_location.can_fill(state, new_location.item, False)):
|
if (new_location.can_fill(state, old_location.item, False) and
|
||||||
replacement_locations.insert(0, new_location)
|
old_location.can_fill(state, new_location.item, False)):
|
||||||
new_location = replacement_locations.pop()
|
replacement_locations.remove(new_location)
|
||||||
|
|
||||||
new_location.item, old_location.item = old_location.item, new_location.item
|
new_location.item, old_location.item = old_location.item, new_location.item
|
||||||
if world.shopsanity[new_location.player]:
|
if world.shopsanity[new_location.player]:
|
||||||
check_shop_swap(new_location)
|
check_shop_swap(new_location)
|
||||||
if world.shopsanity[old_location.player]:
|
if world.shopsanity[old_location.player]:
|
||||||
check_shop_swap(old_location)
|
check_shop_swap(old_location)
|
||||||
new_location.event, old_location.event = True, False
|
new_location.event, old_location.event = True, False
|
||||||
|
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
||||||
|
f"displacing {old_location.item} into {old_location}")
|
||||||
state.collect(new_location.item, True, new_location)
|
state.collect(new_location.item, True, new_location)
|
||||||
replaced_items = True
|
replaced_items = True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logging.warning(f"Could not Progression Balance {old_location.item}")
|
||||||
|
|
||||||
if replaced_items:
|
if replaced_items:
|
||||||
for location in get_sphere_locations(state, [l for l in unlocked_locations if l.player in balancing_players]):
|
unlocked = {fresh for player in balancing_players for fresh in unlocked_locations[player]}
|
||||||
|
for location in get_sphere_locations(state, unlocked):
|
||||||
unchecked_locations.remove(location)
|
unchecked_locations.remove(location)
|
||||||
reachable_locations_count[location.player] += 1
|
reachable_locations_count[location.player] += 1
|
||||||
sphere_locations.append(location)
|
sphere_locations.add(location)
|
||||||
|
|
||||||
for location in sphere_locations:
|
for location in sphere_locations:
|
||||||
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
if location.event and (world.keyshuffle[location.item.player] or not location.item.smallkey) and (world.bigkeyshuffle[location.item.player] or not location.item.bigkey):
|
||||||
state.collect(location.item, True, location)
|
state.collect(location.item, True, location)
|
||||||
checked_locations.extend(sphere_locations)
|
checked_locations |= sphere_locations
|
||||||
|
|
||||||
if world.has_beaten_game(state):
|
if world.has_beaten_game(state):
|
||||||
break
|
break
|
||||||
|
|||||||
17
Main.py
17
Main.py
@@ -541,11 +541,11 @@ def create_playthrough(world):
|
|||||||
while sphere_candidates:
|
while sphere_candidates:
|
||||||
state.sweep_for_events(key_only=True)
|
state.sweep_for_events(key_only=True)
|
||||||
|
|
||||||
sphere = []
|
sphere = set()
|
||||||
# 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
|
||||||
for location in sphere_candidates:
|
for location in sphere_candidates:
|
||||||
if state.can_reach(location) and state.not_flooding_a_key(world, location):
|
if state.can_reach(location) and state.not_flooding_a_key(world, location):
|
||||||
sphere.append(location)
|
sphere.add(location)
|
||||||
|
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
sphere_candidates.remove(location)
|
sphere_candidates.remove(location)
|
||||||
@@ -566,7 +566,7 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
# 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(list(enumerate(collection_spheres))):
|
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
||||||
to_delete = []
|
to_delete = set()
|
||||||
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
|
||||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
||||||
@@ -576,15 +576,14 @@ def create_playthrough(world):
|
|||||||
world.clear_exp_cache()
|
world.clear_exp_cache()
|
||||||
if world.can_beat_game(state_cache[num]):
|
if world.can_beat_game(state_cache[num]):
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is not required')
|
||||||
to_delete.append(location)
|
to_delete.add(location)
|
||||||
else:
|
else:
|
||||||
# still required, got to keep it around
|
# still required, got to keep it around
|
||||||
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
# logging.getLogger('').debug(f'{old_item.name} (Player {old_item.player}) is required')
|
||||||
location.item = old_item
|
location.item = old_item
|
||||||
|
|
||||||
# cull entries in spheres for spoiler walkthrough at end
|
# cull entries in spheres for spoiler walkthrough at end
|
||||||
for location in to_delete:
|
sphere -= to_delete
|
||||||
sphere.remove(location)
|
|
||||||
|
|
||||||
# second phase, sphere 0
|
# second phase, sphere 0
|
||||||
for item in [i for i in world.precollected_items if i.advancement]:
|
for item in [i for i in world.precollected_items if i.advancement]:
|
||||||
@@ -600,7 +599,7 @@ def create_playthrough(world):
|
|||||||
# used to access it was deemed not required.) So we need to do one final sphere collection pass
|
# used to access it was deemed not required.) So we need to do one final sphere collection pass
|
||||||
# to build up the correct spheres
|
# to build up the correct spheres
|
||||||
|
|
||||||
required_locations = [item for sphere in collection_spheres for item in sphere]
|
required_locations = {item for sphere in collection_spheres for item in sphere}
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
while required_locations:
|
while required_locations:
|
||||||
@@ -637,7 +636,7 @@ def create_playthrough(world):
|
|||||||
old_world.spoiler.paths = dict()
|
old_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({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
||||||
for _, path in dict(old_world.spoiler.paths).items():
|
for path in dict(old_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))
|
old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player))
|
||||||
@@ -645,6 +644,6 @@ def create_playthrough(world):
|
|||||||
old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player))
|
old_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 = OrderedDict([("0", [str(item) for item in world.precollected_items if item.advancement])])
|
old_world.spoiler.playthrough = {"0": [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)] = {location.gen_name(): str(location.item) for location in sphere}
|
old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere}
|
||||||
|
|||||||
3
Rom.py
3
Rom.py
@@ -102,8 +102,7 @@ class LocalRom(object):
|
|||||||
self.buffer[address] = value
|
self.buffer[address] = value
|
||||||
|
|
||||||
def write_bytes(self, startaddress, values):
|
def write_bytes(self, startaddress, values):
|
||||||
for i, value in enumerate(values):
|
self.buffer[startaddress:startaddress + len(values)] = values
|
||||||
self.write_byte(startaddress + i, value)
|
|
||||||
|
|
||||||
def write_to_file(self, file):
|
def write_to_file(self, file):
|
||||||
with open(file, 'wb') as outfile:
|
with open(file, 'wb') as outfile:
|
||||||
|
|||||||
Reference in New Issue
Block a user