Made the CollectionState crystal switch aware and added crystal rules

Small adjustment to big key forbidding
This commit is contained in:
aerinon
2020-01-06 15:32:33 -07:00
parent c475dc746c
commit f26c83e852
6 changed files with 137 additions and 17 deletions

View File

@@ -2,7 +2,7 @@ import copy
from enum import Enum, unique from enum import Enum, unique
import logging import logging
import json import json
from collections import OrderedDict from collections import OrderedDict, deque
from _vendor.collections_extended import bag from _vendor.collections_extended import bag
from Utils import int16_as_bytes from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table from Tables import normal_offset_table, spiral_offset_table
@@ -355,6 +355,7 @@ class CollectionState(object):
self.prog_items = bag() self.prog_items = bag()
self.world = parent self.world = parent
self.reachable_regions = {player: set() for player in range(1, parent.players + 1)} self.reachable_regions = {player: set() for player in range(1, parent.players + 1)}
self.colored_regions = {player: {} for player in range(1, parent.players + 1)}
self.events = [] self.events = []
self.path = {} self.path = {}
self.locations_checked = set() self.locations_checked = set()
@@ -366,6 +367,7 @@ class CollectionState(object):
player_regions = [region for region in self.world.regions if region.player == player] player_regions = [region for region in self.world.regions if region.player == player]
self.stale[player] = False self.stale[player] = False
rrp = self.reachable_regions[player] rrp = self.reachable_regions[player]
ccr = self.colored_regions[player]
new_regions = True new_regions = True
reachable_regions_count = len(rrp) reachable_regions_count = len(rrp)
while new_regions: while new_regions:
@@ -373,13 +375,53 @@ class CollectionState(object):
for candidate in possible: for candidate in possible:
if candidate.can_reach_private(self): if candidate.can_reach_private(self):
rrp.add(candidate) rrp.add(candidate)
if candidate.type == RegionType.Dungeon:
c_switch_present = False
for ext in candidate.exits:
door = self.world.check_for_door(ext.name, player)
if door is not None and door.crystal == CrystalBarrier.Either:
c_switch_present = True
if c_switch_present:
ccr[candidate] = CrystalBarrier.Either
self.spread_crystal_access(candidate, CrystalBarrier.Either, rrp, ccr, player)
else:
for entrance in candidate.entrances:
door = self.world.check_for_door(entrance.name, player)
if door is None or entrance.parent_region.type != RegionType.Dungeon:
ccr[candidate] = CrystalBarrier.Orange
if entrance.parent_region in ccr.keys():
color_type = ccr[entrance.parent_region]
current_type = ccr[candidate] if candidate in ccr.keys() else None
ccr[candidate] = color_type if current_type is None or color_type == current_type else CrystalBarrier.Either
new_regions = len(rrp) > reachable_regions_count new_regions = len(rrp) > reachable_regions_count
reachable_regions_count = len(rrp) reachable_regions_count = len(rrp)
def spread_crystal_access(self, region, crystal, rrp, ccr, player):
queue = deque([(region, crystal)])
visited = set()
while len(queue) > 0:
region, crystal = queue.popleft()
visited.add(region)
for ext in region.exits:
connect = ext.connected_region
if connect not in visited and connect is not None and connect.type == RegionType.Dungeon:
if connect in rrp and ext.can_reach(self) and connect:
door = self.world.check_for_door(ext.name, player)
current_crystal = ccr[connect]
if door is not None and not door.blocked and current_crystal != crystal and current_crystal != CrystalBarrier.Either:
if door.crystal in [CrystalBarrier.Either, CrystalBarrier.Null]:
ccr[connect] = crystal
queue.append((connect, crystal))
else:
queue.append((connect, door.crystal))
if door.crystal != current_crystal:
ccr[connect] = CrystalBarrier.Either
def copy(self): def copy(self):
ret = CollectionState(self.world) ret = CollectionState(self.world)
ret.prog_items = self.prog_items.copy() ret.prog_items = self.prog_items.copy()
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)} ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
ret.colored_regions = {player: copy.copy(self.colored_regions[player]) for player in range(1, self.world.players + 1)}
ret.events = copy.copy(self.events) ret.events = copy.copy(self.events)
ret.path = copy.copy(self.path) ret.path = copy.copy(self.path)
ret.locations_checked = copy.copy(self.locations_checked) ret.locations_checked = copy.copy(self.locations_checked)
@@ -400,6 +442,15 @@ class CollectionState(object):
return spot.can_reach(self) return spot.can_reach(self)
def sweep_for_crystal_access(self):
for player, rrp in self.reachable_regions.items():
dungeon_regions = [x for x in rrp if x.type == RegionType.Dungeon]
ccr = self.colored_regions[player]
for region in dungeon_regions:
if region in ccr.keys():
self.spread_crystal_access(region, ccr[region], rrp, ccr, player)
self.stale[player] = True
def sweep_for_events(self, key_only=False, locations=None): def sweep_for_events(self, key_only=False, locations=None):
# this may need improvement # this may need improvement
new_locations = True new_locations = True
@@ -415,6 +466,18 @@ class CollectionState(object):
self.collect(event.item, True, event) self.collect(event.item, True, event)
new_locations = len(reachable_events) > checked_locations new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events) checked_locations = len(reachable_events)
if new_locations:
self.sweep_for_crystal_access()
def can_reach_blue(self, region, player):
if region not in self.colored_regions[player].keys():
return False
return self.colored_regions[player][region] in [CrystalBarrier.Blue, CrystalBarrier.Either]
def can_reach_orange(self, region, player):
if region not in self.colored_regions[player].keys():
return False
return self.colored_regions[player][region] in [CrystalBarrier.Orange, CrystalBarrier.Either]
def _do_not_flood_the_keys(self, reachable_events): def _do_not_flood_the_keys(self, reachable_events):
adjusted_checks = list(reachable_events) adjusted_checks = list(reachable_events)
@@ -699,7 +762,7 @@ class CollectionState(object):
class RegionType(Enum): class RegionType(Enum):
LightWorld = 1 LightWorld = 1
DarkWorld = 2 DarkWorld = 2
Cave = 3 # Also includes Houses Cave = 3 # Also includes Houses
Dungeon = 4 Dungeon = 4
@property @property

