Fill Algorithm optimisations (somewhat minor, but easy pickings)
This commit is contained in:
		
							parent
							
								
									2a2452e30f
								
							
						
					
					
						commit
						df6ee1a08b
					
				| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								Fill.py
								
								
								
								
							
							
						
						
									
										19
									
								
								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):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								Main.py
								
								
								
								
							
							
						
						
									
										12
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue