From df6ee1a08b65ca38308dd29b5d303cb2570fc3da Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 14 Aug 2020 00:34:41 +0200 Subject: [PATCH] Fill Algorithm optimisations (somewhat minor, but easy pickings) --- BaseClasses.py | 42 +++++++++++++++++++++++++++++------------- Fill.py | 19 +++++++++++-------- Main.py | 12 ++++++++---- Utils.py | 2 +- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7f8e624f..972ae07a 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -313,7 +313,8 @@ class World(object): def push_item(self, location: Location, item: Item, collect: bool = True): if not isinstance(location, Location): - raise RuntimeError('Cannot assign item %s to location %s (player %d).' % (item, location, item.player)) + raise RuntimeError( + 'Cannot assign item %s to invalid location %s (player %d).' % (item, location, item.player)) if location.can_fill(self.state, item, False): location.item = item @@ -322,7 +323,7 @@ class World(object): if collect: self.state.collect(item, location.event, location) - logging.getLogger('').debug('Placed %s at %s', item, location) + logging.debug('Placed %s at %s', item, location) else: raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) @@ -353,8 +354,10 @@ class World(object): return [location for location in self.get_locations() if not location.item] def get_filled_locations(self, player=None) -> list: - return [location for location in self.get_locations() if - (player is None or location.player == player) and location.item is not None] + if player is not None: + return [location for location in self.get_locations() if + location.player == player and location.item is not None] + return [location for location in self.get_locations() if location.item is not None] def get_reachable_locations(self, state=None, player=None) -> list: if state is None: @@ -498,15 +501,13 @@ class CollectionState(object): location.item.player] and location.item.bigkey)) and location.can_reach(self)] for event in reachable_events: - if (event.name, event.player) not in self.events: - self.events.append((event.name, event.player)) + if event not in self.events: + self.events.append(event) self.collect(event.item, True, event) new_locations = len(reachable_events) > checked_locations checked_locations = len(reachable_events) def has(self, item, player: int, count: int = 1): - if count == 1: - return (item, player) in self.prog_items return self.prog_items[item, player] >= count def has_key(self, item, player, count: int = 1): @@ -531,18 +532,33 @@ class CollectionState(object): return self.item_count('Triforce Piece', player) + self.item_count('Power Star', player) >= count def has_crystals(self, count: int, player: int) -> bool: - crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7'] - return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count + found: int = 0 + for itemname, itemplayer in self.prog_items: + if itemplayer == player and itemname.startswith('Crystal '): + found += 1 + if found >= count: + return True + return False - def can_lift_rocks(self, player): + def can_lift_rocks(self, player: int): return self.has('Power Glove', player) or self.has('Titans Mitts', player) def has_bottle(self, player: int) -> bool: - return self.bottle_count(player) > 0 + return self.has_bottles(1, player) def bottle_count(self, player: int) -> int: return len( - [item for (item, itemplayer) in self.prog_items if item.startswith('Bottle') and itemplayer == player]) + tuple(item for (item, itemplayer) in self.prog_items if itemplayer == player and item.startswith('Bottle'))) + + def has_bottles(self, bottles: int, player: int): + """Version of bottle_count that allows fast abort""" + found: int = 0 + for itemname, itemplayer in self.prog_items: + if itemplayer == player and itemname.startswith('Bottle'): + found += 1 + if found >= bottles: + return True + return False def has_hearts(self, player: int, count: int) -> int: # Warning: This only considers items that are marked as advancement items diff --git a/Fill.py b/Fill.py index 87d56cbc..81a8dc13 100644 --- a/Fill.py +++ b/Fill.py @@ -1,4 +1,5 @@ import logging +import typing from BaseClasses import CollectionState @@ -199,10 +200,10 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si # we filled all reachable spots. Maybe the game can be beaten anyway? unplaced_items.insert(0, item_to_place) if world.accessibility[item_to_place.player] != 'none' and world.can_beat_game(): - logging.getLogger('').warning( - 'Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place) + logging.warning( + f'Not all items placed. Game beatable anyway. (Could not place {item_to_place})') continue - raise FillError('No more spots to place %s' % item_to_place) + raise FillError(f'No more spots to place {item_to_place}, locations {locations} are invalid') world.push_item(spot_to_fill, item_to_place, False) locations.remove(spot_to_fill) @@ -297,9 +298,9 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None world.random.shuffle(fill_locations) - fast_fill(world, prioitempool, fill_locations) + prioitempool, fill_locations = fast_fill(world, prioitempool, fill_locations) - fast_fill(world, restitempool, fill_locations) + restitempool, fill_locations = fast_fill(world, restitempool, fill_locations) unplaced = [item.name for item in progitempool + prioitempool + restitempool] unfilled = [location.name for location in fill_locations] @@ -307,9 +308,11 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled) -def fast_fill(world, item_pool, fill_locations): - while item_pool and fill_locations: - world.push_item(fill_locations.pop(), item_pool.pop(), False) +def fast_fill(world, item_pool: typing.List, fill_locations: typing.List) -> typing.Tuple[typing.List, typing.List]: + placing = min(len(item_pool), len(fill_locations)) + for item, location in zip(item_pool, fill_locations): + world.push_item(location, item, False) + return item_pool[placing:], fill_locations[placing:] def flood_items(world): diff --git a/Main.py b/Main.py index b4e1b936..fe62cbb5 100644 --- a/Main.py +++ b/Main.py @@ -444,7 +444,7 @@ def create_playthrough(world): collection_spheres = [] state = CollectionState(world) sphere_candidates = list(prog_locations) - logging.getLogger('').debug('Building up collection spheres.') + logging.debug('Building up collection spheres.') while sphere_candidates: state.sweep_for_events(key_only=True) @@ -462,11 +462,15 @@ def create_playthrough(world): 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)) + logging.debug('Calculated sphere %i, containing %i of %i progress items.', len(collection_spheres), len(sphere), + len(prog_locations)) if not sphere: - logging.getLogger('').debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (location.item.name, location.item.player, location.name, location.player) for location in sphere_candidates]) + logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % ( + location.item.name, location.item.player, location.name, location.player) for location in + sphere_candidates]) if any([world.accessibility[location.item.player] != 'none' for location in sphere_candidates]): - raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.') + raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' + f'Something went terribly wrong here.') else: old_world.spoiler.unreachables = sphere_candidates.copy() break diff --git a/Utils.py b/Utils.py index 15660ba0..ee85fbae 100644 --- a/Utils.py +++ b/Utils.py @@ -6,7 +6,7 @@ def tuplize_version(version: str) -> typing.Tuple[int, ...]: return tuple(int(piece, 10) for piece in version.split(".")) -__version__ = "2.5.0" +__version__ = "2.5.1" _version_tuple = tuplize_version(__version__) import os