-Multiworld key rule fix

-Minor generation fix
This commit is contained in:
aerinon
2020-02-25 15:16:18 -07:00
parent 72a94e1e4f
commit 1dbd7e95e2
3 changed files with 111 additions and 15 deletions

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

@@ -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.17.1p' __version__ = '0.0.17.2p'
def main(args, seed=None): def main(args, seed=None):