about summary refs log tree commit diff stats
path: root/apworld/rules.py
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-09-12 13:20:39 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2025-09-12 13:20:39 -0400
commite187853a7cd3fbbfdf99d23a306841e63121e1d8 (patch)
tree36799a8f6b9a5e23471b3ca31808cd62687249e0 /apworld/rules.py
parent67b721503443274351e0729ac57cbff83d31d753 (diff)
downloadlingo2-archipelago-e187853a7cd3fbbfdf99d23a306841e63121e1d8.tar.gz
lingo2-archipelago-e187853a7cd3fbbfdf99d23a306841e63121e1d8.tar.bz2
lingo2-archipelago-e187853a7cd3fbbfdf99d23a306841e63121e1d8.zip
[Apworld] Some access checking optimizations
Letter requirements in OR logic (which is the main thing OR logic is
used for) is simplified now. Any requirement within the OR logic that is
redundant with the top level requirement now has the redundant letters
removed. If a clause in a disjunction becomes empty due to this, the
disjunction can be removed. Additionally, if all of the clauses in a
disjunction are identical, then they can be merged into the top level
requirement.

I manually verified that every requirement that is affected by this
simplification looks correct.

Region objects are also now used in access checking instead of looking
up the regions by name during access checking. This is a little faster
for access checks that involve a lot of rooms, such as the Maze
Gravestone.

Finally, locations no longer check for access to the region the location
is in, and connections no longer check for access to the source region,
because these are both implied by how the graph works.
Diffstat (limited to 'apworld/rules.py')
-rw-r--r--apworld/rules.py22
1 files changed, 16 insertions, 6 deletions
diff --git a/apworld/rules.py b/apworld/rules.py index 6186637..c077858 100644 --- a/apworld/rules.py +++ b/apworld/rules.py
@@ -1,14 +1,15 @@
1from collections.abc import Callable 1from collections.abc import Callable
2from typing import TYPE_CHECKING 2from typing import TYPE_CHECKING
3 3
4from BaseClasses import CollectionState 4from BaseClasses import CollectionState, Region
5from .player_logic import AccessRequirements 5from .player_logic import AccessRequirements
6 6
7if TYPE_CHECKING: 7if TYPE_CHECKING:
8 from . import Lingo2World 8 from . import Lingo2World
9 9
10 10
11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, world: "Lingo2World") -> bool: 11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, regions: list[Region],
12 world: "Lingo2World") -> bool:
12 if not all(state.has(item, world.player) for item in reqs.items): 13 if not all(state.has(item, world.player) for item in reqs.items):
13 return False 14 return False
14 15
@@ -18,6 +19,9 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
18 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): 19 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms):
19 return False 20 return False
20 21
22 if not all(state.can_reach(region) for region in regions):
23 return False
24
21 for letter_key, letter_level in reqs.letters.items(): 25 for letter_key, letter_level in reqs.letters.items():
22 if not state.has(letter_key, world.player, letter_level): 26 if not state.has(letter_key, world.player, letter_level):
23 return False 27 return False
@@ -28,7 +32,7 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
28 return False 32 return False
29 33
30 if len(reqs.or_logic) > 0: 34 if len(reqs.or_logic) > 0:
31 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, world) for sub_reqs in subjunction) 35 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, [], world) for sub_reqs in subjunction)
32 for subjunction in reqs.or_logic): 36 for subjunction in reqs.or_logic):
33 return False 37 return False
34 38
@@ -37,7 +41,7 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
37 checked = 0 41 checked = 0
38 for possibility in reqs.possibilities: 42 for possibility in reqs.possibilities:
39 checked += 1 43 checked += 1
40 if lingo2_can_satisfy_requirements(state, possibility, world): 44 if lingo2_can_satisfy_requirements(state, possibility, [], world):
41 completed += 1 45 completed += 1
42 if completed >= reqs.complete_at: 46 if completed >= reqs.complete_at:
43 break 47 break
@@ -49,5 +53,11 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem
49 53
50 return True 54 return True
51 55
52def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World") -> Callable[[CollectionState], bool]: 56def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World",
53 return lambda state: lingo2_can_satisfy_requirements(state, reqs, world) 57 regions: dict[str, Region]) -> Callable[[CollectionState], bool]:
58 # Replace required rooms with regions for the top level requirement, which saves looking up the regions during rule
59 # checking.
60 required_regions = [regions[room_name] for room_name in reqs.rooms]
61 new_reqs = reqs.copy()
62 new_reqs.rooms.clear()
63 return lambda state: lingo2_can_satisfy_requirements(state, new_reqs, required_regions, world)