Skip to content

Commit

Permalink
Remove and disallow ambiguity in logic files
Browse files Browse the repository at this point in the history
  • Loading branch information
yannl35133 committed Dec 27, 2024
1 parent 7c6f5ee commit cd40881
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 70 deletions.
19 changes: 10 additions & 9 deletions dump.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1109,7 +1109,7 @@ items:
- \Lanayru\Desert\North Part\Lightning Node\Present\Timeshift Stone
- \Lanayru\Desert\North Part\Lightning Node\Past\First Chest
- \Lanayru\Desert\North Part\Lightning Node\Past\Second Chest
- \Lanayru\Desert\North Part\Lightning Node\Past\Lightning Node
- \Lanayru\Desert\North Part\Lightning Node\Past\Activate Lightning Node
- \Lanayru\Desert\North Part\Lightning Node\Present from afar\Raised Chest near Generator
- \Lanayru\Desert\East\Open Wall Shortcut
- \Lanayru\Desert\East\Unlock Statue
Expand All @@ -1123,7 +1123,7 @@ items:
- \Lanayru\Desert\East\Fire Node\Past after Void\Open Grate
- \Lanayru\Desert\East\Fire Node\Past after Grate\Left Ending Chest
- \Lanayru\Desert\East\Fire Node\Past after Grate\Right Ending Chest
- \Lanayru\Desert\East\Fire Node\Past after Grate\Fire Node
- \Lanayru\Desert\East\Fire Node\Past after Grate\Activate Fire Node
- \Lanayru\Temple of Time\South East\Unlock Statue
- \Lanayru\Temple of Time\Front\Gossip Stone in Temple of Time Area
- \Lanayru\Temple of Time\Front\Blow up Rock for Timeshift Stone
Expand Down Expand Up @@ -5591,8 +5591,9 @@ areas:
hint_region: null
locations:
All Three Nodes: "LMF Nodes On option | (\\Lanayru\\Desert\\North Part\\Water\
\ Node & \\Lanayru\\Desert\\North Part\\Lightning Node\\Past\\Lightning\
\ Node & \\Lanayru\\Desert\\East\\Fire Node\\Past after Grate\\Fire Node)"
\ Node & \\Lanayru\\Desert\\North Part\\Lightning Node\\Past\\Activate Lightning\
\ Node & \\Lanayru\\Desert\\East\\Fire Node\\Past after Grate\\Activate\
\ Fire Node)"
Raise Lanayru Mining Facility: "Open LMF option | \\Lanayru\\Desert\\North\
\ Part\\Activate Main Node"
Can Navigate in Oasis: "\\Can Defeat Ampilus | \\Hook Beetle | \\Skyloft\\\
Expand Down Expand Up @@ -6013,7 +6014,7 @@ areas:
locations:
First Chest: 'True'
Second Chest: 'True'
Lightning Node: \Sword
Activate Lightning Node: \Sword
name: \Lanayru\Desert\North Part\Lightning Node\Past
sub_areas: {}
toplevel_alias: null
Expand Down Expand Up @@ -6156,7 +6157,7 @@ areas:
locations:
Left Ending Chest: 'True'
Right Ending Chest: 'True'
Fire Node: \Sword
Activate Fire Node: \Sword
name: \Lanayru\Desert\East\Fire Node\Past after Grate
sub_areas: {}
toplevel_alias: null
Expand Down Expand Up @@ -10971,10 +10972,10 @@ exits:
type: exit
vanilla: Farore's Lair - Entrance from Lake Floria
stage: F102
short_name: Lake Floria - Exit to Farore's Lair
short_name: Lake Floria - Below Rock - Exit to Farore's Lair
\Faron\Lake Floria\Farore's Lair\Exit to Lake Floria:
type: exit
vanilla: Lake Floria - Entrance from Farore's Lair
vanilla: Lake Floria - Below Rock - Entrance from Farore's Lair
stage: F102_2
short_name: Farore's Lair - Exit to Lake Floria
\Faron\Lake Floria\Farore's Lair\Exit to Waterfall:
Expand Down Expand Up @@ -12207,7 +12208,7 @@ entrances:
can-start-at: false
stage: F102
allowed_time_of_day: 1
short_name: Lake Floria - Entrance from Farore's Lair
short_name: Lake Floria - Below Rock - Entrance from Farore's Lair
\Faron\Lake Floria\Farore's Lair\Entrance from Lake Floria:
type: entrance
province: Faron Province
Expand Down
6 changes: 3 additions & 3 deletions entrances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2319,7 +2319,7 @@ Lake Floria - Dive from Faron Woods:
entrance: 0
tod: 2

Lake Floria - Entrance from Farore's Lair:
Lake Floria - Below Rock - Entrance from Farore's Lair:
type: entrance
province: Faron Province
can-start-at: false
Expand All @@ -2330,7 +2330,7 @@ Lake Floria - Entrance from Farore's Lair:
tod: 2


