about summary refs log tree commit diff stats
path: root/apworld/player_logic.py
diff options
context:
space:
mode:
Diffstat (limited to 'apworld/player_logic.py')
-rw-r--r--apworld/player_logic.py169
1 files changed, 1 insertions, 168 deletions
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index ea74266..2c3e08b 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -6,6 +6,7 @@ from .items import SYMBOL_ITEMS
6from typing import TYPE_CHECKING, NamedTuple 6from typing import TYPE_CHECKING, NamedTuple
7 7
8from .options import ShuffleLetters, CyanDoorBehavior, VictoryCondition, FastTravelAccess 8from .options import ShuffleLetters, CyanDoorBehavior, VictoryCondition, FastTravelAccess
9from .rules import AccessRequirements
9 10
10if TYPE_CHECKING: 11if TYPE_CHECKING:
11 from . import Lingo2World 12 from . import Lingo2World
@@ -21,174 +22,6 @@ def calculate_letter_histogram(solution: str) -> dict[str, int]:
21 return histogram 22 return histogram
22 23
23 24
24class AccessRequirements:
25 items: set[str]
26 progressives: dict[str, int]
27 rooms: set[str]
28 letters: dict[str, int]
29 cyans: bool
30
31 # This is an AND of ORs.
32 or_logic: list[list["AccessRequirements"]]
33
34 # When complete_at is set, at least that many of the requirements in possibilities must be accessible. This should
35 # only be used for doors with complete_at > 1, as or_logic is more efficient for complete_at == 1.
36 complete_at: int | None
37 possibilities: list["AccessRequirements"]
38
39 def __init__(self):
40 self.items = set()
41 self.progressives = dict()
42 self.rooms = set()
43 self.letters = dict()
44 self.cyans = False
45 self.or_logic = list()
46 self.complete_at = None
47 self.possibilities = list()
48
49 def copy(self) -> "AccessRequirements":
50 reqs = AccessRequirements()
51 reqs.items = self.items.copy()
52 reqs.progressives = self.progressives.copy()
53 reqs.rooms = self.rooms.copy()
54 reqs.letters = self.letters.copy()
55 reqs.cyans = self.cyans
56 reqs.or_logic = [[other_req.copy() for other_req in disjunction] for disjunction in self.or_logic]
57 reqs.complete_at = self.complete_at
58 reqs.possibilities = self.possibilities.copy()
59 return reqs
60
61 def merge(self, other: "AccessRequirements"):
62 for item in other.items:
63 self.items.add(item)
64
65 for item, amount in other.progressives.items():
66 self.progressives[item] = max(amount, self.progressives.get(item, 0))
67
68 for room in other.rooms:
69 self.rooms.add(room)
70
71 for letter, level in other.letters.items():
72 self.letters[letter] = max(self.letters.get(letter, 0), level)
73
74 self.cyans = self.cyans or other.cyans
75
76 for disjunction in other.or_logic:
77 self.or_logic.append([sub_req.copy() for sub_req in disjunction])
78
79 if other.complete_at is not None:
80 # Merging multiple requirements that use complete_at sucks, and is part of why we want to minimize use of
81 # it. If both requirements use complete_at, we will cheat by using the or_logic field, which supports
82 # conjunctions of requirements.
83 if self.complete_at is not None:
84 print("Merging requirements with complete_at > 1. This is messy and should be avoided!")
85
86 left_req = AccessRequirements()
87 left_req.complete_at = self.complete_at
88 left_req.possibilities = [sub_req.copy() for sub_req in self.possibilities]
89 self.or_logic.append([left_req])
90
91 self.complete_at = None
92 self.possibilities = list()
93
94 right_req = AccessRequirements()
95 right_req.complete_at = other.complete_at
96 right_req.possibilities = [sub_req.copy() for sub_req in other.possibilities]
97 self.or_logic.append([right_req])
98 else:
99 self.complete_at = other.complete_at
100 self.possibilities = [sub_req.copy() for sub_req in other.possibilities]
101
102 def is_empty(self) -> bool:
103 return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0
104 and not self.cyans and len(self.or_logic) == 0 and self.complete_at is None)
105
106 def __eq__(self, other: "AccessRequirements"):
107 return (self.items == other.items and self.progressives == other.progressives and self.rooms == other.rooms and
108 self.letters == other.letters and self.cyans == other.cyans and self.or_logic == other.or_logic and
109 self.complete_at == other.complete_at and self.possibilities == other.possibilities)
110
111 def simplify(self):
112 resimplify = False
113
114 if len(self.or_logic) > 0:
115 old_or_logic = self.or_logic
116
117 def remove_redundant(sub_reqs: "AccessRequirements"):
118 new_reqs = sub_reqs.copy()
119 new_reqs.letters = {l: v for l, v in new_reqs.letters.items() if self.letters.get(l, 0) < v}
120 if new_reqs != sub_reqs:
121 return new_reqs
122 else:
123 return sub_reqs
124
125 self.or_logic = []
126 for disjunction in old_or_logic:
127 new_disjunction = []
128 for ssr in disjunction:
129 new_ssr = remove_redundant(ssr)
130 if not new_ssr.is_empty():
131 new_disjunction.append(new_ssr)
132 else:
133 new_disjunction.clear()
134 break
135 if len(new_disjunction) == 1:
136 self.merge(new_disjunction[0])
137 resimplify = True
138 elif len(new_disjunction) > 1:
139 if all(cjr == new_disjunction[0] for cjr in new_disjunction):
140 self.merge(new_disjunction[0])
141 resimplify = True
142 else:
143 self.or_logic.append(new_disjunction)
144
145 if resimplify:
146 self.simplify()
147
148 def get_referenced_rooms(self):
149 result = set(self.rooms)
150
151 for disjunction in self.or_logic:
152 for sub_req in disjunction:
153 result = result.union(sub_req.get_referenced_rooms())
154
155 for sub_req in self.possibilities:
156 result = result.union(sub_req.get_referenced_rooms())
157
158 return result
159
160 def remove_room(self, room: str):
161 if room in self.rooms:
162 self.rooms.remove(room)
163
164 for disjunction in self.or_logic:
165 for sub_req in disjunction:
166 sub_req.remove_room(room)
167
168 for sub_req in self.possibilities:
169 sub_req.remove_room(room)
170
171 def __repr__(self):
172 parts = []
173 if len(self.items) > 0:
174 parts.append(f"items={self.items}")
175 if len(self.progressives) > 0:
176 parts.append(f"progressives={self.progressives}")
177 if len(self.rooms) > 0:
178 parts.append(f"rooms={self.rooms}")
179 if len(self.letters) > 0:
180 parts.append(f"letters={self.letters}")
181 if self.cyans:
182 parts.append(f"cyans=True")
183 if len(self.or_logic) > 0:
184 parts.append(f"or_logic={self.or_logic}")
185 if self.complete_at is not None:
186 parts.append(f"complete_at={self.complete_at}")
187 if len(self.possibilities) > 0:
188 parts.append(f"possibilities={self.possibilities}")
189 return "AccessRequirements(" + ", ".join(parts) + ")"
190
191
192class PlayerLocation(NamedTuple): 25class PlayerLocation(NamedTuple):
193 code: int | None 26 code: int | None
194 reqs: AccessRequirements 27 reqs: AccessRequirements