Unification of exploration logic
Crystal stuff for PoD/Hera Minor fixes to key logic
This commit is contained in:
@@ -1027,6 +1027,7 @@ class Location(object):
|
|||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
self.forced_item = ItemFactory([forced_item], player)[0]
|
self.forced_item = ItemFactory([forced_item], player)[0]
|
||||||
self.item = self.forced_item
|
self.item = self.forced_item
|
||||||
|
self.item.location = self
|
||||||
self.event = True
|
self.event = True
|
||||||
else:
|
else:
|
||||||
self.forced_item = None
|
self.forced_item = None
|
||||||
|
|||||||
434
DoorShuffle.py
434
DoorShuffle.py
@@ -628,6 +628,279 @@ def find_proposal(proposal, buckets, candidates):
|
|||||||
|
|
||||||
|
|
||||||
# code below is for an algorithm without restarts
|
# code below is for an algorithm without restarts
|
||||||
|
class ExplorableDoor(object):
|
||||||
|
|
||||||
|
def __init__(self, door, crystal):
|
||||||
|
self.door = door
|
||||||
|
self.crystal = crystal
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.__unicode__())
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return '%s (%s)' % (self.door.name, self.crystal.name)
|
||||||
|
|
||||||
|
|
||||||
|
class ExplorationState(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.unattached_doors = []
|
||||||
|
self.avail_doors = []
|
||||||
|
self.event_doors = []
|
||||||
|
|
||||||
|
self.visited_orange = []
|
||||||
|
self.visited_blue = []
|
||||||
|
self.events = set()
|
||||||
|
self.crystal = CrystalBarrier.Orange
|
||||||
|
|
||||||
|
# key region stuff
|
||||||
|
self.door_krs = {}
|
||||||
|
|
||||||
|
# key validation stuff
|
||||||
|
self.small_doors = []
|
||||||
|
self.big_doors = []
|
||||||
|
self.opened_doors = []
|
||||||
|
self.big_key_opened = False
|
||||||
|
self.big_key_special = False
|
||||||
|
|
||||||
|
self.found_locations = []
|
||||||
|
self.ttl_locations = 0
|
||||||
|
self.used_locations = 0
|
||||||
|
self.key_locations = 0
|
||||||
|
self.used_smalls = 0
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
ret = ExplorationState()
|
||||||
|
ret.avail_doors = list(self.avail_doors)
|
||||||
|
ret.event_doors = list(self.event_doors)
|
||||||
|
ret.visited_orange = list(self.visited_orange)
|
||||||
|
ret.visited_blue = list(self.visited_blue)
|
||||||
|
ret.events = set(self.events)
|
||||||
|
ret.crystal = self.crystal
|
||||||
|
ret.door_krs = self.door_krs.copy()
|
||||||
|
|
||||||
|
ret.small_doors = list(self.small_doors)
|
||||||
|
ret.big_doors = list(self.big_doors)
|
||||||
|
ret.opened_doors = list(self.opened_doors)
|
||||||
|
ret.big_key_opened = self.big_key_opened
|
||||||
|
ret.big_key_special = self.big_key_special
|
||||||
|
ret.ttl_locations = self.ttl_locations
|
||||||
|
ret.key_locations = self.key_locations
|
||||||
|
ret.used_locations = self.used_locations
|
||||||
|
ret.used_smalls = self.used_smalls
|
||||||
|
ret.found_locations = list(self.found_locations)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def next_avail_door(self):
|
||||||
|
exp_door = self.avail_doors.pop()
|
||||||
|
self.crystal = exp_door.crystal
|
||||||
|
return exp_door
|
||||||
|
|
||||||
|
def visit_region(self, region, key_region=None, key_checks=False):
|
||||||
|
if self.crystal == CrystalBarrier.Either:
|
||||||
|
if region not in self.visited_blue:
|
||||||
|
self.visited_blue.append(region)
|
||||||
|
if region not in self.visited_orange:
|
||||||
|
self.visited_orange.append(region)
|
||||||
|
elif self.crystal == CrystalBarrier.Orange:
|
||||||
|
self.visited_orange.append(region)
|
||||||
|
elif self.crystal == CrystalBarrier.Blue:
|
||||||
|
self.visited_blue.append(region)
|
||||||
|
for location in region.locations:
|
||||||
|
if location.name in dungeon_events and location.name not in self.events:
|
||||||
|
self.events.add(location.name)
|
||||||
|
queue = collections.deque(self.event_doors)
|
||||||
|
while len(queue) > 0:
|
||||||
|
exp_door = queue.pop()
|
||||||
|
if exp_door.door.req_event == location.name:
|
||||||
|
self.avail_doors.append(exp_door)
|
||||||
|
self.event_doors.remove(exp_door)
|
||||||
|
if key_region is not None:
|
||||||
|
d_name = exp_door.door.name
|
||||||
|
if d_name not in self.door_krs.keys():
|
||||||
|
self.door_krs[d_name] = key_region
|
||||||
|
if key_checks and location not in self.found_locations:
|
||||||
|
if location.name in key_only_locations:
|
||||||
|
self.key_locations += 1
|
||||||
|
if location.name not in dungeon_events and '- Prize' not in location.name:
|
||||||
|
self.ttl_locations += 1
|
||||||
|
self.found_locations.append(location)
|
||||||
|
if key_checks and region.name == 'Hyrule Dungeon Cellblock' and not self.big_key_opened:
|
||||||
|
self.big_key_opened = True
|
||||||
|
self.avail_doors.extend(self.big_doors)
|
||||||
|
self.big_doors.clear()
|
||||||
|
|
||||||
|
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:
|
||||||
|
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)
|
||||||
|
elif not self.in_door_list(door, self.avail_doors):
|
||||||
|
self.append_door_to_list(door, self.avail_doors)
|
||||||
|
|
||||||
|
def add_all_doors_check_key_region(self, region, key_region, world, player):
|
||||||
|
for door in get_doors(world, region, player):
|
||||||
|
if self.can_traverse(door):
|
||||||
|
if 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)
|
||||||
|
elif not self.in_door_list(door, self.avail_doors):
|
||||||
|
self.append_door_to_list(door, self.avail_doors)
|
||||||
|
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)
|
||||||
|
elif not self.in_door_list(door, self.avail_doors):
|
||||||
|
self.append_door_to_list(door, self.avail_doors)
|
||||||
|
|
||||||
|
def visited(self, region):
|
||||||
|
if self.crystal == CrystalBarrier.Either:
|
||||||
|
return region in self.visited_blue and region in self.visited_orange
|
||||||
|
elif self.crystal == CrystalBarrier.Orange:
|
||||||
|
return region in self.visited_orange
|
||||||
|
elif self.crystal == CrystalBarrier.Blue:
|
||||||
|
return region in self.visited_blue
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_traverse(self, door):
|
||||||
|
if door.blocked:
|
||||||
|
return False
|
||||||
|
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
|
||||||
|
return self.crystal == CrystalBarrier.Either or door.crystal == self.crystal
|
||||||
|
return True
|
||||||
|
|
||||||
|
def in_door_list(self, door, door_list):
|
||||||
|
for d in door_list:
|
||||||
|
if d.door == door and d.crystal == self.crystal:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def in_door_list_ic(door, door_list):
|
||||||
|
for d in door_list:
|
||||||
|
if d.door == door:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def append_door_to_list(self, door, door_list):
|
||||||
|
if door.crystal == CrystalBarrier.Null:
|
||||||
|
door_list.append(ExplorableDoor(door, self.crystal))
|
||||||
|
else:
|
||||||
|
door_list.append(ExplorableDoor(door, door.crystal))
|
||||||
|
|
||||||
|
def key_door_sort(self, d):
|
||||||
|
if d.door.smallKey:
|
||||||
|
if d.door in self.opened_doors:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def extend_reachable_state(search_regions, state, world, player):
|
||||||
|
local_state = state.copy()
|
||||||
|
for region in search_regions:
|
||||||
|
local_state.visit_region(region)
|
||||||
|
local_state.add_all_doors_check_unattached(region, world, player)
|
||||||
|
while len(local_state.avail_doors) > 0:
|
||||||
|
explorable_door = local_state.next_avail_door()
|
||||||
|
entrance = world.get_entrance(explorable_door.door.name, player)
|
||||||
|
connect_region = entrance.connected_region
|
||||||
|
if connect_region is not None:
|
||||||
|
if not local_state.visited(connect_region):
|
||||||
|
local_state.visit_region(connect_region)
|
||||||
|
local_state.add_all_doors_check_unattached(connect_region, world, player)
|
||||||
|
return local_state
|
||||||
|
|
||||||
|
|
||||||
|
def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names):
|
||||||
|
logger = logging.getLogger('')
|
||||||
|
random.shuffle(available_sectors)
|
||||||
|
for sector in available_sectors:
|
||||||
|
random.shuffle(sector.outstanding_doors)
|
||||||
|
|
||||||
|
entrance_regions = []
|
||||||
|
# current_sector = None
|
||||||
|
for region_name in entrance_region_names:
|
||||||
|
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:
|
||||||
|
# Pick a random available door to connect
|
||||||
|
explorable_door = random.choice(reachable_doors_new)
|
||||||
|
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]
|
||||||
|
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
|
||||||
|
while len(compatibles) > 0:
|
||||||
|
connect_sector, connect_door = compatibles.pop()
|
||||||
|
logger.info(' Found possible new sector via %s', connect_door.name)
|
||||||
|
# Check if valid
|
||||||
|
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?
|
||||||
|
connect_sector.outstanding_doors.remove(connect_door)
|
||||||
|
if sector != connect_sector: # combine if not the same
|
||||||
|
available_sectors.remove(connect_sector)
|
||||||
|
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||||
|
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)
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
compatibles = find_all_compatible_door_in_list(door, reachable_doors)
|
||||||
|
while len(compatibles) > 0:
|
||||||
|
connect_door = compatibles.pop()
|
||||||
|
logger.info(' Adding loop via %s', connect_door.name)
|
||||||
|
# Check if valid
|
||||||
|
if is_loop_valid(door, connect_door, 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]
|
||||||
|
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
|
||||||
|
available_sectors.remove(connect_sector)
|
||||||
|
sector.outstanding_doors.extend(connect_sector.outstanding_doors)
|
||||||
|
sector.regions.extend(connect_sector.regions)
|
||||||
|
break # skips else block with exception
|
||||||
|
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:
|
||||||
|
raise Exception('Rejected last option due to likely improper loops...')
|
||||||
|
else:
|
||||||
|
raise Exception('Nothing is apparently compatible with %s', door.name)
|
||||||
|
# Check that we used everything, we failed otherwise
|
||||||
|
if len(available_sectors) != 1:
|
||||||
|
logger.warning('Failed to add all regions/doors to dungeon, generation will likely fail.')
|
||||||
|
return available_sectors[0]
|
||||||
|
|
||||||
|
|
||||||
def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region_names):
|
def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region_names):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
@@ -956,107 +1229,30 @@ def ncr(n, r):
|
|||||||
return numerator / denominator
|
return numerator / denominator
|
||||||
|
|
||||||
|
|
||||||
class KeyDoorState(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.avail_doors = []
|
|
||||||
self.small_doors = []
|
|
||||||
self.big_doors = []
|
|
||||||
self.event_doors = []
|
|
||||||
self.opened_doors = []
|
|
||||||
self.big_key_opened = False
|
|
||||||
self.big_key_special = False
|
|
||||||
|
|
||||||
self.visited_regions = set()
|
|
||||||
self.ttl_locations = 0
|
|
||||||
self.used_locations = 0
|
|
||||||
self.key_locations = 0
|
|
||||||
self.used_smalls = 0
|
|
||||||
self.events = set()
|
|
||||||
self.crystal = CrystalBarrier.Orange
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
ret = KeyDoorState()
|
|
||||||
ret.avail_doors = list(self.avail_doors)
|
|
||||||
ret.small_doors = list(self.small_doors)
|
|
||||||
ret.big_doors = list(self.big_doors)
|
|
||||||
ret.opened_doors = list(self.opened_doors)
|
|
||||||
ret.big_key_opened = self.big_key_opened
|
|
||||||
ret.big_key_special = self.big_key_special
|
|
||||||
ret.visited_regions = set(self.visited_regions)
|
|
||||||
ret.ttl_locations = self.ttl_locations
|
|
||||||
ret.key_locations = self.key_locations
|
|
||||||
ret.used_locations = self.used_locations
|
|
||||||
ret.used_smalls = self.used_smalls
|
|
||||||
ret.events = set(self.events)
|
|
||||||
ret.crystal = self.crystal
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def validate_key_layout(sector, start_regions, key_door_proposal, world, player):
|
def validate_key_layout(sector, start_regions, key_door_proposal, world, player):
|
||||||
flat_proposal = flatten_pair_list(key_door_proposal)
|
flat_proposal = flatten_pair_list(key_door_proposal)
|
||||||
state = KeyDoorState()
|
state = ExplorationState()
|
||||||
state.key_locations = len(world.get_dungeon(sector.name, player).small_keys)
|
state.key_locations = len(world.get_dungeon(sector.name, player).small_keys)
|
||||||
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in sector.regions
|
state.big_key_special = world.get_region('Hyrule Dungeon Cellblock', player) in sector.regions
|
||||||
# Everything in a start region is in key region 0.
|
# Everything in a start region is in key region 0.
|
||||||
for region in start_regions:
|
for region in start_regions:
|
||||||
visit_region(state, region)
|
state.visit_region(region, key_checks=True)
|
||||||
count_locations(state, region)
|
state.add_all_doors_check_keys(region, flat_proposal, world, player)
|
||||||
add_doors_to_lists(region, flat_proposal, state, state.crystal, world, player)
|
|
||||||
checked_states = set()
|
checked_states = set()
|
||||||
return validate_key_layout_r(state, flat_proposal, checked_states, world, player)
|
return validate_key_layout_r(state, flat_proposal, checked_states, world, player)
|
||||||
|
|
||||||
|
|
||||||
def visit_region(state, region):
|
|
||||||
if state.crystal == CrystalBarrier.Either:
|
|
||||||
state.visited_regions.add((region, CrystalBarrier.Orange))
|
|
||||||
state.visited_regions.add((region, CrystalBarrier.Blue))
|
|
||||||
else:
|
|
||||||
state.visited_regions.add((region, state.crystal))
|
|
||||||
for location in region.locations:
|
|
||||||
if location.name in dungeon_events and location.name not in state.events:
|
|
||||||
state.events.add(location.name)
|
|
||||||
queue = collections.deque(state.event_doors)
|
|
||||||
while len(queue) > 0:
|
|
||||||
door, crystal = queue.pop()
|
|
||||||
if door.req_event == location.name:
|
|
||||||
state.avail_doors.append((door, crystal))
|
|
||||||
state.event_doors.remove((door, crystal))
|
|
||||||
if region.name == 'Hyrule Dungeon Cellblock' and not state.big_key_opened:
|
|
||||||
state.big_key_opened = True
|
|
||||||
state.avail_doors.extend(state.big_doors)
|
|
||||||
state.big_doors.clear()
|
|
||||||
|
|
||||||
|
|
||||||
def count_locations(state, region):
|
|
||||||
for location in region.locations:
|
|
||||||
if location.name in key_only_locations:
|
|
||||||
state.key_locations += 1
|
|
||||||
if location.name not in dungeon_events and '- Prize' not in location.name:
|
|
||||||
state.ttl_locations += 1
|
|
||||||
|
|
||||||
|
|
||||||
def is_region_visited(state, region):
|
|
||||||
if state.crystal == CrystalBarrier.Either:
|
|
||||||
return (region, CrystalBarrier.Blue) in state.visited_regions and (region, CrystalBarrier.Orange) in state.visited_regions
|
|
||||||
else:
|
|
||||||
return (region, state.crystal) in state.visited_regions
|
|
||||||
|
|
||||||
|
|
||||||
def validate_key_layout_r(state, flat_proposal, checked_states, world, player):
|
def validate_key_layout_r(state, flat_proposal, checked_states, world, player):
|
||||||
|
|
||||||
# improvements: remove recursion to make this iterative
|
# improvements: remove recursion to make this iterative
|
||||||
# store a cache of various states of opened door to increase speed of checks - many are repetitive
|
# store a cache of various states of opened door to increase speed of checks - many are repetitive
|
||||||
while len(state.avail_doors) > 0:
|
while len(state.avail_doors) > 0:
|
||||||
door, crystal_state = state.avail_doors.pop()
|
exp_door = state.next_avail_door()
|
||||||
|
door = exp_door.door
|
||||||
connect_region = world.get_entrance(door.name, player).connected_region
|
connect_region = world.get_entrance(door.name, player).connected_region
|
||||||
if can_traverse(crystal_state, door) and not is_region_visited(state, connect_region):
|
if state.can_traverse(door) and not state.visited(connect_region):
|
||||||
visit_region(state, connect_region)
|
state.visit_region(connect_region, key_checks=True)
|
||||||
count_locations(state, connect_region)
|
state.add_all_doors_check_keys(connect_region, flat_proposal, world, player)
|
||||||
if door.crystal != CrystalBarrier.Null:
|
|
||||||
crystal_state = door.crystal
|
|
||||||
add_doors_to_lists(connect_region, flat_proposal, state, crystal_state, world, player)
|
|
||||||
smalls_avail = len(state.small_doors) > 0
|
smalls_avail = len(state.small_doors) > 0
|
||||||
num_bigs = 1 if len(state.big_doors) > 0 else 0 # all or nothing
|
num_bigs = 1 if len(state.big_doors) > 0 else 0 # all or nothing
|
||||||
if not smalls_avail and num_bigs == 0:
|
if not smalls_avail and num_bigs == 0:
|
||||||
@@ -1068,16 +1264,18 @@ def validate_key_layout_r(state, flat_proposal, checked_states, world, player):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if smalls_avail and available_small_locations > 0:
|
if smalls_avail and available_small_locations > 0:
|
||||||
for d, crystal_sd in state.small_doors:
|
for exp_door in state.small_doors:
|
||||||
state_copy = state.copy()
|
state_copy = state.copy()
|
||||||
state_copy.opened_doors.append(d)
|
state_copy.opened_doors.append(exp_door.door)
|
||||||
state_copy.avail_doors.append((d, crystal_sd))
|
state_copy.avail_doors.append(exp_door)
|
||||||
state_copy.small_doors.remove((d, crystal_sd))
|
state_copy.small_doors.remove(exp_door)
|
||||||
if d.dest in flat_proposal:
|
dest_door = exp_door.door.dest
|
||||||
state_copy.opened_doors.append(d.dest)
|
if dest_door in flat_proposal:
|
||||||
if in_door_list(d.dest, state_copy.small_doors):
|
state_copy.opened_doors.append(dest_door)
|
||||||
state_copy.small_doors[:] = [x for x in state_copy.small_doors if x[0] != d.dest]
|
if state_copy.in_door_list_ic(dest_door, state_copy.small_doors):
|
||||||
state_copy.avail_doors.append((d.dest, crystal_sd))
|
now_available = [x for x in state_copy.small_doors if x.door == dest_door]
|
||||||
|
state_copy.small_doors[:] = [x for x in state_copy.small_doors if x.door != dest_door]
|
||||||
|
state_copy.avail_doors.extend(now_available)
|
||||||
state_copy.used_locations += 1
|
state_copy.used_locations += 1
|
||||||
state_copy.used_smalls += 1
|
state_copy.used_smalls += 1
|
||||||
code = state_id(state_copy, flat_proposal)
|
code = state_id(state_copy, flat_proposal)
|
||||||
@@ -1101,52 +1299,6 @@ def validate_key_layout_r(state, flat_proposal, checked_states, world, player):
|
|||||||
return valid
|
return valid
|
||||||
|
|
||||||
|
|
||||||
def can_traverse(crystal, door):
|
|
||||||
if door.blocked:
|
|
||||||
return False
|
|
||||||
if door.crystal not in [CrystalBarrier.Null, CrystalBarrier.Either]:
|
|
||||||
return crystal == CrystalBarrier.Either or door.crystal == crystal
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def add_doors_to_lists(region, key_door_proposal, state, crystal, world, player):
|
|
||||||
for door in get_doors(world, region, player):
|
|
||||||
if can_traverse(crystal, door):
|
|
||||||
if door in key_door_proposal and not_in_door_list(door, state.small_doors, crystal) and door not in state.opened_doors:
|
|
||||||
append_door_to_list(door, state.small_doors, crystal)
|
|
||||||
elif door.bigKey and not state.big_key_opened and not_in_door_list(door, state.big_doors, crystal):
|
|
||||||
append_door_to_list(door, state.big_doors, crystal)
|
|
||||||
elif door.req_event is not None and door.req_event not in state.events and not_in_door_list(door, state.event_doors, crystal):
|
|
||||||
append_door_to_list(door, state.event_doors, crystal)
|
|
||||||
elif not_in_door_list(door, state.avail_doors, crystal):
|
|
||||||
append_door_to_list(door, state.avail_doors, crystal)
|
|
||||||
|
|
||||||
|
|
||||||
def in_door_list(door, door_list):
|
|
||||||
for d, crystal in door_list:
|
|
||||||
if d == door:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def not_in_door_list(door, door_list, crystal):
|
|
||||||
if crystal == CrystalBarrier.Either:
|
|
||||||
return (door, CrystalBarrier.Orange) not in door_list or (door, CrystalBarrier.Blue) not in door_list
|
|
||||||
else:
|
|
||||||
return (door, crystal) not in door_list
|
|
||||||
|
|
||||||
|
|
||||||
def append_door_to_list(door, door_list, crystal):
|
|
||||||
if crystal == CrystalBarrier.Either:
|
|
||||||
if (door, CrystalBarrier.Blue) not in door_list and (door, CrystalBarrier.Orange) not in door_list:
|
|
||||||
door_list.append((door, crystal))
|
|
||||||
elif (door, CrystalBarrier.Blue) not in door_list:
|
|
||||||
door_list.append((door, CrystalBarrier.Blue))
|
|
||||||
elif (door, CrystalBarrier.Orange) not in door_list:
|
|
||||||
door_list.append((door, CrystalBarrier.Orange))
|
|
||||||
else:
|
|
||||||
door_list.append((door, crystal))
|
|
||||||
|
|
||||||
|
|
||||||
def state_id(state, flat_proposal):
|
def state_id(state, flat_proposal):
|
||||||
state_id = '1' if state.big_key_opened else '0'
|
state_id = '1' if state.big_key_opened else '0'
|
||||||
for d in flat_proposal:
|
for d in flat_proposal:
|
||||||
@@ -1180,6 +1332,7 @@ def reassign_key_doors(current_doors, proposal, world, player):
|
|||||||
d2 = obj[1]
|
d2 = obj[1]
|
||||||
if d1.type is DoorType.Interior:
|
if d1.type is DoorType.Interior:
|
||||||
change_door_to_small_key(d1, world, player)
|
change_door_to_small_key(d1, world, player)
|
||||||
|
d2.smallKey = True # ensure flag is set
|
||||||
else:
|
else:
|
||||||
names = [d1.name, d2.name]
|
names = [d1.name, d2.name]
|
||||||
found = False
|
found = False
|
||||||
@@ -1201,6 +1354,7 @@ def reassign_key_doors(current_doors, proposal, world, player):
|
|||||||
d = obj
|
d = obj
|
||||||
if d.type is DoorType.Interior:
|
if d.type is DoorType.Interior:
|
||||||
change_door_to_small_key(d, world, player)
|
change_door_to_small_key(d, world, player)
|
||||||
|
d.dest.smallKey = True # ensure flag is set
|
||||||
elif d.type is DoorType.SpiralStairs:
|
elif d.type is DoorType.SpiralStairs:
|
||||||
pass # we don't have spiral stairs candidates yet that aren't already key doors
|
pass # we don't have spiral stairs candidates yet that aren't already key doors
|
||||||
elif d.type is DoorType.Normal:
|
elif d.type is DoorType.Normal:
|
||||||
|
|||||||
32
Doors.py
32
Doors.py
@@ -454,10 +454,38 @@ def create_doors(world, player):
|
|||||||
world.get_door('Swamp Drain Left Switch', player).event('Swamp Drain')
|
world.get_door('Swamp Drain Left Switch', player).event('Swamp Drain')
|
||||||
world.get_door('Swamp Drain Right Switch', player).event('Swamp Drain')
|
world.get_door('Swamp Drain Right Switch', player).event('Swamp Drain')
|
||||||
world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain')
|
world.get_door('Swamp Flooded Room Ladder', player).event('Swamp Drain')
|
||||||
#todo - new region for flooded chests???
|
|
||||||
# world.get_door('', player).event('')
|
|
||||||
|
|
||||||
# crystal switches and barriers
|
# crystal switches and barriers
|
||||||
|
world.get_door('Hera Lobby Down Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Lobby Key Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Lobby Up Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Basement Cage Up Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Tile Room Up Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Tile Room EN', player).c_switch()
|
||||||
|
world.get_door('Hera Tridorm WN', player).c_switch()
|
||||||
|
world.get_door('Hera Tridorm SE', player).c_switch()
|
||||||
|
world.get_door('Hera Beetles Down Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Beetles WS', player).c_switch()
|
||||||
|
world.get_door('Hera Beetles Holes', player).c_switch()
|
||||||
|
world.get_door('Hera Startile Wide SW', player).c_switch()
|
||||||
|
world.get_door('Hera Startile Wide Up Stairs', player).c_switch()
|
||||||
|
world.get_door('Hera Startile Wide Holes', player).c_switch()
|
||||||
|
|
||||||
|
world.get_door('PoD Arena Main SW', player).c_switch()
|
||||||
|
world.get_door('PoD Arena Bridge SE', player).c_switch()
|
||||||
|
world.get_door('PoD Arena Main NW', player).barrier(CrystalBarrier.Orange)
|
||||||
|
world.get_door('PoD Arena Main NE', player).barrier(CrystalBarrier.Orange)
|
||||||
|
# maybe you can cross this way with blue up??
|
||||||
|
world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||||
|
world.get_door('PoD Arena Crystals E', player).barrier(CrystalBarrier.Blue)
|
||||||
|
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
|
||||||
|
world.get_door('PoD Sexy Statue W', player).c_switch()
|
||||||
|
world.get_door('PoD Sexy Statue NW', player).c_switch()
|
||||||
|
world.get_door('PoD Bow Statue SW', player).c_switch()
|
||||||
|
world.get_door('PoD Bow Statue Down Ladder', player).c_switch()
|
||||||
|
world.get_door('PoD Dark Pegs Up Ladder', player).c_switch()
|
||||||
|
world.get_door('PoD Dark Pegs WN', player).c_switch()
|
||||||
|
|
||||||
world.get_door('Swamp Crystal Switch EN', player).c_switch()
|
world.get_door('Swamp Crystal Switch EN', player).c_switch()
|
||||||
world.get_door('Swamp Crystal Switch SE', player).c_switch()
|
world.get_door('Swamp Crystal Switch SE', player).c_switch()
|
||||||
world.get_door('Swamp Shortcut Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('Swamp Shortcut Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
|||||||
74
Rules.py
74
Rules.py
@@ -1,8 +1,9 @@
|
|||||||
import collections
|
import collections
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import logging
|
import logging
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState, DoorType
|
||||||
from Dungeons import region_starts
|
from Dungeons import region_starts
|
||||||
|
from DoorShuffle import ExplorationState
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world, player):
|
def set_rules(world, player):
|
||||||
@@ -1695,72 +1696,79 @@ def generate_key_logic(start_region_names, small_key_name, world, player):
|
|||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
# Now that the dungeon layout is done, we need to search again to generate key logic.
|
# Now that the dungeon layout is done, we need to search again to generate key logic.
|
||||||
# TODO: This assumes all start doors are accessible, which isn't always true.
|
# TODO: This assumes all start doors are accessible, which isn't always true.
|
||||||
# TODO: This can generate solvable-but-really-annoying layouts due to one ways.
|
state = ExplorationState()
|
||||||
available_doors = [] # Doors to explore
|
# available_doors = [] # Doors to explore
|
||||||
visited_regions = set() # Regions we've been to and don't need to expand
|
# visited_regions = set() # Regions we've been to and don't need to expand
|
||||||
current_kr = 0 # Key regions are numbered, starting at 0
|
current_kr = 0 # Key regions are numbered, starting at 0
|
||||||
door_krs = {} # Map of key door name to KR it lives in
|
# door_krs = {} # Map of key door name to KR it lives in
|
||||||
kr_parents = {} # Key region to parent map
|
kr_parents = {} # Key region to parent map
|
||||||
kr_location_counts = defaultdict(int) # Number of locations in each key region
|
# kr_location_counts = defaultdict(int) # Number of locations in each key region
|
||||||
# Everything in a start region is in key region 0.
|
# Everything in a start region is in key region 0.
|
||||||
for name in start_region_names:
|
for name in start_region_names:
|
||||||
region = world.get_region(name, player)
|
region = world.get_region(name, player)
|
||||||
visited_regions.add(name)
|
state.visit_region(region, current_kr)
|
||||||
kr_location_counts[current_kr] += len(region.locations)
|
state.add_all_doors_check_key_region(region, current_kr, world, player)
|
||||||
for door in get_doors(world, region, player):
|
|
||||||
if not door.blocked:
|
|
||||||
available_doors.append(door)
|
|
||||||
door_krs[door.name] = current_kr
|
|
||||||
# Search into the dungeon
|
# Search into the dungeon
|
||||||
logger.debug('Begin key region search. %s', small_key_name)
|
logger.debug('Begin key region search. %s', small_key_name)
|
||||||
while len(available_doors) > 0:
|
while len(state.avail_doors) > 0:
|
||||||
# Open as many non-key doors as possible before opening a key door.
|
# Open as many non-key doors as possible before opening a key door.
|
||||||
# This guarantees that we're only exploring one key region at a time.
|
# This guarantees that we're only exploring one key region at a time.
|
||||||
available_doors.sort(key=lambda door: 0 if door.smallKey else 1)
|
state.avail_doors.sort(key=state.key_door_sort)
|
||||||
door = available_doors.pop()
|
explorable_door = state.next_avail_door()
|
||||||
# Bail early if we've been here before or the door is blocked
|
door = explorable_door.door
|
||||||
local_kr = door_krs[door.name]
|
local_kr = state.door_krs[door.name]
|
||||||
logger.debug(' kr %s: Door %s', local_kr, door.name)
|
logger.debug(' kr %s: Door %s', local_kr, door.name)
|
||||||
exit = world.get_entrance(door.name, player).connected_region
|
connect_region = world.get_entrance(door.name, player).connected_region
|
||||||
if door.blocked or exit.name in visited_regions:
|
# Bail early if we've been here before or the door is blocked
|
||||||
|
if not state.can_traverse(door) or state.visited(connect_region):
|
||||||
continue
|
continue
|
||||||
# Once we open a key door, we need a new region.
|
# Once we open a key door, we need a new region.
|
||||||
if door.smallKey:
|
if door.smallKey and door not in state.opened_doors: # we tend to open doors in a DFS manner
|
||||||
current_kr += 1
|
current_kr += 1
|
||||||
kr_parents[current_kr] = local_kr
|
kr_parents[current_kr] = local_kr
|
||||||
local_kr = current_kr
|
local_kr = current_kr
|
||||||
|
state.opened_doors.append(door)
|
||||||
|
if door.dest.smallKey:
|
||||||
|
state.opened_doors.append(door.dest)
|
||||||
logger.debug(' New KR %s', current_kr)
|
logger.debug(' New KR %s', current_kr)
|
||||||
# Account for the new region
|
# Account for the new region
|
||||||
visited_regions.add(exit.name)
|
state.visit_region(connect_region, local_kr)
|
||||||
kr_location_counts[local_kr] += len(exit.locations)
|
state.add_all_doors_check_key_region(connect_region, local_kr, world, player)
|
||||||
for new_door in get_doors(world, exit, player):
|
# kr_location_counts[local_kr] += len(exit.locations)
|
||||||
available_doors.append(new_door)
|
|
||||||
door_krs[new_door.name] = local_kr
|
|
||||||
# Now that we have doors divided up into key regions, we can analyze the map
|
# Now that we have doors divided up into key regions, we can analyze the map
|
||||||
# Invert the door -> kr map into one that lists doors by region.
|
# Invert the door -> kr map into one that lists doors by region.
|
||||||
kr_doors = defaultdict(list)
|
kr_doors = defaultdict(list)
|
||||||
region_krs = {}
|
region_krs = {}
|
||||||
for door_name in door_krs:
|
for door_name in state.door_krs:
|
||||||
kr = door_krs[door_name]
|
kr = state.door_krs[door_name]
|
||||||
exit = world.get_entrance(door_name, player);
|
entrance = world.get_entrance(door_name, player)
|
||||||
door = world.check_for_door(door_name, player)
|
door = world.check_for_door(door_name, player)
|
||||||
region_krs[exit.parent_region.name] = kr
|
ent_name = entrance.parent_region.name
|
||||||
|
if ent_name in region_krs.keys():
|
||||||
|
region_krs[entrance.parent_region.name] = min(region_krs[entrance.parent_region.name], kr)
|
||||||
|
else:
|
||||||
|
region_krs[entrance.parent_region.name] = kr
|
||||||
if door.smallKey and not door.blocked:
|
if door.smallKey and not door.blocked:
|
||||||
kr_doors[kr].append(exit)
|
kr_doors[kr].append(entrance)
|
||||||
kr_keys = defaultdict(int) # Number of keys each region needs
|
kr_keys = defaultdict(int) # Number of keys each region needs
|
||||||
for kr in range(0, current_kr + 1):
|
for kr in range(0, current_kr + 1):
|
||||||
logic_doors = []
|
logic_doors = []
|
||||||
keys = 0
|
keys = 0
|
||||||
for door in kr_doors[kr]:
|
for door in kr_doors[kr]:
|
||||||
dest_kr = region_krs[door.connected_region.name]
|
dest_kr = region_krs[door.connected_region.name]
|
||||||
if dest_kr > kr:
|
if dest_kr > kr: # may be the case if dest_kr != parent_kr of kr
|
||||||
# This door heads deeper into the dungeon. It needs a full key, and logic
|
# This door heads deeper into the dungeon. It needs a full key, and logic
|
||||||
keys += 1
|
keys += 1
|
||||||
logic_doors.append(door)
|
logic_doors.append(door)
|
||||||
elif dest_kr == kr:
|
elif dest_kr == kr:
|
||||||
# This door doesn't get us any deeper, but it's possible to waste a key.
|
# This door doesn't get us any deeper, but it's possible to waste a key.
|
||||||
# We're going to see its sibling in this search, so add half a key
|
# If we're going to see its sibling in this search, add half a key
|
||||||
keys += 0.5
|
actual_door = world.get_door(door.name, player)
|
||||||
|
if actual_door.type == DoorType.SpiralStairs:
|
||||||
|
keys += 1
|
||||||
|
else:
|
||||||
|
keys += 0.5
|
||||||
|
logic_doors.append(door) # this may still need logic
|
||||||
# Add key count from parent region
|
# Add key count from parent region
|
||||||
if kr in kr_parents:
|
if kr in kr_parents:
|
||||||
keys += kr_keys[kr_parents[kr]]
|
keys += kr_keys[kr_parents[kr]]
|
||||||
|
|||||||
Reference in New Issue
Block a user