From 1dbd7e95e21ba315022f44647f185d0973ab6e73 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 25 Feb 2020 15:16:18 -0700 Subject: [PATCH] -Multiworld key rule fix -Minor generation fix --- DungeonGenerator.py | 122 +++++++++++++++++++++++++++++++++++++++----- Fill.py | 2 +- Main.py | 2 +- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index f79d40fe..e065f6d5 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1,5 +1,6 @@ import random import collections +import itertools from collections import defaultdict, deque from enum import Enum, unique 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) # 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 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()))) - choices = random.choices(list(dungeon_map.keys()), k=len(neutral_choices)) - valid = [] + if combinations: + 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): - builder = dungeon_map[choice] - if valid_assignment(builder, neutral_choices[i]): + chosen_sectors[choice].extend(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]: assign_sector(sector, builder, polarized_sectors, global_pole) - valid.append(neutral_choices[i]) - for c in valid: - neutral_choices.remove(c) tries += 1 @@ -1931,6 +1944,7 @@ def resolve_equations(builder, sector_list): # negative benefit transforms (dead end) def find_priority_equation(equations, current_access): flex = calc_flex(equations, current_access) + required = calc_required(equations, current_access) best_profit = None triplet_candidates = [] local_profit_map = {} @@ -1951,14 +1965,17 @@ def find_priority_equation(equations, current_access): else: triplet_candidates.append((eq, eq_list, sector)) 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 - if len(triplet_candidates) == 1: - return triplet_candidates[0] + if len(filtered_candidates) == 1: + 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: - required_candidates = triplet_candidates + required_candidates = filtered_candidates if len(required_candidates) == 1: 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 +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): flex_spending = defaultdict(int) required_costs = defaultdict(int) @@ -1987,6 +2044,45 @@ def calc_flex(equations, current_access): 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): for key, door_list in equation.cost.items(): if current_access[key] - len(door_list) < 0: diff --git a/Fill.py b/Fill.py index 39a4d24e..54b9581f 100644 --- a/Fill.py +++ b/Fill.py @@ -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): 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]) + 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) else: inside_dungeon_item = ((item.smallkey and not world.keyshuffle[item.player]) diff --git a/Main.py b/Main.py index e45f3764..c23041a0 100644 --- a/Main.py +++ b/Main.py @@ -24,7 +24,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names -__version__ = '0.0.17.1p' +__version__ = '0.0.17.2p' def main(args, seed=None):