Lake Floria - Exit to Farore's Lair:
Lake Floria - Below Rock - Exit to Farore's Lair:
type: exit
vanilla: Farore's Lair - Entrance from Lake Floria
stage: F102
Expand Down Expand Up @@ -2362,7 +2362,7 @@ Farore's Lair - Entrance from Waterfall:

Farore's Lair - Exit to Lake Floria:
type: exit
vanilla: Lake Floria - Entrance from Farore's Lair
vanilla: Lake Floria - Below Rock - Entrance from Farore's Lair
stage: F102_2
room: 0
index: 0
Expand Down
140 changes: 85 additions & 55 deletions logic/logic_input.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Deque, Dict, Generic, List, Set, Any, Tuple, TypeVar
from collections import deque
from functools import cache
from enum import Enum
from dataclasses import dataclass, field

Expand Down Expand Up @@ -32,6 +32,8 @@ def __missing__(self, item):
LE = TypeVar("LE")
LE_bis = TypeVar("LE_bis")

map_exit_suffixes = None


@dataclass
class Area(Generic[LE]):
Expand All @@ -43,6 +45,7 @@ class Area(Generic[LE]):
can_save: bool = False
abstract: bool = False
sub_areas: Dict[str, Area[LE]] = field(default_factory=dict)
sub_area_aliases: Dict[str, Area[LE]] = field(default_factory=dict)
locations: Dict[str, LE] = field(default_factory=dict)
exits: Dict[str, LE] = field(default_factory=dict)
entrances: Dict[str, str] = field(default_factory=dict)
Expand Down Expand Up @@ -141,6 +144,54 @@ def map(
for sub_area in self.sub_areas.values():
sub_area.map(floc, fexit)

def __getitem__(self, element: str):
return self.lookup(element, direct=True)

def __setitem__(self, alias, area):
self.sub_area_aliases[alias] = area
return

def __hash__(self):
return hash(self.name)

@cache
def lookup(self, element: str, /, direct: bool) -> EXTENDED_ITEM_NAME | Area:
"""
Computes the thing referred by [element] in area [self]. Must not be ambiguous
In a direct search, consider child elements as unambiguous.
"""

assert " - " not in element
found_as_child = None
if (
element in self.locations
or element in self.entrances
or (element in self.exits and element in map_exit_suffixes)
):
# If exit, exclude logical exits and only keep map exits
found_as_child = with_sep_full(self.name, element)

if element in self.sub_areas:
found_as_child = self.sub_areas[element]

if element in self.sub_area_aliases:
found_as_child = self.sub_area_aliases[element]

if direct and found_as_child is not None:
return found_as_child

results = [
r
for sub_area in self.sub_areas.values()
if (r := sub_area.lookup(element, direct=False)) is not None
]

if len(results) == 0:
return found_as_child
if len(results) == 1 and found_as_child is None:
return results[0]
raise ValueError(f"Ambiguous element '{element}' from '{self.name}'.")


class Areas:
areas: Dict[str, Area[DNFInventory]]
Expand All @@ -153,48 +204,36 @@ def search(
) -> EXTENDED_ITEM_NAME:
"""Computes the thing referred by [partial_address] when located at [base_address]"""

queue: Deque[Area] = deque([self.all_areas])
area = self.all_areas
areas = []
area = self.parent_area
for base_addr_atom in base_address_str.split("\\"):
if base_addr_atom == "":
continue
areas.append(area)
area = area.sub_areas[base_addr_atom]
queue.appendleft(area)

partial_address = partial_address_str.split(" - ")
j = 0
if partial_address[0] == "General":
queue = deque([self.all_areas])
area = self.parent_area
j = 1

head = partial_address[j]
while queue:
area = queue.popleft()
if j == len(partial_address):
return EIN(area.name)

if j + 1 == len(partial_address) and (
head in area.locations
or (head in area.entrances)
or (head in area.exits and head in self.map_exit_suffixes)
):
return with_sep_full(area.name, head)

if head in area.sub_areas: # Abandon the base address, we've branched off
if j + 1 == len(partial_address):
return EIN(area.sub_areas[head].name)
queue.clear()
queue.append(area.sub_areas[head])
j += 1
head = partial_address[j]
continue
while area[partial_address[j]] is None and areas:
area = areas.pop()

# Now we search everywhere
queue.extend(area.sub_areas.values())
else:
raise ValueError(
f"Could not find '{partial_address_str}' from '{base_address_str}'."
)
for head in partial_address[j:]:
assert isinstance(area, Area), (base_address_str, partial_address_str, area)
area = area[head]

if area is None:
if base_address_str:
s = f"Could not find '{partial_address_str}' from '{base_address_str}'."
else:
s = f"Could not find '{partial_address_str}'."
raise ValueError(s)
if isinstance(area, Area):
return EIN(area.name)
return area

def prettify(self, s):
if s in ALL_ITEM_NAMES:
Expand Down Expand Up @@ -227,20 +266,11 @@ def __init__(
}

self.parent_area = Area.of_yaml(name="", raw_dict=raw_area)
self.all_areas = Area(
name="",
abstract=True,
exits=self.parent_area.exits,
sub_areas=self.parent_area.sub_areas,
locations=self.parent_area.locations,
)
areas = {}
all_areas = {}
for area in areas_list:
areas[area.name] = area
if area.toplevel_alias is not None:
all_areas[area.toplevel_alias] = area
all_areas |= areas | self.all_areas.sub_areas
self.parent_area[area.toplevel_alias] = area

assert not EXTENDED_ITEM.complete
EXTENDED_ITEM.items_list.extend(events)
Expand All @@ -251,41 +281,42 @@ def __init__(
else:
EXTENDED_ITEM.items_list.append(EIN(area.name))

self.map_exit_suffixes: dict[str, bool] = {}
self.map_exits_entrances: dict[str, str] = {"Exit": "Entrance"}
global map_exit_suffixes
map_exit_suffixes = {}
map_exits_entrances: dict[str, str] = {"Exit": "Entrance"}
for exit_full_name in map_exits:
exit_name = exit_full_name.rsplit(" - ", 1)[-1]
self.map_exit_suffixes[exit_name] = False
map_exit_suffixes[exit_name] = False
if exit_name.endswith(" Exit"):
self.map_exit_suffixes[exit_name[: -len(" Exit")]] = True
map_exit_suffixes[exit_name[: -len(" Exit")]] = True
if (
entrance_full_name := exit_full_name.replace("Exit", "Entrance")
) in map_entrances:
entrance_name = entrance_full_name.rsplit(" - ", 1)[-1]
self.map_exits_entrances[exit_name] = entrance_name
map_exits_entrances[exit_name] = entrance_name
if "Exit to " in exit_name:
if (
entrance_full_name := exit_full_name.replace(
"Exit to", "Entrance from"
)
) in map_entrances:
entrance_name = entrance_full_name.rsplit(" - ", 1)[-1]
self.map_exits_entrances[exit_name] = entrance_name
map_exits_entrances[exit_name] = entrance_name

self.map_entrances_suffixes = {
map_entrances_suffixes = {
entrance_name.rsplit(" - ", 1)[-1] for entrance_name in map_entrances
}

def fexit(prefix, k, v):
v = v.localize(lambda text: self.search(prefix, text))
if k in self.map_entrances_suffixes:
if k in map_entrances_suffixes:
return (None, k)
if k not in self.map_exit_suffixes:
if k not in map_exit_suffixes:
return ((self.search(prefix, k), v), None)
if self.map_exit_suffixes[k]:
if map_exit_suffixes[k]:
k = k + " Exit"
if k in self.map_exits_entrances:
return ((k, v), self.map_exits_entrances[k])
if k in map_exits_entrances:
return ((k, v), map_exits_entrances[k])
else:
return ((k, v), None)

Expand All @@ -298,7 +329,6 @@ def fexit(prefix, k, v):
)

self.areas: Dict[str, Area[DNFInventory]] = areas
self.all_areas.sub_areas = {k: v for (k, v) in all_areas.items() if k != ""}

self.short_full: List[Tuple[str, EXTENDED_ITEM_NAME]] = [("", EIN(""))]
self.entrance_allowed_time_of_day = {}
Expand Down
6 changes: 3 additions & 3 deletions logic/requirements/Lanayru.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
allowed-time-of-day: DayOnly
macros:
All Three Nodes: LMF Nodes On option | (North Part - Water Node & Lightning Node - Lightning Node & East - Fire Node - Fire Node)
All Three Nodes: LMF Nodes On option | (North Part - Water Node & Lightning Node - Activate Lightning Node & East - Fire Node - Activate Fire Node)
Raise Lanayru Mining Facility: Open LMF option | Desert - North Part - Activate Main Node
Can Navigate in Oasis: Can Defeat Ampilus | Hook Beetle | Central Skyloft - Bazaar - Endurance Potion | Brakeslide Trick | Stuttersprint Trick

Expand Down Expand Up @@ -202,7 +202,7 @@ Desert:
locations:
First Chest: Nothing
Second Chest: Nothing
Lightning Node: Sword
Activate Lightning Node: Sword

Present from afar:
exits:
Expand Down Expand Up @@ -265,7 +265,7 @@ Desert:
locations:
Left Ending Chest: Nothing
Right Ending Chest: Nothing
Fire Node: Sword
Activate Fire Node: Sword

Temple of Time:
# NB: There are no hintable checks here but logic consumers
Expand Down

0 comments on commit cd40881

Please sign in to comment.