Merge pull request #2 from aerinon/DoorDev

Update to DoorDev
This commit is contained in:
Mike A. Trethewey
2020-02-26 01:40:06 -08:00
committed by GitHub
5 changed files with 117 additions and 21 deletions

View File

@@ -160,7 +160,7 @@ def vanilla_key_logic(world, player):
world.key_logic[player][builder.name] = key_layout.key_logic world.key_logic[player][builder.name] = key_layout.key_logic
log_key_logic(builder.name, key_layout.key_logic) log_key_logic(builder.name, key_layout.key_logic)
last_key = None last_key = None
if world.shuffle[player] == 'vanilla': if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items':
validate_vanilla_key_logic(world, player) validate_vanilla_key_logic(world, player)

View File

@@ -1,5 +1,6 @@
import random import random
import collections import collections
import itertools
from collections import defaultdict, deque from collections import defaultdict, deque
from enum import Enum, unique from enum import Enum, unique
import logging import logging
@@ -1467,20 +1468,32 @@ def assign_polarized_sectors(dungeon_map, polarized_sectors, global_pole, logger
problem_builders = identify_branching_issues_2(problem_builders) problem_builders = identify_branching_issues_2(problem_builders)
# step 5: assign randomly until gone - must maintain connectedness, neutral polarity, branching, lack, etc. # step 5: assign randomly until gone - must maintain connectedness, neutral polarity, branching, lack, etc.
comb_w_replace = len(dungeon_map) ** len(neutral_choices)
combinations = None
if comb_w_replace <= 1000:
combinations = list(itertools.product(dungeon_map.keys(), repeat=len(neutral_choices)))
random.shuffle(combinations)
tries = 0 tries = 0
while len(polarized_sectors) > 0: while len(polarized_sectors) > 0:
if tries > 100: if tries > 1000 or (combinations and tries >= len(combinations)):
raise Exception('No valid assignment found. Ref: %s' % next(iter(dungeon_map.keys()))) raise Exception('No valid assignment found. Ref: %s' % next(iter(dungeon_map.keys())))
choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices)) if combinations:
valid = [] choices = combinations[tries]
else:
choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices))
chosen_sectors = defaultdict(list)
for i, choice in enumerate(choices): for i, choice in enumerate(choices):
builder = dungeon_map[choice] chosen_sectors[choice].extend(neutral_choices[i])
if valid_assignment(builder, neutral_choices[i]): all_valid = True
for name, sector_list in chosen_sectors.items():
if not valid_assignment(dungeon_map[name], sector_list):
all_valid = False
break
if all_valid:
for i, choice in enumerate(choices):
builder = dungeon_map[choice]
for sector in neutral_choices[i]: for sector in neutral_choices[i]:
assign_sector(sector, builder, polarized_sectors, global_pole) assign_sector(sector, builder, polarized_sectors, global_pole)
valid.append(neutral_choices[i])
for c in valid:
neutral_choices.remove(c)
tries += 1 tries += 1
@@ -1931,6 +1944,7 @@ def resolve_equations(builder, sector_list):
# negative benefit transforms (dead end) # negative benefit transforms (dead end)
def find_priority_equation(equations, current_access): def find_priority_equation(equations, current_access):
flex = calc_flex(equations, current_access) flex = calc_flex(equations, current_access)
required = calc_required(equations, current_access)
best_profit = None best_profit = None
triplet_candidates = [] triplet_candidates = []
local_profit_map = {} local_profit_map = {}
@@ -1951,14 +1965,17 @@ def find_priority_equation(equations, current_access):
else: else:
triplet_candidates.append((eq, eq_list, sector)) triplet_candidates.append((eq, eq_list, sector))
local_profit_map[sector] = best_local_profit local_profit_map[sector] = best_local_profit
if len(triplet_candidates) == 0: filtered_candidates = filter_requirements(triplet_candidates, equations, required, current_access)
if len(filtered_candidates) == 0:
filtered_candidates = triplet_candidates
if len(filtered_candidates) == 0:
return None, None, None # can't pay for anything return None, None, None # can't pay for anything
if len(triplet_candidates) == 1: if len(filtered_candidates) == 1:
return triplet_candidates[0] return filtered_candidates[0]
required_candidates = [x for x in triplet_candidates if x[0].required] required_candidates = [x for x in filtered_candidates if x[0].required]
if len(required_candidates) == 0: if len(required_candidates) == 0:
required_candidates = triplet_candidates required_candidates = filtered_candidates
if len(required_candidates) == 1: if len(required_candidates) == 1:
return required_candidates[0] return required_candidates[0]
@@ -1974,6 +1991,46 @@ def find_priority_equation(equations, current_access):
return good_local_candidates[0] # just pick one I guess return good_local_candidates[0] # just pick one I guess
def calc_required(equations, current_access):
ttl = 0
for num in current_access.values():
ttl += num
local_profit_map = {}
for sector, eq_list in equations.items():
best_local_profit = None
for eq in eq_list:
profit = eq.profit()
if best_local_profit is None or profit > best_local_profit:
best_local_profit = profit
local_profit_map[sector] = best_local_profit
ttl += best_local_profit
if ttl == 0:
new_lists = {}
for sector, eq_list in equations.items():
if len(eq_list) > 1:
rem_list = []
for eq in eq_list:
if eq.profit() < local_profit_map[sector]:
rem_list.append(eq)
if len(rem_list) > 0:
new_lists[sector] = [x for x in eq_list if x not in rem_list]
for sector, eq_list in new_lists.items():
if len(eq_list) <= 1:
for eq in eq_list:
eq.required = True
equations[sector] = eq_list
required_costs = defaultdict(int)
required_benefits = defaultdict(int)
for sector, eq_list in equations.items():
for eq in eq_list:
if eq.required:
for key, door_list in eq.cost.items():
required_costs[key] += len(door_list)
for key, door_list in eq.benefit.items():
required_benefits[key] += len(door_list)
return required_costs, required_benefits
def calc_flex(equations, current_access): def calc_flex(equations, current_access):
flex_spending = defaultdict(int) flex_spending = defaultdict(int)
required_costs = defaultdict(int) required_costs = defaultdict(int)
@@ -1987,6 +2044,45 @@ def calc_flex(equations, current_access):
return flex_spending return flex_spending
def filter_requirements(triplet_candidates, equations, required, current_access):
r_costs, r_exits = required
valid_candidates = []
for cand, cand_list, cand_sector in triplet_candidates:
valid = True
if not cand.required:
potential_benefit = defaultdict(int)
potential_costs = defaultdict(int)
for h_type, benefit in current_access.items():
cur_cost = len(cand.cost[h_type])
if benefit - cur_cost > 0:
potential_benefit[h_type] += benefit - cur_cost
for h_type, benefit_list in cand.benefit.items():
potential_benefit[h_type] += len(benefit_list)
for sector, eq_list in equations.items():
if sector == cand_sector:
affected_doors = [d for x in cand.benefit.values() for d in x] + [d for x in cand.cost.values() for d in x]
adj_list = [x for x in eq_list if x.door not in affected_doors]
else:
adj_list = eq_list
for eq in adj_list:
for h_type, benefit_list in eq.benefit.items():
potential_benefit[h_type] += len(benefit_list)
for h_type, cost_list in eq.cost.items():
potential_costs[h_type] += len(cost_list)
for h_type, requirement in r_costs.items():
if requirement > 0 and potential_benefit[h_type] < requirement:
valid = False
break
if valid:
for h_type, requirement in r_exits.items():
if requirement > 0 and potential_costs[h_type] < requirement:
valid = False
break
if valid:
valid_candidates.append((cand, cand_list, cand_sector))
return valid_candidates
def resolve_equation(equation, eq_list, sector, current_access, reached_doors, equations): def resolve_equation(equation, eq_list, sector, current_access, reached_doors, equations):
for key, door_list in equation.cost.items(): for key, door_list in equation.cost.items():
if current_access[key] - len(door_list) < 0: if current_access[key] - len(door_list) < 0:

