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.py143
1 files changed, 140 insertions, 3 deletions
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index a3b86bf..958abc5 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -5,22 +5,159 @@ if TYPE_CHECKING:
5 from . import Lingo2World 5 from . import Lingo2World
6 6
7 7
8def calculate_letter_histogram(solution: str) -> dict[str, int]:
9 histogram = dict()
10 for l in solution:
11 if l.isalpha():
12 real_l = l.upper()
13 histogram[real_l] = min(histogram.get(l, 0) + 1, 2)
14
15 return histogram
16
17
18class AccessRequirements:
19 items: set[str]
20 rooms: set[str]
21 symbols: set[str]
22 letters: dict[str, int]
23
24 # This is an AND of ORs.
25 or_logic: list[list["AccessRequirements"]]
26
27 def __init__(self):
28 self.items = set()
29 self.rooms = set()
30 self.symbols = set()
31 self.letters = dict()
32 self.or_logic = list()
33
34 def add_solution(self, solution: str):
35 histogram = calculate_letter_histogram(solution)
36
37 for l, a in histogram.items():
38 self.letters[l] = max(self.letters.get(l, 0), histogram.get(l))
39
40 def merge(self, other: "AccessRequirements"):
41 for item in other.items:
42 self.items.add(item)
43
44 for room in other.rooms:
45 self.rooms.add(room)
46
47 for symbol in other.symbols:
48 self.symbols.add(symbol)
49
50 for letter, level in other.letters.items():
51 self.letters[letter] = max(self.letters.get(letter, 0), level)
52
53 for disjunction in other.or_logic:
54 self.or_logic.append(disjunction)
55
56
8class PlayerLocation(NamedTuple): 57class PlayerLocation(NamedTuple):
9 code: int | None 58 code: int | None
59 reqs: AccessRequirements
10 60
11 61
12class Lingo2PlayerLogic: 62class Lingo2PlayerLogic:
63 world: "Lingo2World"
64
13 locations_by_room: dict[int, list[PlayerLocation]] 65 locations_by_room: dict[int, list[PlayerLocation]]
14 66
67 panel_reqs: dict[int, AccessRequirements]
68 proxy_reqs: dict[int, dict[str, AccessRequirements]]
69 door_reqs: dict[int, AccessRequirements]
70
71 real_items: list[str]
72
15 def __init__(self, world: "Lingo2World"): 73 def __init__(self, world: "Lingo2World"):
74 self.world = world
16 self.locations_by_room = {} 75 self.locations_by_room = {}
76 self.panel_reqs = dict()
77 self.proxy_reqs = dict()
78 self.door_reqs = dict()
79 self.real_items = list()
17 80
18 for door in world.static_logic.objects.doors: 81 for door in world.static_logic.objects.doors:
19 if door.type in [common_pb2.DoorType.STANDARD, common_pb2.DoorType.LOCATION_ONLY]: 82 if door.type in [common_pb2.DoorType.STANDARD, common_pb2.DoorType.LOCATION_ONLY]:
20 self.locations_by_room.setdefault(door.room_id, []).append(PlayerLocation(door.ap_id)) 83 self.locations_by_room.setdefault(door.room_id, []).append(PlayerLocation(door.ap_id,
84 self.get_door_reqs(door.id)))
85
86 if door.type in [common_pb2.DoorType.STANDARD, common_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors:
87 self.real_items.append(self.world.static_logic.get_door_item_name(door.id))
21 88
22 for letter in world.static_logic.objects.letters: 89 for letter in world.static_logic.objects.letters:
23 self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id)) 90 self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id,
91 AccessRequirements()))
24 92
25 for mastery in world.static_logic.objects.masteries: 93 for mastery in world.static_logic.objects.masteries:
26 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id)) 94 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
95 AccessRequirements()))
96
97 def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
98 if answer is None:
99 if panel_id not in self.panel_reqs:
100 self.panel_reqs[panel_id] = self.calculate_panel_reqs(panel_id, answer)
101
102 return self.panel_reqs.get(panel_id)
103 else:
104 if panel_id not in self.proxy_reqs or answer not in self.proxy_reqs.get(panel_id):
105 self.proxy_reqs.setdefault(panel_id, {})[answer] = self.calculate_panel_reqs(panel_id, answer)
106
107 return self.proxy_reqs.get(panel_id).get(answer)
108
109 def calculate_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
110 panel = self.world.static_logic.objects.panels[panel_id]
111 reqs = AccessRequirements()
112
113 reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.room_id))
114
115 if answer is not None:
116 reqs.add_solution(answer)
117 elif len(panel.proxies) > 0:
118 for proxy in panel.proxies:
119 proxy_reqs = AccessRequirements()
120 proxy_reqs.add_solution(proxy.answer)
121
122 reqs.or_logic.append([proxy_reqs])
123 else:
124 reqs.add_solution(panel.answer)
125
126 for symbol in panel.symbols:
127 reqs.symbols.add(symbol)
128
129 if panel.HasField("required_door"):
130 door_reqs = self.get_door_reqs(panel.required_door)
131 reqs.merge(door_reqs)
132
133 if panel.HasField("required_room"):
134 reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.required_room))
135
136 return reqs
137
138 def get_door_reqs(self, door_id: int) -> AccessRequirements:
139 if door_id not in self.door_reqs:
140 self.door_reqs[door_id] = self.calculate_door_reqs(door_id)
141
142 return self.door_reqs.get(door_id)
143
144 def calculate_door_reqs(self, door_id: int) -> AccessRequirements:
145 door = self.world.static_logic.objects.doors[door_id]
146 reqs = AccessRequirements()
147
148 use_item = False
149 if door.type in [common_pb2.DoorType.STANDARD, common_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors:
150 use_item = True
151
152 if use_item:
153 reqs.items.add(self.world.static_logic.get_door_item_name(door.id))
154 else:
155 # TODO: complete_at, control_center_color, switches, keyholders
156 for proxy in door.panels:
157 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
158 reqs.merge(panel_reqs)
159
160 for room in door.rooms:
161 reqs.rooms.add(self.world.static_logic.get_room_region_name(room))
162
163 return reqs