From 4d35265e5c8d936e71bacb097fe01ac6e4d288e3 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 15 Oct 2019 15:15:29 -0600 Subject: [PATCH] Key flood protection in sweep for events Better loop validation Small key validation tweaks --- BaseClasses.py | 12 ++++++++++ DoorShuffle.py | 64 +++++++++++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index c1115837..03f4db77 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -401,6 +401,7 @@ class CollectionState(object): if locations is None: locations = self.world.get_filled_locations() reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)] + reachable_events = self._do_not_flood_the_keys(reachable_events) for event in reachable_events: if (event.name, event.player) not in self.events: self.events.append((event.name, event.player)) @@ -408,6 +409,17 @@ class CollectionState(object): new_locations = len(reachable_events) > checked_locations checked_locations = len(reachable_events) + def _do_not_flood_the_keys(self, reachable_events): + adjusted_checks = list(reachable_events) + for event in reachable_events: + if event.name == 'Trench 2 Switch' and self.world.get_location('Swamp Palace - Trench 2 Pot Key', event.player) not in reachable_events: + adjusted_checks.remove(event) + if event.name == 'Trench 1 Switch' and self.world.get_location('Swamp Palace - Trench 1 Pot Key', event.player) not in reachable_events: + adjusted_checks.remove(event) + if len(adjusted_checks) < len(reachable_events): + return adjusted_checks + return reachable_events + def has(self, item, player, count=1): if count == 1: return (item, player) in self.prog_items diff --git a/DoorShuffle.py b/DoorShuffle.py index f3ccae58..57b8dc7f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -672,6 +672,7 @@ class ExplorationState(object): def copy(self): ret = ExplorationState() + ret.unattached_doors = list(self.unattached_doors) ret.avail_doors = list(self.avail_doors) ret.event_doors = list(self.event_doors) ret.visited_orange = list(self.visited_orange) @@ -734,7 +735,7 @@ class ExplorationState(object): def add_all_doors_check_unattached(self, region, world, player): for door in get_doors(world, region, player): if self.can_traverse(door): - if door.dest is None and door not in self.unattached_doors: + if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors): self.append_door_to_list(door, self.unattached_doors) elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors): self.append_door_to_list(door, self.event_doors) @@ -751,16 +752,18 @@ class ExplorationState(object): if door.name not in self.door_krs.keys(): self.door_krs[door.name] = key_region - def add_all_doors_check_keys(self, region, key_door_proposal, world, player): for door in get_doors(world, region, player): if self.can_traverse(door): - if door in key_door_proposal and not self.in_door_list(door, self.small_doors) and door not in self.opened_doors: - self.append_door_to_list(door, self.small_doors) - elif door.bigKey and not self.big_key_opened and not self.in_door_list(door, self.big_doors): - self.append_door_to_list(door, self.big_doors) - elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors): - self.append_door_to_list(door, self.event_doors) + if door in key_door_proposal and door not in self.opened_doors: + if not self.in_door_list(door, self.small_doors): + self.append_door_to_list(door, self.small_doors) + elif door.bigKey and not self.big_key_opened: + if not self.in_door_list(door, self.big_doors): + self.append_door_to_list(door, self.big_doors) + elif door.req_event is not None and door.req_event not in self.events: + if not self.in_door_list(door, self.event_doors): + self.append_door_to_list(door, self.event_doors) elif not self.in_door_list(door, self.avail_doors): self.append_door_to_list(door, self.avail_doors) @@ -824,6 +827,8 @@ def extend_reachable_state(search_regions, state, world, player): return local_state +# todo: this sometimes generates two independent parts - that could be valid if the entrances are accessible +# todo: prevent crystal barrier dead ends def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names): logger = logging.getLogger('') random.shuffle(available_sectors) @@ -836,18 +841,17 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re entrance_regions.append(world.get_region(region_name, player)) state = extend_reachable_state(entrance_regions, ExplorationState(), world, player) - reachable_doors_new = state.unattached_doors # Loop until all available doors are used - while len(reachable_doors_new) > 0: + while len(state.unattached_doors) > 0: # Pick a random available door to connect - explorable_door = random.choice(reachable_doors_new) + explorable_door = random.choice(state.unattached_doors) door = explorable_door.door sector = find_sector_for_door(door, available_sectors) sector.outstanding_doors.remove(door) # door_connected = False logger.info('Linking %s', door.name) # Find an available region that has a compatible door - reachable_doors = [d.door for d in reachable_doors_new] + reachable_doors = [d.door for d in state.unattached_doors] compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors) while len(compatibles) > 0: connect_sector, connect_door = compatibles.pop() @@ -856,7 +860,7 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re if is_valid(door, connect_door, sector, connect_sector, available_sectors): # Apply connection and add the new region's doors to the available list maybe_connect_two_way(world, door, connect_door, player) - reachable_doors_new.remove(explorable_door) # todo: does this remove it from the state list? + state.unattached_doors.remove(explorable_door) connect_sector.outstanding_doors.remove(connect_door) if sector != connect_sector: # combine if not the same available_sectors.remove(connect_sector) @@ -864,12 +868,12 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re sector.regions.extend(connect_sector.regions) if not door.blocked: connect_region = world.get_entrance(door.dest.name, player).parent_region - state = extend_reachable([connect_region], state, world, player) + state = extend_reachable_state([connect_region], state, world, player) break # skips else block below logger.info(' Not Linking %s to %s', door.name, connect_door.name) if len(compatibles) == 0: # time to try again sector.outstanding_doors.insert(0, door) - if len(reachable_doors_new) <= 1: + if len(state.unattached_doors) <= 1: raise Exception('Rejected last option due to dead end... infinite loop ensues') else: # If there's no available region with a door, use an internal connection @@ -877,11 +881,12 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re while len(compatibles) > 0: connect_door = compatibles.pop() logger.info(' Adding loop via %s', connect_door.name) + connect_sector = find_sector_for_door(connect_door, available_sectors) # Check if valid - if is_loop_valid(door, connect_door, sector, available_sectors): + if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors): maybe_connect_two_way(world, door, connect_door, player) - reachable_doors_new.remove(explorable_door) - reachable_doors_new[:] = [ed for ed in reachable_doors_new if ed.door != connect_door] + state.unattached_doors.remove(explorable_door) + state.unattached_doors[:] = [ed for ed in state.unattached_doors if ed.door != connect_door] connect_sector = find_sector_for_door(connect_door, available_sectors) connect_sector.outstanding_doors.remove(connect_door) if sector != connect_sector: # combine if not the same @@ -892,7 +897,7 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re else: logger.info(' Not Linking %s to %s', door.name, connect_door.name) sector.outstanding_doors.insert(0, door) - if len(reachable_doors_new) <= 2: + if len(state.unattached_doors) <= 2: raise Exception('Rejected last option due to likely improper loops...') else: raise Exception('Nothing is apparently compatible with %s', door.name) @@ -953,8 +958,9 @@ def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region while len(compatibles) > 0: connect_door = compatibles.pop() logger.info(' Adding loop via %s', connect_door.name) + connect_sector = find_sector_for_door(connect_door, available_sectors) # Check if valid - if is_loop_valid(door, connect_door, sector, available_sectors): + if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors): maybe_connect_two_way(world, door, connect_door, player) reachable_doors.remove(door) reachable_doors.remove(connect_door) @@ -1051,16 +1057,20 @@ def is_valid(door_a, door_b, sector_a, sector_b, available_sectors): return False # not sure how we got here, but it's a bad idea -def is_loop_valid(door_a, door_b, sector, available_sectors): +def is_loop_valid(door_a, door_b, sector_a, sector_b, available_sectors): if len(available_sectors) == 1: return True - if len(available_sectors) == 2 and door_b not in sector.outstanding_doors: + if len(available_sectors) == 2 and door_b not in sector_a.outstanding_doors: return True - elif not door_a.blocked and not door_b.blocked: - return sector.outflow() - 1 > 0 - elif door_a.blocked or door_b.blocked: - return sector.outflow() > 0 - return True # I think this is always true. Both blocked but we're connecting loops now, so dead end? + elif not door_a.blocked and not door_b.blocked and sector_a != sector_b: + return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one + elif door_a.blocked or door_b.blocked and sector_a != sector_b: + return sector_a.outflow() + sector_b.outflow() > 0 + elif sector_a == sector_b and not door_a.blocked and not door_b.blocked: + return sector_a.outflow() - 1 > 0 + elif sector_a == sector_b and door_a.blocked or door_b.blocked: + return sector_a.outflow() > 0 + return True # I think this is always true. Both blocked but we're connecting loops now, so dead end? def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):