View File

@@ -233,7 +233,7 @@ def valid_key_placement(item, location, itempool, world):
if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name):
return True return True
key_logic = world.key_logic[item.player][dungeon.name] 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]) unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player])
return key_logic.check_placement(unplaced_keys) return key_logic.check_placement(unplaced_keys)
else: else:
inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player])

View File

@@ -147,7 +147,7 @@ def analyze_dungeon(key_layout, world, player):
key_layout.key_counters = create_key_counters(key_layout, world, player) key_layout.key_counters = create_key_counters(key_layout, world, player)
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
find_bk_locked_sections(key_layout, world) find_bk_locked_sections(key_layout, world, player)
key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations)) key_logic.bk_chests.update(find_big_chest_locations(key_layout.all_chest_locations))
if world.retro[player] and world.mode[player] != 'standard': if world.retro[player] and world.mode[player] != 'standard':
return return
@@ -304,14 +304,14 @@ def queue_sorter_2(queue_item):
return 1 if door.bigKey else 0 return 1 if door.bigKey else 0
def find_bk_locked_sections(key_layout, world): def find_bk_locked_sections(key_layout, world, player):
if key_layout.big_key_special: if key_layout.big_key_special:
return return
key_counters = key_layout.key_counters key_counters = key_layout.key_counters
key_logic = key_layout.key_logic key_logic = key_layout.key_logic
bk_key_not_required = set() bk_key_not_required = set()
big_chest_allowed_big_key = world.accessibility != 'locations' big_chest_allowed_big_key = world.accessibility[player] != 'locations'
for counter in key_counters.values(): for counter in key_counters.values():
key_layout.all_chest_locations.update(counter.free_locations) key_layout.all_chest_locations.update(counter.free_locations)
key_layout.all_locations.update(counter.free_locations) key_layout.all_locations.update(counter.free_locations)
@@ -1450,8 +1450,8 @@ def validate_key_placement(key_layout, world, player):
if not can_progress: if not can_progress:
missing_locations = set(max_counter.free_locations.keys()).difference(found_locations) missing_locations = set(max_counter.free_locations.keys()).difference(found_locations)
missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name] missing_items = [l for l in missing_locations if l.item is None or (l.item.name != smallkey_name and l.item != dungeon.big_key) or "- Boss" in l.name]
#missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations? # missing_key_only = set(max_counter.key_only_locations.keys()).difference(counter.key_only_locations.keys()) # do freestanding keys matter for locations?
if len(missing_items) > 0: #world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0): if len(missing_items) > 0: # world.accessibility[player]=='locations' and (len(missing_locations)>0 or len(missing_key_only) > 0):
logging.getLogger('').error("Keylock - can't open locations: ") logging.getLogger('').error("Keylock - can't open locations: ")
for i in missing_locations: for i in missing_locations:
logging.getLogger('').error(i) logging.getLogger('').error(i)

View File

@@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.0.17pre' __version__ = '0.0.17.2p'
def main(args, seed=None): def main(args, seed=None):