View File

@@ -1034,7 +1034,7 @@ def reassign_key_doors(builder, proposal, world, player):
room.delete(d.doorListPos) room.delete(d.doorListPos)
else: else:
if len(room.doorList) > 1: if len(room.doorList) > 1:
room.mirror(d.doorListPos) room.mirror(d.doorListPos) # todo: I don't think this works for crossed - maybe it will
else: else:
room.delete(d.doorListPos) room.delete(d.doorListPos)
d.smallKey = False d.smallKey = False

View File

@@ -1084,11 +1084,12 @@ def create_doors(world, player):
world.get_door('Hera Startile Wide Holes', player).c_switch() world.get_door('Hera Startile Wide Holes', player).c_switch()
world.get_door('PoD Arena Main SW', player).c_switch() world.get_door('PoD Arena Main SW', player).c_switch()
world.get_door('PoD Arena Bonk Path', player).c_switch()
world.get_door('PoD Arena Bridge SE', player).c_switch() world.get_door('PoD Arena Bridge SE', player).c_switch()
world.get_door('PoD Arena Bridge Drop Down', player).c_switch()
world.get_door('PoD Arena Main Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('PoD Arena Main Orange Barrier', player).barrier(CrystalBarrier.Orange)
# maybe you can cross this way with blue up?? # maybe you can cross this way with blue up??
world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue) world.get_door('PoD Arena Main Crystal Path', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Arena Crystals E', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue) world.get_door('PoD Arena Crystal Path', player).barrier(CrystalBarrier.Blue)
world.get_door('PoD Sexy Statue W', player).c_switch() world.get_door('PoD Sexy Statue W', player).c_switch()
world.get_door('PoD Sexy Statue NW', player).c_switch() world.get_door('PoD Sexy Statue NW', player).c_switch()
@@ -1185,6 +1186,7 @@ def create_doors(world, player):
world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('GT Hookshot Entry Blue Barrier', player).barrier(CrystalBarrier.Blue)
world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Orange Barrier', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Orange Barrier 2', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Orange Path', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange) world.get_door('GT Double Switch Key Orange Path', player).barrier(CrystalBarrier.Orange)
world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue) world.get_door('GT Double Switch Key Blue Path', player).barrier(CrystalBarrier.Blue)
world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue) world.get_door('GT Double Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)

View File

@@ -118,9 +118,9 @@ def analyze_dungeon(key_layout, world, player):
raw_avail = chest_keys + len(key_counter.key_only_locations) raw_avail = chest_keys + len(key_counter.key_only_locations)
available = raw_avail - key_counter.used_keys available = raw_avail - key_counter.used_keys
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
avail_bigs = count_unique_big_doors(key_counter) avail_bigs = exist_relevant_big_doors(key_counter, key_layout)
if not key_counter.big_key_opened: if not key_counter.big_key_opened:
if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and avail_bigs == 0: if chest_keys == count_locations_big_optional(key_counter.free_locations) and available <= possible_smalls and not avail_bigs:
key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations)) key_logic.bk_restricted.update(filter_big_chest(key_counter.free_locations))
if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations): if not key_counter.big_key_opened and big_chest_in_locations(key_counter.free_locations):
key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations)) key_logic.sm_restricted.update(find_big_chest_locations(key_counter.free_locations))
@@ -318,6 +318,7 @@ def create_rule(key_counter, prev_counter, key_layout, world):
available = raw_avail - key_counter.used_keys available = raw_avail - key_counter.used_keys
possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop) possible_smalls = count_unique_small_doors(key_counter, key_layout.flat_prop)
required_keys = min(available, possible_smalls) + key_counter.used_keys required_keys = min(available, possible_smalls) + key_counter.used_keys
# required_keys = key_counter.used_keys + 1 # this sometimes makes more sense
# if prev_avail < required_keys: # if prev_avail < required_keys:
# required_keys = prev_avail + prev_counter.used_keys # required_keys = prev_avail + prev_counter.used_keys
# return DoorRules(required_keys) # return DoorRules(required_keys)
@@ -487,15 +488,16 @@ def count_unique_small_doors(key_counter, proposal):
return cnt return cnt
def count_unique_big_doors(key_counter): def exist_relevant_big_doors(key_counter, key_layout):
cnt = 0 bk_counter = find_counter(key_counter.open_doors, True, key_layout, False)
counted = set() if bk_counter is not None:
for door in key_counter.child_doors: diff = dict_difference(bk_counter.free_locations, key_counter.free_locations)
if door.bigKey and door not in counted: if len(diff) > 0:
cnt += 1 return True
counted.add(door) diff = dict_difference(bk_counter.key_only_locations, key_counter.key_only_locations)
counted.add(door.dest) if len(diff) > 0:
return cnt return True
return False
def count_locations_big_optional(locations, bk=False): def count_locations_big_optional(locations, bk=False):
@@ -788,7 +790,7 @@ def state_id(state, flat_proposal):
return s_id return s_id
def find_counter(opened_doors, bk_hint, key_layout): def find_counter(opened_doors, bk_hint, key_layout, raise_on_error=True):
counter = find_counter_hint(opened_doors, bk_hint, key_layout) counter = find_counter_hint(opened_doors, bk_hint, key_layout)
if counter is not None: if counter is not None:
return counter return counter
@@ -801,7 +803,9 @@ def find_counter(opened_doors, bk_hint, key_layout):
counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout) counter = find_counter_hint(dict.fromkeys(more_doors), bk_hint, key_layout)
if counter is not None: if counter is not None:
return counter return counter
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop)) if raise_on_error:
raise Exception('Unable to find door permutation. Init CID: %s' % counter_id(opened_doors, bk_hint, key_layout.flat_prop))
return None
def find_counter_hint(opened_doors, bk_hint, key_layout): def find_counter_hint(opened_doors, bk_hint, key_layout):

