690 lines
28 KiB
Python
690 lines
28 KiB
Python
import random
|
|
import collections
|
|
from collections import defaultdict
|
|
from enum import Enum, unique
|
|
import logging
|
|
|
|
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, flooded_keys
|
|
from Regions import key_only_locations, dungeon_events, flooded_keys_reverse
|
|
|
|
|
|
@unique
|
|
class Hook(Enum):
|
|
North = 0
|
|
West = 1
|
|
South = 2
|
|
East = 3
|
|
Stairs = 4
|
|
|
|
|
|
class GraphPiece:
|
|
|
|
def __init__(self):
|
|
self.hanger_info = None
|
|
self.hooks = {}
|
|
self.visited_regions = set()
|
|
|
|
|
|
def generate_dungeon(available_sectors, entrance_region_names, world, player):
|
|
logger = logging.getLogger('')
|
|
entrance_regions = convert_regions(entrance_region_names, world, player)
|
|
doors_to_connect = set()
|
|
all_regions = set()
|
|
for sector in available_sectors:
|
|
for door in sector.outstanding_doors:
|
|
doors_to_connect.add(door)
|
|
all_regions.update(sector.regions)
|
|
proposed_map = {}
|
|
choices_master = [[]]
|
|
depth = 0
|
|
dungeon_cache = {}
|
|
backtrack = False
|
|
# last_choice = None
|
|
while len(proposed_map) < len(doors_to_connect):
|
|
# what are my choices?
|
|
if depth not in dungeon_cache.keys():
|
|
dungeon, hangers, hooks = gen_dungeon_info(available_sectors, entrance_regions, proposed_map, doors_to_connect, world, player)
|
|
dungeon_cache[depth] = dungeon, hangers, hooks
|
|
valid = check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions)
|
|
else:
|
|
dungeon, hangers, hooks = dungeon_cache[depth]
|
|
valid = True
|
|
if valid:
|
|
prev_choices = choices_master[depth]
|
|
# make a choice
|
|
hanger, hook = make_a_choice(dungeon, hangers, hooks, prev_choices)
|
|
if hanger is None:
|
|
backtrack = True
|
|
else:
|
|
logger.debug(' '*depth+"%d: Linking %s to %s", depth, hanger.name, hook.name)
|
|
proposed_map[hanger] = hook
|
|
proposed_map[hook] = hanger
|
|
last_choice = (hanger, hook)
|
|
choices_master[depth].append(last_choice)
|
|
depth += 1
|
|
choices_master.append([])
|
|
else:
|
|
backtrack = True
|
|
if backtrack:
|
|
backtrack = False
|
|
choices_master.pop()
|
|
dungeon_cache.pop(depth, None)
|
|
depth -= 1
|
|
a, b = choices_master[depth][-1]
|
|
logger.debug(' '*depth+"%d: Rescinding %s, %s", depth, a.name, b.name)
|
|
proposed_map.pop(a, None)
|
|
proposed_map.pop(b, None)
|
|
queue = collections.deque(proposed_map.items())
|
|
while len(queue) > 0:
|
|
a, b = queue.pop()
|
|
connect_doors(a, b, world, player)
|
|
queue.remove((b, a))
|
|
master_sector = available_sectors.pop()
|
|
for sub_sector in available_sectors:
|
|
master_sector.regions.extend(sub_sector.regions)
|
|
return master_sector
|
|
|
|
|
|
def gen_dungeon_info(available_sectors, entrance_regions, proposed_map, valid_doors, world, player):
|
|
# step 1 create dungeon: Dict<DoorName|Origin, GraphPiece>
|
|
dungeon = {}
|
|
original_state = extend_reachable_state_improved(entrance_regions, ExplorationState(), proposed_map, valid_doors, world, player)
|
|
dungeon['Origin'] = create_graph_piece_from_state(None, original_state, original_state, proposed_map)
|
|
doors_to_connect = set()
|
|
blue_hooks = []
|
|
o_state_cache = {}
|
|
for sector in available_sectors:
|
|
for door in sector.outstanding_doors:
|
|
doors_to_connect.add(door)
|
|
if not door.stonewall and door not in proposed_map.keys():
|
|
parent = parent_region(door, world, player).parent_region
|
|
o_state = extend_reachable_state_improved([parent], ExplorationState(), proposed_map, valid_doors, world, player)
|
|
o_state_cache[door.name] = o_state
|
|
piece = create_graph_piece_from_state(door, o_state, o_state, proposed_map)
|
|
dungeon[door.name] = piece
|
|
for hook, crystal in piece.hooks.items():
|
|
if crystal == CrystalBarrier.Blue or crystal == CrystalBarrier.Either:
|
|
h_type = hook_from_door(hook)
|
|
if h_type not in blue_hooks:
|
|
blue_hooks.append(h_type) # todo: specific hooks and valid path to c_switch
|
|
if len(blue_hooks) > 0:
|
|
for sector in available_sectors:
|
|
for door in sector.outstanding_doors:
|
|
h_type = hanger_from_door(door)
|
|
if not door.stonewall and door not in proposed_map.keys() and h_type in blue_hooks:
|
|
parent = parent_region(door, world, player).parent_region
|
|
blue_start = ExplorationState(CrystalBarrier.Blue)
|
|
b_state = extend_reachable_state_improved([parent], blue_start, proposed_map, valid_doors, world, player)
|
|
o_state = o_state_cache[door.name]
|
|
dungeon[door.name] = create_graph_piece_from_state(door, o_state, b_state, proposed_map)
|
|
|
|
# catalog hooks: Dict<Hook, Set<>
|
|
# and hangers:
|
|
avail_hooks = defaultdict(set)
|
|
hangers = defaultdict(set)
|
|
for key, piece in dungeon.items():
|
|
door_hang = piece.hanger_info
|
|
if door_hang is not None:
|
|
hanger = hanger_from_door(door_hang)
|
|
hangers[hanger].add(door_hang)
|
|
for door, crystal in piece.hooks.items():
|
|
hook = hook_from_door(door)
|
|
avail_hooks[hook].add((door, crystal, door_hang))
|
|
|
|
# thin out invalid hanger
|
|
winnow_hangers(hangers, avail_hooks)
|
|
return dungeon, hangers, avail_hooks
|
|
|
|
|
|
def make_a_choice(dungeon, hangers, avail_hooks, prev_choices):
|
|
# choose a hanger
|
|
all_hooks = set()
|
|
origin = dungeon['Origin']
|
|
for key in avail_hooks.keys():
|
|
for hstuff in avail_hooks[key]:
|
|
all_hooks.add(hstuff[0])
|
|
candidate_hangers = []
|
|
for key in hangers.keys():
|
|
candidate_hangers.extend(hangers[key])
|
|
candidate_hangers.sort(key=lambda x: x.name) # sorting to create predictable seeds
|
|
random.shuffle(candidate_hangers) # randomize if equal preference
|
|
stage_2_hangers = []
|
|
hookable_hangers = collections.deque()
|
|
queue = collections.deque(candidate_hangers)
|
|
while len(queue) > 0:
|
|
c_hang = queue.pop()
|
|
if c_hang in all_hooks:
|
|
hookable_hangers.append(c_hang)
|
|
else:
|
|
stage_2_hangers.append(c_hang) # prefer hangers that are not hooks
|
|
# todo : prefer hangers with fewer hooks at some point? not sure about this
|
|
# this prefer hangers of the fewest type - to catch problems fast
|
|
hookable_hangers = sorted(hookable_hangers, key=lambda door: len(hangers[hanger_from_door(door)]), reverse=True)
|
|
origin_hangers = []
|
|
while len(hookable_hangers) > 0:
|
|
c_hang = hookable_hangers.pop()
|
|
if c_hang in origin.hooks.keys():
|
|
origin_hangers.append(c_hang)
|
|
else:
|
|
stage_2_hangers.append(c_hang) # prefer hangers that are not hooks on the 'origin'
|
|
stage_2_hangers.extend(origin_hangers)
|
|
|
|
hook = None
|
|
next_hanger = None
|
|
while hook is None:
|
|
if len(stage_2_hangers) == 0:
|
|
return None, None
|
|
next_hanger = stage_2_hangers.pop(0)
|
|
next_hanger_type = hanger_from_door(next_hanger)
|
|
hook_candidates = []
|
|
for door, crystal, orig_hang in avail_hooks[next_hanger_type]:
|
|
if filter_choices(next_hanger, door, orig_hang, prev_choices, hook_candidates):
|
|
hook_candidates.append(door)
|
|
if len(hook_candidates) > 0:
|
|
hook_candidates.sort(key=lambda x: x.name) # sort for deterministic seeds
|
|
hook = random.choice(tuple(hook_candidates))
|
|
|
|
return next_hanger, hook
|
|
|
|
|
|
def filter_choices(next_hanger, door, orig_hang, prev_choices, hook_candidates):
|
|
if (next_hanger, door) in prev_choices or (door, next_hanger) in prev_choices:
|
|
return False
|
|
return next_hanger != door and orig_hang != next_hanger and door not in hook_candidates
|
|
|
|
|
|
def check_valid(dungeon, hangers, hooks, proposed_map, doors_to_connect, all_regions):
|
|
# evaluate if everything is still plausible
|
|
|
|
# only origin is left in the dungeon and not everything is connected
|
|
if len(dungeon.keys()) <= 1 and len(proposed_map.keys()) < len(doors_to_connect):
|
|
return False
|
|
# origin has no more hooks, but not all doors have been proposed
|
|
if len(dungeon['Origin'].hooks) == 0 and len(proposed_map.keys()) < len(doors_to_connect):
|
|
return False
|
|
for key in hangers.keys():
|
|
if len(hooks[key]) > 0 and len(hangers[key]) == 0:
|
|
return False
|
|
# todo: stonewall - check that there's no hook-only that is without a matching hanger
|
|
all_visited = set()
|
|
for piece in dungeon.values():
|
|
all_visited.update(piece.visited_regions)
|
|
if len(all_regions.difference(all_visited)) > 0:
|
|
return False
|
|
new_hangers_found = True
|
|
accessible_hook_types = []
|
|
hanger_matching = set()
|
|
all_hangers = set()
|
|
origin_hooks = set(dungeon['Origin'].hooks.keys())
|
|
for door_hook in origin_hooks:
|
|
h_type = hook_from_door(door_hook)
|
|
if h_type not in accessible_hook_types:
|
|
accessible_hook_types.append(h_type)
|
|
while new_hangers_found:
|
|
new_hangers_found = False
|
|
for hanger_set in hangers.values():
|
|
for hanger in hanger_set:
|
|
all_hangers.add(hanger)
|
|
h_type = hanger_from_door(hanger)
|
|
if (h_type in accessible_hook_types or hanger in origin_hooks) and hanger not in hanger_matching:
|
|
new_hangers_found = True
|
|
hanger_matching.add(hanger)
|
|
matching_hooks = dungeon[hanger.name].hooks.keys()
|
|
origin_hooks.update(matching_hooks)
|
|
for door_hook in matching_hooks:
|
|
new_h_type = hook_from_door(door_hook)
|
|
if new_h_type not in accessible_hook_types:
|
|
accessible_hook_types.append(new_h_type)
|
|
return len(all_hangers.difference(hanger_matching)) == 0
|
|
|
|
|
|
def winnow_hangers(hangers, hooks):
|
|
removal_info = []
|
|
for hanger, door_set in hangers.items():
|
|
for door in door_set:
|
|
hook_set = hooks[hanger]
|
|
if len(hook_set) == 0:
|
|
removal_info.append((hanger, door))
|
|
else:
|
|
found_valid = False
|
|
for door_hook, crystal, orig_hanger in hook_set:
|
|
if orig_hanger != door:
|
|
found_valid = True
|
|
if not found_valid:
|
|
removal_info.append((hanger, door))
|
|
for hanger, door in removal_info:
|
|
hangers[hanger].remove(door)
|
|
|
|
|
|
def create_graph_piece_from_state(door, o_state, b_state, proposed_map):
|
|
# todo: info about dungeon events - not sure about that
|
|
graph_piece = GraphPiece()
|
|
all_unattached = {}
|
|
for exp_d in o_state.unattached_doors:
|
|
all_unattached[exp_d.door] = exp_d.crystal
|
|
for exp_d in b_state.unattached_doors:
|
|
d = exp_d.door
|
|
if d in all_unattached.keys():
|
|
if all_unattached[d] != exp_d.crystal:
|
|
if all_unattached[d] == CrystalBarrier.Orange and exp_d.crystal == CrystalBarrier.Blue:
|
|
all_unattached[d] = CrystalBarrier.Null
|
|
else:
|
|
logging.getLogger('').warning('Mismatched state @ %s (o:%s b:%s)', d.name, all_unattached[d], exp_d.crystal)
|
|
else:
|
|
all_unattached[exp_d.door] = exp_d.crystal
|
|
for d, crystal in all_unattached.items():
|
|
if (door is None or d != door) and not d.blocked and d not in proposed_map.keys():
|
|
graph_piece.hooks[d] = crystal
|
|
graph_piece.hanger_info = door
|
|
graph_piece.visited_regions.update(o_state.visited_blue)
|
|
graph_piece.visited_regions.update(o_state.visited_orange)
|
|
graph_piece.visited_regions.update(b_state.visited_blue)
|
|
graph_piece.visited_regions.update(b_state.visited_orange)
|
|
return graph_piece
|
|
|
|
|
|
def parent_region(door, world, player):
|
|
return world.get_entrance(door.name, player)
|
|
|
|
|
|
def hook_from_door(door):
|
|
if door.type == DoorType.SpiralStairs:
|
|
return Hook.Stairs
|
|
if door.type == DoorType.Normal:
|
|
dir = {
|
|
Direction.North: Hook.North,
|
|
Direction.South: Hook.South,
|
|
Direction.West: Hook.West,
|
|
Direction.East: Hook.East,
|
|
}
|
|
return dir[door.direction]
|
|
return None
|
|
|
|
|
|
def hanger_from_door(door):
|
|
if door.type == DoorType.SpiralStairs:
|
|
return Hook.Stairs
|
|
if door.type == DoorType.Normal:
|
|
dir = {
|
|
Direction.North: Hook.South,
|
|
Direction.South: Hook.North,
|
|
Direction.West: Hook.East,
|
|
Direction.East: Hook.West,
|
|
}
|
|
return dir[door.direction]
|
|
return None
|
|
|
|
|
|
def connect_doors(a, b, world, player):
|
|
# Return on unsupported types.
|
|
if a.type in [DoorType.Open, DoorType.StraightStairs, DoorType.Hole, DoorType.Warp, DoorType.Ladder,
|
|
DoorType.Interior, DoorType.Logical]:
|
|
return
|
|
# Connect supported types
|
|
if a.type == DoorType.Normal or a.type == DoorType.SpiralStairs:
|
|
if a.blocked:
|
|
connect_one_way(world, b.name, a.name, player)
|
|
elif b.blocked:
|
|
connect_one_way(world, a.name, b.name, player)
|
|
else:
|
|
connect_two_way(world, a.name, b.name, player)
|
|
return
|
|
# If we failed to account for a type, panic
|
|
raise RuntimeError('Unknown door type ' + a.type.name)
|
|
|
|
|
|
def connect_two_way(world, entrancename, exitname, player):
|
|
entrance = world.get_entrance(entrancename, player)
|
|
ext = world.get_entrance(exitname, player)
|
|
|
|
# if these were already connected somewhere, remove the backreference
|
|
if entrance.connected_region is not None:
|
|
entrance.connected_region.entrances.remove(entrance)
|
|
if ext.connected_region is not None:
|
|
ext.connected_region.entrances.remove(ext)
|
|
|
|
entrance.connect(ext.parent_region)
|
|
ext.connect(entrance.parent_region)
|
|
if entrance.parent_region.dungeon:
|
|
ext.parent_region.dungeon = entrance.parent_region.dungeon
|
|
x = world.check_for_door(entrancename, player)
|
|
y = world.check_for_door(exitname, player)
|
|
if x is not None:
|
|
x.dest = y
|
|
if y is not None:
|
|
y.dest = x
|
|
|
|
|
|
def connect_one_way(world, entrancename, exitname, player):
|
|
entrance = world.get_entrance(entrancename, player)
|
|
ext = world.get_entrance(exitname, player)
|
|
|
|
# if these were already connected somewhere, remove the backreference
|
|
if entrance.connected_region is not None:
|
|
entrance.connected_region.entrances.remove(entrance)
|
|
if ext.connected_region is not None:
|
|
ext.connected_region.entrances.remove(ext)
|
|
|
|
entrance.connect(ext.parent_region)
|
|
if entrance.parent_region.dungeon:
|
|
ext.parent_region.dungeon = entrance.parent_region.dungeon
|
|
x = world.check_for_door(entrancename, player)
|
|
y = world.check_for_door(exitname, player)
|
|
if x is not None:
|
|
x.dest = y
|
|
if y is not None:
|
|
y.dest = x
|
|
|
|
|
|
class ExplorationState(object):
|
|
|
|
def __init__(self, init_crystal=CrystalBarrier.Orange):
|
|
|
|
self.unattached_doors = []
|
|
self.avail_doors = []
|
|
self.event_doors = []
|
|
|
|
self.visited_orange = []
|
|
self.visited_blue = []
|
|
self.events = set()
|
|
self.crystal = init_crystal
|
|
|
|
# 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
|
|
|
|
self.non_door_entrances = []
|
|
|
|
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)
|
|
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)
|
|
|
|
ret.non_door_entrances = list(self.non_door_entrances)
|
|
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 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
|
|
if location not in self.found_locations:
|
|
self.found_locations.append(location)
|
|
if location.name in dungeon_events and location.name not in self.events:
|
|
if self.flooded_key_check(location):
|
|
self.perform_event(location.name, key_region)
|
|
if location.name in flooded_keys_reverse.keys() and self.location_found(flooded_keys_reverse[location.name]):
|
|
self.perform_event(flooded_keys_reverse[location.name], key_region)
|
|
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 flooded_key_check(self, location):
|
|
if location.name not in flooded_keys.keys():
|
|
return True
|
|
return flooded_keys[location.name] in [x.name for x in self.found_locations]
|
|
|
|
def location_found(self, location_name):
|
|
for l in self.found_locations:
|
|
if l.name == location_name:
|
|
return True
|
|
return False
|
|
|
|
def perform_event(self, location_name, key_region):
|
|
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
|
|
|
|
def add_all_entrance_doors_check_unattached(self, region, world, player):
|
|
door_list = [x for x in get_doors(world, region, player) if x.type in [DoorType.Normal, DoorType.SpiralStairs]]
|
|
door_list.extend(get_entrance_doors(world, region, player))
|
|
for door in door_list:
|
|
if self.can_traverse(door):
|
|
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)
|
|
elif not self.in_door_list(door, self.avail_doors):
|
|
self.append_door_to_list(door, self.avail_doors)
|
|
for entrance in region.entrances:
|
|
door = world.check_for_door(entrance.name, player)
|
|
if door is None:
|
|
self.non_door_entrances.append(entrance)
|
|
|
|
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 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)
|
|
elif not self.in_door_list(door, self.avail_doors):
|
|
self.append_door_to_list(door, self.avail_doors)
|
|
|
|
def add_all_doors_check_proposed(self, region, proposed_map, valid_doors, world, player):
|
|
for door in get_dungeon_doors(region, world, player):
|
|
if self.can_traverse(door):
|
|
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors)\
|
|
and door not in proposed_map.keys() and door in valid_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
|
|
else:
|
|
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 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)
|
|
|
|
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 visited_at_all(self, region):
|
|
return region in self.visited_blue or region in self.visited_orange
|
|
|
|
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 validate(self, door, region, world):
|
|
return self.can_traverse(door) and not self.visited(region) and valid_region_to_explore(region, world)
|
|
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
|
|
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()
|
|
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
|
|
if connect_region is not None:
|
|
if valid_region_to_explore(connect_region, world) and 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 extend_reachable_state_improved(search_regions, state, proposed_map, valid_doors, world, player):
|
|
local_state = state.copy()
|
|
for region in search_regions:
|
|
local_state.visit_region(region)
|
|
local_state.add_all_doors_check_proposed(region, proposed_map, valid_doors, world, player)
|
|
while len(local_state.avail_doors) > 0:
|
|
explorable_door = local_state.next_avail_door()
|
|
if explorable_door.door in proposed_map:
|
|
connect_region = world.get_entrance(proposed_map[explorable_door.door].name, player).parent_region
|
|
else:
|
|
connect_region = world.get_entrance(explorable_door.door.name, player).connected_region
|
|
if connect_region is not None:
|
|
if valid_region_to_explore(connect_region, world) and not local_state.visited(connect_region):
|
|
local_state.visit_region(connect_region)
|
|
local_state.add_all_doors_check_proposed(connect_region, proposed_map, valid_doors, world, player)
|
|
return local_state
|
|
|
|
|
|
# cross-utility methods
|
|
def valid_region_to_explore(region, world):
|
|
return region.type == RegionType.Dungeon or region.name in world.inaccessible_regions
|
|
|
|
|
|
def get_doors(world, region, player):
|
|
res = []
|
|
for exit in region.exits:
|
|
door = world.check_for_door(exit.name, player)
|
|
if door is not None:
|
|
res.append(door)
|
|
return res
|
|
|
|
|
|
def get_dungeon_doors(region, world, player):
|
|
res = []
|
|
for ext in region.exits:
|
|
door = world.check_for_door(ext.name, player)
|
|
if door is not None and ext.parent_region.type == RegionType.Dungeon:
|
|
res.append(door)
|
|
return res
|
|
|
|
|
|
def get_entrance_doors(world, region, player):
|
|
res = []
|
|
for exit in region.entrances:
|
|
door = world.check_for_door(exit.name, player)
|
|
if door is not None:
|
|
res.append(door)
|
|
return res
|
|
|
|
|
|
def convert_regions(region_names, world, player):
|
|
region_list = []
|
|
for name in region_names:
|
|
region_list.append(world.get_region(name, player))
|
|
return region_list
|