diff --git a/BaseClasses.py b/BaseClasses.py index c125d08e..0cad0326 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1024,6 +1024,30 @@ class Direction(Enum): Up = 4 Down = 5 +@unique +class Hook(Enum): + North = 0 + West = 1 + South = 2 + East = 3 + Stairs = 4 + + +hook_dir_map = { + Direction.North: Hook.North, + Direction.South: Hook.South, + Direction.West: Hook.West, + Direction.East: Hook.East, +} + + +def hook_from_door(door): + if door.type == DoorType.SpiralStairs: + return Hook.Stairs + if door.type == DoorType.Normal: + return hook_dir_map[door.direction] + return None + class Polarity: def __init__(self): @@ -1282,6 +1306,13 @@ class Sector(object): magnitude[idx] = magnitude[idx] + 1 return magnitude + def hook_magnitude(self): + magnitude = [0] * len(Hook) + for door in self.outstanding_doors: + idx = hook_from_door(door).value + magnitude[idx] = magnitude[idx] + 1 + return magnitude + def outflow(self): outflow = 0 for door in self.outstanding_doors: diff --git a/DungeonGenerator.py b/DungeonGenerator.py index 36c87738..8f1b06c8 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -2,26 +2,17 @@ import random import collections import itertools from collections import defaultdict, deque -from enum import Enum, unique import logging from functools import reduce import operator as op from typing import List -from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, Sector, PolSlot, flooded_keys +from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys +from BaseClasses import Hook, hook_from_door from Regions import key_only_locations, dungeon_events, flooded_keys_reverse from Dungeons import dungeon_regions -@unique -class Hook(Enum): - North = 0 - West = 1 - South = 2 - East = 3 - Stairs = 4 - - class GraphPiece: def __init__(self): @@ -515,43 +506,32 @@ def filter_for_potential_bk_locations(locations): and x.name not in key_only_locations.keys() and x.name not in ['Agahnim 1', 'Agahnim 2']] -def opposite_h_type(h_type): - type_map = { - Hook.Stairs: Hook.Stairs, - Hook.North: Hook.South, - Hook.South: Hook.North, - Hook.West: Hook.East, - Hook.East: Hook.West, +type_map = { + Hook.Stairs: Hook.Stairs, + Hook.North: Hook.South, + Hook.South: Hook.North, + Hook.West: Hook.East, + Hook.East: Hook.West, +} - } + +def opposite_h_type(h_type): return type_map[h_type] -def hook_from_door(door): - if door.type == DoorType.SpiralStairs: - return Hook.Stairs - if door.type == DoorType.Normal: - dir = { - Direction.North: Hook.North, - Direction.South: Hook.South, - Direction.West: Hook.West, - Direction.East: Hook.East, - } - return dir[door.direction] - return None +hang_dir_map = { + Direction.North: Hook.South, + Direction.South: Hook.North, + Direction.West: Hook.East, + Direction.East: Hook.West, +} def hanger_from_door(door): if door.type == DoorType.SpiralStairs: return Hook.Stairs if door.type == DoorType.Normal: - dir = { - Direction.North: Hook.South, - Direction.South: Hook.North, - Direction.West: Hook.East, - Direction.East: Hook.West, - } - return dir[door.direction] + return hang_dir_map[door.direction] return None @@ -1443,6 +1423,15 @@ def sum_magnitude(sector_list): return result +def sum_hook_magnitude(sector_list): + result = [0] * len(Hook) + for sector in sector_list: + vector = sector.hook_magnitude() + for i in range(len(result)): + result[i] = result[i] + vector[i] + return result + + def sum_polarity(sector_list): pol = Polarity() for sector in sector_list: @@ -1944,6 +1933,7 @@ def balance_split(candidate_sectors, dungeon_map, global_pole): logger = logging.getLogger('') # categorize sectors check_for_forced_dead_ends(dungeon_map, candidate_sectors, global_pole) + check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole) crystal_switches, crystal_barriers, neutral_sectors, polarized_sectors = categorize_sectors(candidate_sectors) leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, global_pole, len(crystal_barriers) > 0) for sector in leftover: @@ -1966,20 +1956,21 @@ def check_for_forced_dead_ends(dungeon_map, candidate_sectors, global_pole): other_sectors = [x for x in candidate_sectors if x not in dead_end_sectors] for name, builder in dungeon_map.items(): other_sectors += builder.sectors - other_magnitude = sum_magnitude(other_sectors) - dead_cnt = [0] * len(PolSlot) + other_magnitude = sum_hook_magnitude(other_sectors) + dead_cnt = [0] * len(Hook) for sector in dead_end_sectors: - pol = sector.polarity() - for slot in PolSlot: - if pol.vector[slot.value] != 0: - dead_cnt[slot.value] += 1 - for slot in PolSlot: - if dead_cnt[slot.value] > other_magnitude[slot.value]: + hook_mag = sector.hook_magnitude() + for hook in Hook: + if hook_mag[hook.value] != 0: + dead_cnt[hook.value] += 1 + for hook in Hook: + opp = opposite_h_type(hook) + if dead_cnt[hook.value] > other_magnitude[opp.value]: raise Exception('Impossible to satisfy all these dead ends') - elif dead_cnt[slot.value] == other_magnitude[slot.value]: - candidates = [x for x in dead_end_sectors if x.magnitude()[slot.value] > 0] + elif dead_cnt[hook.value] == other_magnitude[opp.value]: + candidates = [x for x in dead_end_sectors if x.hook_magnitude()[hook.value] > 0] for sector in other_sectors: - if sector.magnitude()[slot.value] > 0 and sector.is_entrance_sector() and sector.branching_factor() == 2: + if sector.hook_magnitude()[opp.value] > 0 and sector.is_entrance_sector() and sector.branching_factor() == 2: builder = None for b in dungeon_map.values(): if sector in b.sectors: @@ -1996,6 +1987,31 @@ def check_for_forced_dead_ends(dungeon_map, candidate_sectors, global_pole): builder.c_locked = True +def check_for_forced_assignments(dungeon_map, candidate_sectors, global_pole): + done = False + while not done: + done = True + magnitude = sum_hook_magnitude(candidate_sectors) + dungeon_hooks = {} + for name, builder in dungeon_map.items(): + dungeon_hooks[name] = sum_hook_magnitude(builder.sectors) + for val in Hook: + if magnitude[val.value] == 1: + found_hooks = [] + opp = opposite_h_type(val) + for name, hooks in dungeon_hooks.items(): + if hooks[opp.value] > 0 and not dungeon_map[name].c_locked: + found_hooks.append(name) + if len(found_hooks) == 1: + done = False + forced_sector = None + for sec in candidate_sectors: + if sec.hook_magnitude()[val.value] > 0: + forced_sector = sec + break + assign_sector(forced_sector, dungeon_map[found_hooks[0]], candidate_sectors, global_pole) + + def check_crystal(dead_end, entrance): if dead_end.blue_barrier and not entrance.c_switch and not dead_end.c_switch: return False diff --git a/ItemList.py b/ItemList.py index 971424fc..d7861cc0 100644 --- a/ItemList.py +++ b/ItemList.py @@ -265,10 +265,10 @@ def generate_itempool(world, player): amt = world.pool_adjustment[player] if amt < 0: for i in range(0, amt): - pool.remove(get_custom_array_key('Rupees (20)')) + pool.remove('Rupees (20)') elif amt > 0: for i in range(0, amt): - pool.append(get_custom_array_key('Rupees (20)')) + pool.append('Rupees (20)') for item in precollected_items: world.push_precollected(ItemFactory(item, player)) diff --git a/Main.py b/Main.py index dafd3b9c..0ef877e9 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.f-dev' +__version__ = '0.0.g-dev' def main(args, seed=None):