View File

@@ -325,6 +325,7 @@ def create_playthrough(world):
while sphere_candidates: while sphere_candidates:
if not world.keysanity: if not world.keysanity:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = [] sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
@@ -379,6 +380,7 @@ def create_playthrough(world):
while required_locations: while required_locations:
if not world.keysanity: if not world.keysanity:
state.sweep_for_events(key_only=True) state.sweep_for_events(key_only=True)
state.sweep_for_crystal_access()
sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations)) sphere = list(filter(lambda loc: state.can_reach(loc) and state.not_flooding_a_key(world, loc), required_locations))

View File

@@ -423,6 +423,55 @@ def global_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
add_key_logic_rules(world, player) add_key_logic_rules(world, player)
# crystal switch rules
set_rule(world.get_entrance('PoD Arena Crystal Path', player), lambda state: state.can_reach_blue(world.get_region('PoD Arena Crystal', player), player))
set_rule(world.get_entrance('Swamp Trench 2 Pots Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Trench 2 Pots', player), player))
set_rule(world.get_entrance('Swamp Shortcut Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Swamp Shortcut', player), player))
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
set_rule(world.get_entrance('Thieves Hellway Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway', player), player))
set_rule(world.get_entrance('Thieves Hellway Crystal Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Hellway N Crystal', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass SE', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass WN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Thieves Triple Bypass EN', player), lambda state: state.can_reach_blue(world.get_region('Thieves Triple Bypass', player), player))
set_rule(world.get_entrance('Ice Crystal Right Blue Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Right', player), player))
set_rule(world.get_entrance('Ice Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Ice Crystal Left', player), player))
set_rule(world.get_entrance('Ice Backwards Room Hole', player), lambda state: state.can_reach_blue(world.get_region('Ice Backwards Room', player), player))
set_rule(world.get_entrance('Mire Hub Upper Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
set_rule(world.get_entrance('Mire Hub Lower Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub', player), player))
set_rule(world.get_entrance('Mire Hub Right Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Right', player), player))
set_rule(world.get_entrance('Mire Hub Top Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Hub Top', player), player))
set_rule(world.get_entrance('Mire Map Spike Side Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spike Side', player), player))
set_rule(world.get_entrance('Mire Map Spot Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Map Spot', player), player))
set_rule(world.get_entrance('Mire Crystal Dead End Left Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
set_rule(world.get_entrance('Mire Crystal Dead End Right Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Dead End', player), player))
set_rule(world.get_entrance('Mire South Fish Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire South Fish', player), player))
set_rule(world.get_entrance('Mire Compass Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Compass Room', player), player))
set_rule(world.get_entrance('Mire Crystal Mid Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Mid', player), player))
set_rule(world.get_entrance('Mire Crystal Left Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Mire Crystal Left', player), player))
set_rule(world.get_entrance('TR Crystal Maze Blue Path', player), lambda state: state.can_reach_blue(world.get_region('TR Crystal Maze End', player), player))
set_rule(world.get_entrance('GT Hookshot Entry Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Hookshot South Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Key Blue Path', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Key Spot', player), player))
set_rule(world.get_entrance('GT Double Switch Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Switches', player), player))
set_rule(world.get_entrance('GT Double Switch Transition Blue', player), lambda state: state.can_reach_blue(world.get_region('GT Double Switch Transition', player), player))
set_rule(world.get_entrance('Swamp Barrier Ledge - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier Ledge', player), player))
set_rule(world.get_entrance('Swamp Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('Swamp Barrier', player), player))
set_rule(world.get_entrance('Thieves Hellway Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway', player), player))
set_rule(world.get_entrance('Thieves Hellway Crystal Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Hellway S Crystal', player), player))
set_rule(world.get_entrance('Ice Bomb Jump Ledge Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Ledge', player), player))
set_rule(world.get_entrance('Ice Bomb Jump Catwalk Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Bomb Jump Catwalk', player), player))
set_rule(world.get_entrance('Ice Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Right', player), player))
set_rule(world.get_entrance('Ice Crystal Left Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Ice Crystal Left', player), player))
set_rule(world.get_entrance('Mire Crystal Right Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Right', player), player))
set_rule(world.get_entrance('Mire Crystal Mid Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Crystal Mid', player), player))
set_rule(world.get_entrance('Mire Firesnake Skip Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Firesnake Skip', player), player))
set_rule(world.get_entrance('Mire Antechamber Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Mire Antechamber', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Barrier 2', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Entry', player), player))
set_rule(world.get_entrance('GT Double Switch Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Switches', player), player))
set_rule(world.get_entrance('GT Double Switch Key Orange Path', player), lambda state: state.can_reach_orange(world.get_region('GT Double Switch Key Spot', player), player))
# End of door rando rules. # End of door rando rules.
add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player)) add_rule(world.get_location('Sunken Treasure', player), lambda state: state.has('Open Floodgate', player))