From 72a94e1e4f92272d1fc36ae588fd9a12bb7f4a08 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 24 Feb 2020 08:53:03 -0700 Subject: [PATCH 1/2] Vanilla fixes for 100% locations --- DoorShuffle.py | 2 +- KeyDoorShuffle.py | 10 +++++----- Main.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 658b5e6b..4e8a475a 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -160,7 +160,7 @@ def vanilla_key_logic(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': + if world.shuffle[player] == 'vanilla' and world.accessibility[player] == 'items': validate_vanilla_key_logic(world, player) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 24b7dbb7..9c3d07eb 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -147,7 +147,7 @@ def analyze_dungeon(key_layout, world, player): key_layout.key_counters = create_key_counters(key_layout, world, player) 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)) if world.retro[player] and world.mode[player] != 'standard': return @@ -304,14 +304,14 @@ def queue_sorter_2(queue_item): 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: return key_counters = key_layout.key_counters key_logic = key_layout.key_logic 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(): key_layout.all_chest_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: 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_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): + # 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): logging.getLogger('').error("Keylock - can't open locations: ") for i in missing_locations: logging.getLogger('').error(i) diff --git a/Main.py b/Main.py index 179a02d4..e45f3764 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.17pre' +__version__ = '0.0.17.1p' def main(args, seed=None): From 1dbd7e95e21ba315022f44647f185d0973ab6e73 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 25 Feb 2020 15:16:18 -0700 Subject: [PATCH 2/2] -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):