Fixed a trap door in GT

Tried to remove set sensitivity in KeyDoorShuffle
Added a sophisticated way to find permutations in KeyDoorShuffle
This commit is contained in:
aerinon
2019-12-11 06:37:54 -07:00
parent f8218cf2ea
commit 45e7e228f6
2 changed files with 82 additions and 67 deletions

View File

@@ -10,13 +10,13 @@ class KeySphere(object):
def __init__(self):
self.access_door = None
self.free_locations = set()
self.free_locations = {}
self.prize_region = False
self.key_only_locations = set()
self.child_doors = set()
self.key_only_locations = {}
self.child_doors = {}
self.bk_locked = False
self.parent_sphere = None
self.other_locations = set()
self.other_locations = {}
def __eq__(self, other):
if self.prize_region != other.prize_region:
@@ -51,7 +51,7 @@ class KeyLayout(object):
self.flat_prop = None
self.max_chests = None
self.max_drops = None
self.all_chest_locations = set()
self.all_chest_locations = {}
# bk special?
# bk required? True if big chests or big doors exists
@@ -90,10 +90,10 @@ class KeyCounter(object):
def __init__(self, max_chests):
self.max_chests = max_chests
self.free_locations = set()
self.key_only_locations = set()
self.child_doors = set()
self.open_doors = set()
self.free_locations = {}
self.key_only_locations = {}
self.child_doors = {}
self.open_doors = {}
self.used_keys = 0
self.big_key_opened = False
self.important_location = False
@@ -101,7 +101,7 @@ class KeyCounter(object):
def update(self, key_sphere):
self.free_locations.update(key_sphere.free_locations)
self.key_only_locations.update(key_sphere.key_only_locations)
self.child_doors.update([x for x in key_sphere.child_doors if x not in self.open_doors and x.dest not in self.open_doors])
self.child_doors.update(dict.fromkeys([x for x in key_sphere.child_doors if x not in self.open_doors and x.dest not in self.open_doors]))
self.important_location = self.important_location or key_sphere.prize_region or self.special_region(key_sphere)
@staticmethod
@@ -115,16 +115,16 @@ class KeyCounter(object):
def open_door(self, door, flat_proposal):
if door in flat_proposal:
self.used_keys += 1
self.child_doors.remove(door)
self.open_doors.add(door)
del self.child_doors[door]
self.open_doors[door] = None
if door.dest in flat_proposal:
self.open_doors.add(door.dest)
self.open_doors[door.dest] = None
if door.dest in self.child_doors:
self.child_doors.remove(door.dest)
del self.child_doors[door.dest]
elif door.bigKey:
self.big_key_opened = True
self.child_doors.remove(door)
self.open_doors.add(door)
del self.child_doors[door]
self.open_doors[door] = None
def used_smalls_loc(self, reserve=0):
return max(self.used_keys + reserve - len(self.key_only_locations), 0)
@@ -225,7 +225,7 @@ def find_bk_locked_sections(key_layout, world):
big_chest_allowed_big_key = False
if not sphere.bk_locked:
bk_key_not_required.update(sphere.free_locations)
key_logic.bk_restricted.update(key_layout.all_chest_locations.difference(bk_key_not_required))
key_logic.bk_restricted.update(dict.fromkeys(set(key_layout.all_chest_locations).difference(bk_key_not_required)))
if not big_chest_allowed_big_key:
key_logic.bk_restricted.update(find_big_chest_locations(key_layout.all_chest_locations))
@@ -236,21 +236,10 @@ def empty_sphere(sphere):
return not sphere.prize_region
def find_best_parent_rule(key_layout, child):
best = None
for door_name, sphere in key_layout.key_spheres.items():
if sphere.access_door is not None and child in sphere.child_doors:
if door_name in key_layout.key_logic.door_rules.keys():
rule = key_layout.key_logic.door_rules[door_name]
if best is None or rule.small_key_num < best.small_key_num:
best = rule
return best
def relative_empty_sphere(sphere, key_counter):
if len(sphere.key_only_locations.difference(key_counter.key_only_locations)) > 0:
if len(set(sphere.key_only_locations).difference(key_counter.key_only_locations)) > 0:
return False
if len(sphere.free_locations.difference(key_counter.free_locations)) > 0:
if len(set(sphere.free_locations).difference(key_counter.free_locations)) > 0:
return False
new_child_door = False
for child in sphere.child_doors:
@@ -283,7 +272,7 @@ def find_best_counter(door, key_counter, key_layout, world, skip_bk): # try to
door_sphere = key_layout.key_spheres[door.name]
ignored_doors = {door, door.dest}
finished = False
opened_doors = set(key_counter.open_doors)
opened_doors = dict(key_counter.open_doors)
bk_opened = key_counter.big_key_opened
# new_counter = key_counter
last_counter = key_counter
@@ -293,10 +282,10 @@ def find_best_counter(door, key_counter, key_layout, world, skip_bk): # try to
finished = True
continue
for new_door in door_set:
new_sphere = key_layout.key_spheres[new_door.name]
proposed_doors = opened_doors.union({new_door, new_door.dest})
bk_open = bk_opened or new_door.bigKey or check_special_locations(new_sphere.free_locations)
new_counter = key_layout.key_counters[counter_id(proposed_doors, bk_open, key_layout.flat_prop)]
proposed_doors = {**opened_doors, **dict.fromkeys([new_door, new_door.dest])}
bk_open = bk_opened or new_door.bigKey
new_counter = find_counter(proposed_doors, bk_open, key_layout)
bk_open = new_counter.big_key_opened
# this means the new_door invalidates the door / leads to the same stuff
if relative_empty_sphere(door_sphere, new_counter):
ignored_doors.add(new_door)
@@ -311,19 +300,19 @@ def find_best_counter(door, key_counter, key_layout, world, skip_bk): # try to
def find_potential_open_doors(key_counter, ignored_doors, skip_bk):
small_doors = set()
big_doors = set()
small_doors = []
big_doors = []
for other in key_counter.child_doors:
if other not in ignored_doors:
if other not in ignored_doors and other.dest not in ignored_doors:
if other.bigKey:
if not skip_bk:
big_doors.add(other)
big_doors.append(other)
elif other.dest not in small_doors:
small_doors.add(other)
small_doors.append(other)
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
if len(small_doors) == 0 and (not skip_bk and (len(big_doors) == 0 or not big_key_available)):
return None
return small_doors.union(big_doors)
return small_doors + big_doors
def key_wasted(new_door, old_counter, new_counter, key_layout, world):
@@ -336,23 +325,22 @@ def key_wasted(new_door, old_counter, new_counter, key_layout, world):
if new_avail < old_avail:
return True
if new_avail == old_avail:
new_children = new_counter.child_doors.difference(old_counter.child_doors)
# new_children = {x for x in new_children if x.dest not in old_counter.child_doors}
old_children = old_counter.child_doors.keys()
new_children = [x for x in new_counter.child_doors.keys() if x not in old_children and x.dest not in old_children]
current_counter = new_counter
opened_doors = set(current_counter.open_doors)
opened_doors = dict(current_counter.open_doors)
bk_opened = current_counter.big_key_opened
for new_child in new_children:
new_sphere = key_layout.key_spheres[new_child.name]
proposed_doors = opened_doors.union({new_child, new_child.dest})
bk_open = bk_opened or new_door.bigKey or check_special_locations(new_sphere.free_locations)
new_counter = key_layout.key_counters[counter_id(proposed_doors, bk_open, key_layout.flat_prop)]
proposed_doors = {**opened_doors, **dict.fromkeys([new_child, new_child.dest])}
bk_open = bk_opened or new_door.bigKey
new_counter = find_counter(proposed_doors, bk_open, key_layout)
if key_wasted(new_child, current_counter, new_counter, key_layout, world):
return True # waste is possible
return False
def find_next_counter(new_door, old_counter, next_sphere, key_layout):
proposed_doors = old_counter.open_doors.union({new_door, new_door.dest})
proposed_doors = {**old_counter.open_doors, **dict.fromkeys([new_door, new_door.dest])}
bk_open = old_counter.big_key_opened or new_door.bigKey or check_special_locations(next_sphere.free_locations)
return key_layout.key_counters[counter_id(proposed_doors, bk_open, key_layout.flat_prop)]
@@ -448,7 +436,7 @@ def bk_restricted_rules(rule, sphere, key_counter, key_layout, world):
if not new_door.bigKey and new_door not in already_queued and new_door.dest not in already_queued:
queue.append(new_door)
already_queued.add(new_door)
unique_loc = post_counter.free_locations.difference(best_counter.free_locations)
unique_loc = set(post_counter.free_locations).difference(set(best_counter.free_locations))
if len(unique_loc) > 0:
rule.alternate_small_key = bk_number
rule.alternate_big_key_loc.update(unique_loc)
@@ -513,8 +501,8 @@ def create_key_spheres(key_layout, world, player):
merge_sphere.bk_locked = old_sphere.bk_locked and child_kr.bk_locked
if not empty_sphere(old_sphere) and not empty_sphere(child_kr) and not old_sphere == child_kr:
# ugly sphere merge function - just union locations - ugh
merge_sphere.free_locations = old_sphere.free_locations.union(child_kr.free_locations)
merge_sphere.key_only_locations = old_sphere.key_only_locations.union(child_kr.key_only_locations)
merge_sphere.free_locations = {**old_sphere.free_locations, **child_kr.free_locations}
merge_sphere.key_only_locations = {**old_sphere.key_only_locations, **child_kr.key_only_locations}
# this feels so ugly, key counters are much smarter than this - would love to get rid of spheres
return key_spheres
@@ -531,19 +519,19 @@ def create_key_sphere(state, parent_sphere, door):
parent_locations.update(p_region.key_only_locations)
parent_locations.update(p_region.other_locations)
p_region = p_region.parent_sphere
u_doors = unique_doors(state.small_doors+state.big_doors).difference(parent_doors)
key_sphere.child_doors.update(u_doors)
region_locations = set(state.found_locations).difference(parent_locations)
u_doors = set(unique_doors(state.small_doors+state.big_doors)).difference(parent_doors)
key_sphere.child_doors.update(dict.fromkeys(u_doors))
region_locations = list(set(state.found_locations).difference(parent_locations))
for loc in region_locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
key_sphere.prize_region = True
key_sphere.other_locations.add(loc)
key_sphere.other_locations[loc] = None
elif loc.event and 'Small Key' in loc.item.name:
key_sphere.key_only_locations.add(loc)
key_sphere.key_only_locations[loc] = None
elif loc.name not in dungeon_events:
key_sphere.free_locations.add(loc)
key_sphere.free_locations[loc] = None
else:
key_sphere.other_locations.add(loc)
key_sphere.other_locations[loc] = None
# todo: Cellblock in a dungeon with a big_key door or chest - Crossed Mode
key_sphere.bk_locked = state.big_key_opened if not state.big_key_special else False
if door is not None:
@@ -573,10 +561,10 @@ def open_a_door(door, child_state, flat_proposal):
# allows dest doors
def unique_doors(doors):
unique_d_set = set()
unique_d_set = []
for d in doors:
if d.door not in unique_d_set:
unique_d_set.add(d.door)
unique_d_set.append(d.door)
return unique_d_set
@@ -804,7 +792,7 @@ def create_key_counters(key_layout, world, player):
def create_key_counter_x(state, key_layout, world, player):
key_counter = KeyCounter(key_layout.max_chests)
key_counter.child_doors.update(unique_doors(state.small_doors+state.big_doors))
key_counter.child_doors.update(dict.fromkeys(unique_doors(state.small_doors+state.big_doors)))
for loc in state.found_locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
key_counter.important_location = True
@@ -812,10 +800,10 @@ def create_key_counter_x(state, key_layout, world, player):
elif loc.name in ['Attic Cracked Floor', 'Suspicious Maiden']:
key_counter.important_location = True
elif loc.event and 'Small Key' in loc.item.name:
key_counter.key_only_locations.add(loc)
key_counter.key_only_locations[loc] = None
elif loc.name not in dungeon_events:
key_counter.free_locations.add(loc)
key_counter.open_doors.update(state.opened_doors)
key_counter.free_locations[loc] = None
key_counter.open_doors.update(dict.fromkeys(state.opened_doors))
key_counter.used_keys = count_unique_sm_doors(state.opened_doors)
if state.big_key_special:
key_counter.big_key_opened = state.visited(world.get_region('Hyrule Dungeon Cellblock', player))
@@ -836,10 +824,37 @@ def state_id(state, flat_proposal):
return s_id
def find_counter(opened_doors, bk_hint, key_layout):
counter = find_counter_hint(opened_doors, bk_hint, key_layout)
if counter is not None:
return counter
more_doors = []
for door in opened_doors.keys():
more_doors.append(door)
if door.dest not in opened_doors.keys():
more_doors.append(door.dest)
if len(more_doors) > len(opened_doors.keys()):
counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout)
if counter is not None:
return counter
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop))
def find_counter_hint(opened_doors, bk_hint, key_layout):
cid = counter_id(opened_doors, bk_hint, key_layout.flat_prop)
if cid in key_layout.key_counters.keys():
return key_layout.key_counters[cid]
if not bk_hint:
cid = counter_id(opened_doors, True, key_layout.flat_prop)
if cid in key_layout.key_counters.keys():
return key_layout.key_counters[cid]
return None
def counter_id(opened_doors, bk_unlocked, flat_proposal):
s_id = '1' if bk_unlocked else '0'
for d in flat_proposal:
s_id += '1' if d in opened_doors else '0'
s_id += '1' if d in opened_doors.keys() else '0'
return s_id