Placement rules implemented for checking key placement
This commit is contained in:
@@ -158,6 +158,7 @@ def vanilla_key_logic(world, player):
|
||||
world.key_logic[player] = {}
|
||||
analyze_dungeon(key_layout, world, player)
|
||||
world.key_logic[player][builder.name] = key_layout.key_logic
|
||||
log_key_logic(builder.name, key_layout.key_logic)
|
||||
last_key = None
|
||||
if world.shuffle[player] == 'vanilla':
|
||||
validate_vanilla_key_logic(world, player)
|
||||
@@ -1013,6 +1014,13 @@ def log_key_logic(d_name, key_logic):
|
||||
if rule.alternate_small_key is not None:
|
||||
for loc in rule.alternate_big_key_loc:
|
||||
logger.debug('---BK Loc %s', loc.name)
|
||||
logger.debug('Placement rules for %s', d_name)
|
||||
for rule in key_logic.placement_rules:
|
||||
logger.debug('*Rule for %s:', rule.door_reference)
|
||||
if rule.bk_conditional_set:
|
||||
logger.debug('**BK Checks %s', ','.join([x.name for x in rule.bk_conditional_set]))
|
||||
logger.debug('**BK Blocked By Door (%s) : %s', rule.needed_keys_wo_bk, ','.join([x.name for x in rule.check_locations_wo_bk]))
|
||||
logger.debug('**BK Elsewhere (%s) : %s', rule.needed_keys_w_bk, ','.join([x.name for x in rule.check_locations_w_bk]))
|
||||
|
||||
|
||||
def build_pair_list(flat_list):
|
||||
@@ -1105,6 +1113,7 @@ def ncr(n, r):
|
||||
|
||||
def reassign_key_doors(builder, world, player):
|
||||
logger = logging.getLogger('')
|
||||
logger.debug('Key doors for %s', builder.name)
|
||||
proposal = builder.key_door_proposal
|
||||
flat_proposal = flatten_pair_list(proposal)
|
||||
queue = deque(find_current_key_doors(builder))
|
||||
|
||||
34
Fill.py
34
Fill.py
@@ -201,7 +201,8 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
||||
else:
|
||||
test_state = maximum_exploration_state
|
||||
if (not single_player_placement or location.player == item_to_place.player)\
|
||||
and location.can_fill(test_state, item_to_place, perform_access_check):
|
||||
and location.can_fill(test_state, item_to_place, perform_access_check)\
|
||||
and valid_key_placement(item_to_place, location, itempool, world):
|
||||
spot_to_fill = location
|
||||
break
|
||||
elif item_to_place.smallkey or item_to_place.bigkey:
|
||||
@@ -217,11 +218,42 @@ def fill_restrictive(world, base_state, locations, itempool, single_player_place
|
||||
raise FillError('No more spots to place %s' % item_to_place)
|
||||
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
track_outside_keys(item_to_place, spot_to_fill, world)
|
||||
locations.remove(spot_to_fill)
|
||||
spot_to_fill.event = True
|
||||
|
||||
itempool.extend(unplaced_items)
|
||||
|
||||
|
||||
def valid_key_placement(item, location, itempool, world):
|
||||
if (not item.smallkey and not item.bigkey) or item.player != location.player or world.retro[item.player]:
|
||||
return True
|
||||
dungeon = location.parent_region.dungeon
|
||||
if dungeon:
|
||||
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
|
||||
return True
|
||||
key_logic = world.key_logic[item.player][dungeon.name]
|
||||
unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name])
|
||||
return key_logic.check_placement(unplaced_keys)
|
||||
else:
|
||||
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])
|
||||
or (item.bigkey and not world.bigkeyshuffle[item.player]))
|
||||
return not inside_dungeon_item
|
||||
|
||||
|
||||
def track_outside_keys(item, location, world):
|
||||
if not item.smallkey:
|
||||
return
|
||||
item_dungeon = item.name.split('(')[1][:-1]
|
||||
if item_dungeon == 'Escape':
|
||||
item_dungeon = 'Hyrule Castle'
|
||||
if location.player == item.player:
|
||||
loc_dungeon = location.parent_region.dungeon
|
||||
if loc_dungeon and loc_dungeon.name == item_dungeon:
|
||||
return # this is an inside key
|
||||
world.key_logic[item.player][item_dungeon].outside_keys += 1
|
||||
|
||||
|
||||
def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None):
|
||||
# If not passed in, then get a shuffled list of locations to fill in
|
||||
if not fill_locations:
|
||||
|
||||
@@ -21,6 +21,7 @@ class KeyLayout(object):
|
||||
self.max_drops = None
|
||||
self.all_chest_locations = {}
|
||||
self.big_key_special = False
|
||||
self.all_locations = set()
|
||||
|
||||
# bk special?
|
||||
# bk required? True if big chests or big doors exists
|
||||
@@ -44,6 +45,14 @@ class KeyLogic(object):
|
||||
self.bk_chests = set()
|
||||
self.logic_min = {}
|
||||
self.logic_max = {}
|
||||
self.placement_rules = []
|
||||
self.outside_keys = 0
|
||||
|
||||
def check_placement(self, unplaced_keys):
|
||||
for rule in self.placement_rules:
|
||||
if not rule.is_satisfiable(self.outside_keys, unplaced_keys):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class DoorRules(object):
|
||||
@@ -59,6 +68,38 @@ class DoorRules(object):
|
||||
self.small_location = None
|
||||
|
||||
|
||||
class PlacementRule(object):
|
||||
|
||||
def __init__(self):
|
||||
self.door_reference = None
|
||||
self.small_key = None
|
||||
self.bk_conditional_set = None # the location that means
|
||||
self.needed_keys_w_bk = None
|
||||
self.needed_keys_wo_bk = None
|
||||
self.check_locations_w_bk = None
|
||||
self.check_locations_wo_bk = None
|
||||
|
||||
def is_satisfiable(self, outside_keys, unplaced_keys):
|
||||
bk_blocked = False
|
||||
if self.bk_conditional_set:
|
||||
for loc in self.bk_conditional_set:
|
||||
if loc.item and loc.item.bigkey:
|
||||
bk_blocked = True
|
||||
break
|
||||
available_keys = outside_keys
|
||||
empty_chests = 0
|
||||
check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk
|
||||
threshold = self.needed_keys_wo_bk if bk_blocked else self.needed_keys_w_bk
|
||||
for loc in check_locations:
|
||||
if not loc.item:
|
||||
empty_chests += 1
|
||||
elif loc.item and loc.item.name == self.small_key:
|
||||
available_keys += 1
|
||||
place_able_keys = min(empty_chests, unplaced_keys)
|
||||
available_keys += place_able_keys
|
||||
return available_keys >= threshold
|
||||
|
||||
|
||||
class KeyCounter(object):
|
||||
|
||||
def __init__(self, max_chests):
|
||||
@@ -108,6 +149,8 @@ def analyze_dungeon(key_layout, world, player):
|
||||
|
||||
find_bk_locked_sections(key_layout, world)
|
||||
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
|
||||
if world.retro[player] and world.mode[player] != 'standard':
|
||||
return
|
||||
|
||||
original_key_counter = find_counter({}, False, key_layout)
|
||||
queue = deque([(None, original_key_counter)])
|
||||
@@ -155,6 +198,7 @@ def analyze_dungeon(key_layout, world, player):
|
||||
check_for_self_lock_key(rule, child, best_counter, key_layout, world, player)
|
||||
bk_restricted_rules(rule, child, odd_counter, empty_flag, key_counter, key_layout, world, player)
|
||||
key_logic.door_rules[child.name] = rule
|
||||
create_placement_rule(key_layout, child, odd_counter, key_counter, world, player)
|
||||
doors_completed.add(child)
|
||||
next_counter = find_next_counter(child, key_counter, key_layout)
|
||||
ctr_id = cid(next_counter, key_layout)
|
||||
@@ -177,6 +221,65 @@ def analyze_dungeon(key_layout, world, player):
|
||||
rule.small_key_num, rule.alternate_small_key = rule.alternate_small_key, rule.small_key_num
|
||||
|
||||
|
||||
def create_placement_rule(key_layout, door, odd_ctr, current_ctr, world, player):
|
||||
key_logic = key_layout.key_logic
|
||||
worst_ctr = find_worst_counter(door, odd_ctr, current_ctr, key_layout, False)
|
||||
sm_num = worst_ctr.used_keys + 1
|
||||
accessible_loc = set()
|
||||
accessible_loc.update(worst_ctr.free_locations)
|
||||
accessible_loc.update(worst_ctr.key_only_locations)
|
||||
worst_ctr_wo_bk, post_ctr, alt_num = find_worst_counter_wo_bk(sm_num, accessible_loc, door, odd_ctr, current_ctr, key_layout)
|
||||
blocked_loc = key_layout.all_locations.difference(accessible_loc)
|
||||
|
||||
if len(blocked_loc) > 0:
|
||||
rule = PlacementRule()
|
||||
rule.door_reference = door
|
||||
rule.small_key = key_logic.small_key_name
|
||||
rule.needed_keys_w_bk = sm_num
|
||||
placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player)
|
||||
rule.check_locations_w_bk = accessible_loc
|
||||
if worst_ctr_wo_bk:
|
||||
accessible_wo_bk, post_set = set(), set()
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||
post_set.update(post_ctr.free_locations)
|
||||
post_set.update(post_ctr.key_only_locations)
|
||||
blocked_wo_bk = post_set.difference(accessible_wo_bk)
|
||||
if len(blocked_wo_bk) > 0:
|
||||
rule.bk_conditional_set = blocked_wo_bk
|
||||
rule.needed_keys_wo_bk = alt_num
|
||||
# can this self lock a key if bk not avail? I'm thinking no.
|
||||
# placement_self_lock_adjustment(rule, key_layout, ???, worst_ctr_wo_bk, world, player)
|
||||
rule.check_locations_wo_bk = accessible_wo_bk
|
||||
key_logic.placement_rules.append(rule)
|
||||
if worst_ctr_wo_bk:
|
||||
check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num)
|
||||
|
||||
|
||||
def check_bk_restriction_needed(key_layout, worst_ctr_wo_bk, post_ctr, alt_num):
|
||||
avail_keys = len(worst_ctr_wo_bk.key_only_locations)
|
||||
place_able_keys = min(key_layout.max_chests, len(worst_ctr_wo_bk.free_locations))
|
||||
if avail_keys + place_able_keys < alt_num:
|
||||
accessible_wo_bk, post_set = set(), set()
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.free_locations)
|
||||
accessible_wo_bk.update(worst_ctr_wo_bk.key_only_locations)
|
||||
post_set.update(post_ctr.free_locations)
|
||||
post_set.update(post_ctr.key_only_locations)
|
||||
key_layout.key_logic.bk_restricted.update(post_set.difference(accessible_wo_bk))
|
||||
|
||||
|
||||
def placement_self_lock_adjustment(rule, key_layout, blocked_loc, worst_ctr, world, player):
|
||||
if len(blocked_loc) == 1 and world.accessibility[player] != 'locations':
|
||||
max_ctr = find_max_counter(key_layout)
|
||||
blocked_others = set(max_ctr.other_locations).difference(set(worst_ctr.other_locations))
|
||||
important_found = False
|
||||
for loc in blocked_others:
|
||||
if important_location(loc, world, player):
|
||||
important_found = True
|
||||
break
|
||||
if not important_found:
|
||||
rule.needed_keys_w_bk -= 1
|
||||
|
||||
|
||||
def count_key_drops(sector):
|
||||
cnt = 0
|
||||
@@ -211,6 +314,8 @@ def find_bk_locked_sections(key_layout, world):
|
||||
big_chest_allowed_big_key = world.accessibility != 'locations'
|
||||
for counter in key_counters.values():
|
||||
key_layout.all_chest_locations.update(counter.free_locations)
|
||||
key_layout.all_locations.update(counter.free_locations)
|
||||
key_layout.all_locations.update(counter.key_only_locations)
|
||||
if counter.big_key_opened and counter.important_location:
|
||||
big_chest_allowed_big_key = False
|
||||
if not counter.big_key_opened:
|
||||
@@ -241,6 +346,28 @@ def relative_empty_counter(odd_counter, key_counter):
|
||||
return True
|
||||
|
||||
|
||||
def relative_empty_counter_2(odd_counter, key_counter):
|
||||
if len(set(odd_counter.key_only_locations).difference(key_counter.key_only_locations)) > 0:
|
||||
return False
|
||||
if len(set(odd_counter.free_locations).difference(key_counter.free_locations)) > 0:
|
||||
return False
|
||||
for child in odd_counter.child_doors:
|
||||
if unique_child_door_2(child, key_counter):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def progressive_ctr(new_counter, last_counter):
|
||||
if len(set(new_counter.key_only_locations).difference(last_counter.key_only_locations)) > 0:
|
||||
return True
|
||||
if len(set(new_counter.free_locations).difference(last_counter.free_locations)) > 0:
|
||||
return True
|
||||
for child in new_counter.child_doors:
|
||||
if unique_child_door_2(child, last_counter):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def unique_child_door(child, key_counter):
|
||||
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
@@ -251,6 +378,14 @@ def unique_child_door(child, key_counter):
|
||||
return True
|
||||
|
||||
|
||||
def unique_child_door_2(child, key_counter):
|
||||
if child in key_counter.child_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
if child in key_counter.open_doors or child.dest in key_counter.child_doors:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def find_best_counter(door, odd_counter, key_counter, key_layout, world, player, skip_bk, empty_flag): # try to waste as many keys as possible?
|
||||
ignored_doors = {door, door.dest} if door is not None else {}
|
||||
finished = False
|
||||
@@ -280,7 +415,34 @@ def find_best_counter(door, odd_counter, key_counter, key_layout, world, player,
|
||||
return last_counter
|
||||
|
||||
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
||||
def find_worst_counter(door, odd_counter, key_counter, key_layout, skip_bk): # try to waste as many keys as possible?
|
||||
ignored_doors = {door, door.dest} if door is not None else {}
|
||||
finished = False
|
||||
opened_doors = dict(key_counter.open_doors)
|
||||
bk_opened = key_counter.big_key_opened
|
||||
# new_counter = key_counter
|
||||
last_counter = key_counter
|
||||
while not finished:
|
||||
door_set = find_potential_open_doors(last_counter, ignored_doors, key_layout, skip_bk, 0)
|
||||
if door_set is None or len(door_set) == 0:
|
||||
finished = True
|
||||
continue
|
||||
for new_door in door_set:
|
||||
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
|
||||
if not new_door.bigKey and progressive_ctr(new_counter, last_counter) and relative_empty_counter_2(odd_counter, new_counter):
|
||||
ignored_doors.add(new_door)
|
||||
else:
|
||||
last_counter = new_counter
|
||||
opened_doors = proposed_doors
|
||||
bk_opened = bk_open
|
||||
# this means the new_door invalidates the door / leads to the same stuff
|
||||
return last_counter
|
||||
|
||||
|
||||
def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk, reserve=1):
|
||||
small_doors = []
|
||||
big_doors = []
|
||||
for other in key_counter.child_doors:
|
||||
@@ -293,7 +455,7 @@ def find_potential_open_doors(key_counter, ignored_doors, key_layout, skip_bk):
|
||||
if key_layout.big_key_special:
|
||||
big_key_available = key_counter.big_key_opened
|
||||
else:
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(1) > 0
|
||||
big_key_available = len(key_counter.free_locations) - key_counter.used_smalls_loc(reserve) > 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 + big_doors
|
||||
@@ -367,7 +529,7 @@ def create_rule(key_counter, prev_counter, key_layout, world, player):
|
||||
|
||||
|
||||
def check_for_self_lock_key(rule, door, parent_counter, key_layout, world, player):
|
||||
if world.accessibility != 'locations':
|
||||
if world.accessibility[player] != 'locations':
|
||||
counter = find_inverted_counter(door, parent_counter, key_layout, world, player)
|
||||
if not self_lock_possible(counter):
|
||||
return
|
||||
@@ -489,6 +651,27 @@ def bk_restricted_rules(rule, door, odd_counter, empty_flag, key_counter, key_la
|
||||
# key_layout.key_logic.bk_restricted.update(unique_loc)
|
||||
|
||||
|
||||
def find_worst_counter_wo_bk(small_key_num, accessible_set, door, odd_ctr, key_counter, key_layout):
|
||||
if key_counter.big_key_opened:
|
||||
return None, None, None
|
||||
worst_counter = find_worst_counter(door, odd_ctr, key_counter, key_layout, True)
|
||||
bk_rule_num = worst_counter.used_keys + 1
|
||||
bk_access_set = set()
|
||||
bk_access_set.update(worst_counter.free_locations)
|
||||
bk_access_set.update(worst_counter.key_only_locations)
|
||||
if bk_rule_num == small_key_num and len(bk_access_set ^ accessible_set) == 0:
|
||||
return None, None, None
|
||||
door_open = find_next_counter(door, worst_counter, key_layout)
|
||||
ignored_doors = dict_intersection(worst_counter.child_doors, door_open.child_doors)
|
||||
dest_ignored = []
|
||||
for door in ignored_doors.keys():
|
||||
if door.dest not in ignored_doors:
|
||||
dest_ignored.append(door.dest)
|
||||
ignored_doors = {**ignored_doors, **dict.fromkeys(dest_ignored)}
|
||||
post_counter = open_some_counter(door_open, key_layout, ignored_doors.keys())
|
||||
return worst_counter, post_counter, bk_rule_num
|
||||
|
||||
|
||||
def open_a_door(door, child_state, flat_proposal):
|
||||
if door.bigKey:
|
||||
child_state.big_key_opened = True
|
||||
@@ -833,6 +1016,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
|
||||
available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player)
|
||||
if invalid_self_locking_key(state, prev_state, prev_avail, world, player):
|
||||
return False
|
||||
# todo: allow more key shuffles - refine placement rules
|
||||
# if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||
if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0):
|
||||
return False
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user