summary refs log tree commit diff stats
path: root/static_logic.py
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2023-11-08 18:35:12 -0500
committerGitHub <noreply@github.com>2023-11-08 17:35:12 -0600
commitbbbbc71bee25cfd22c5304f98f5a7881383585a3 (patch)
treed27581db7b8db03da4b731fe8c2d5072d3162cf8 /static_logic.py
downloadlingo-apworld-bbbbc71bee25cfd22c5304f98f5a7881383585a3.tar.gz
lingo-apworld-bbbbc71bee25cfd22c5304f98f5a7881383585a3.tar.bz2
lingo-apworld-bbbbc71bee25cfd22c5304f98f5a7881383585a3.zip
Lingo: New game (#1806)
Co-authored-by: Aaron Wagener <mmmcheese158@gmail.com>
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
Co-authored-by: Phar <zach@alliware.com>
Diffstat (limited to 'static_logic.py')
-rw-r--r--static_logic.py544
1 files changed, 544 insertions, 0 deletions
diff --git a/static_logic.py b/static_logic.py new file mode 100644 index 0000000..d122169 --- /dev/null +++ b/static_logic.py
@@ -0,0 +1,544 @@
1from typing import Dict, List, NamedTuple, Optional, Set
2
3import yaml
4
5
6class RoomAndDoor(NamedTuple):
7 room: Optional[str]
8 door: str
9
10
11class RoomAndPanel(NamedTuple):
12 room: Optional[str]
13 panel: str
14
15
16class RoomEntrance(NamedTuple):
17 room: str # source room
18 door: Optional[RoomAndDoor]
19 painting: bool
20
21
22class Room(NamedTuple):
23 name: str
24 entrances: List[RoomEntrance]
25
26
27class Door(NamedTuple):
28 name: str
29 item_name: str
30 location_name: Optional[str]
31 panels: Optional[List[RoomAndPanel]]
32 skip_location: bool
33 skip_item: bool
34 door_ids: List[str]
35 painting_ids: List[str]
36 event: bool
37 group: Optional[str]
38 include_reduce: bool
39 junk_item: bool
40
41
42class Panel(NamedTuple):
43 required_rooms: List[str]
44 required_doors: List[RoomAndDoor]
45 required_panels: List[RoomAndPanel]
46 colors: List[str]
47 check: bool
48 event: bool
49 internal_ids: List[str]
50 exclude_reduce: bool
51 achievement: bool
52 non_counting: bool
53
54
55class Painting(NamedTuple):
56 id: str
57 room: str
58 enter_only: bool
59 exit_only: bool
60 orientation: str
61 required: bool
62 required_when_no_doors: bool
63 required_door: Optional[RoomAndDoor]
64 disable: bool
65 move: bool
66
67
68class Progression(NamedTuple):
69 item_name: str
70 index: int
71
72
73ROOMS: Dict[str, Room] = {}
74PANELS: Dict[str, Panel] = {}
75DOORS: Dict[str, Door] = {}
76PAINTINGS: Dict[str, Painting] = {}
77
78ALL_ROOMS: List[Room] = []
79DOORS_BY_ROOM: Dict[str, Dict[str, Door]] = {}
80PANELS_BY_ROOM: Dict[str, Dict[str, Panel]] = {}
81PAINTINGS_BY_ROOM: Dict[str, List[Painting]] = {}
82
83PROGRESSIVE_ITEMS: List[str] = []
84PROGRESSION_BY_ROOM: Dict[str, Dict[str, Progression]] = {}
85
86PAINTING_ENTRANCES: int = 0
87PAINTING_EXIT_ROOMS: Set[str] = set()
88PAINTING_EXITS: int = 0
89REQUIRED_PAINTING_ROOMS: List[str] = []
90REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS: List[str] = []
91
92SPECIAL_ITEM_IDS: Dict[str, int] = {}
93PANEL_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
94DOOR_LOCATION_IDS: Dict[str, Dict[str, int]] = {}
95DOOR_ITEM_IDS: Dict[str, Dict[str, int]] = {}
96DOOR_GROUP_ITEM_IDS: Dict[str, int] = {}
97PROGRESSIVE_ITEM_IDS: Dict[str, int] = {}
98
99
100def load_static_data():
101 global PAINTING_EXITS, SPECIAL_ITEM_IDS, PANEL_LOCATION_IDS, DOOR_LOCATION_IDS, DOOR_ITEM_IDS, \
102 DOOR_GROUP_ITEM_IDS, PROGRESSIVE_ITEM_IDS
103
104 try:
105 from importlib.resources import files
106 except ImportError:
107 from importlib_resources import files
108
109 # Load in all item and location IDs. These are broken up into groups based on the type of item/location.
110 with files("worlds.lingo").joinpath("ids.yaml").open() as file:
111 config = yaml.load(file, Loader=yaml.Loader)
112
113 if "special_items" in config:
114 for item_name, item_id in config["special_items"].items():
115 SPECIAL_ITEM_IDS[item_name] = item_id
116
117 if "panels" in config:
118 for room_name in config["panels"].keys():
119 PANEL_LOCATION_IDS[room_name] = {}
120
121 for panel_name, location_id in config["panels"][room_name].items():
122 PANEL_LOCATION_IDS[room_name][panel_name] = location_id
123
124 if "doors" in config:
125 for room_name in config["doors"].keys():
126 DOOR_LOCATION_IDS[room_name] = {}
127 DOOR_ITEM_IDS[room_name] = {}
128
129 for door_name, door_data in config["doors"][room_name].items():
130 if "location" in door_data:
131 DOOR_LOCATION_IDS[room_name][door_name] = door_data["location"]
132
133 if "item" in door_data:
134 DOOR_ITEM_IDS[room_name][door_name] = door_data["item"]
135
136 if "door_groups" in config:
137 for item_name, item_id in config["door_groups"].items():
138 DOOR_GROUP_ITEM_IDS[item_name] = item_id
139
140 if "progression" in config:
141 for item_name, item_id in config["progression"].items():
142 PROGRESSIVE_ITEM_IDS[item_name] = item_id
143
144 # Process the main world file.
145 with files("worlds.lingo").joinpath("LL1.yaml").open() as file:
146 config = yaml.load(file, Loader=yaml.Loader)
147
148 for room_name, room_data in config.items():
149 process_room(room_name, room_data)
150
151 PAINTING_EXITS = len(PAINTING_EXIT_ROOMS)
152
153
154def get_special_item_id(name: str):
155 if name not in SPECIAL_ITEM_IDS:
156 raise Exception(f"Item ID for special item {name} not found in ids.yaml.")
157
158 return SPECIAL_ITEM_IDS[name]
159
160
161def get_panel_location_id(room: str, name: str):
162 if room not in PANEL_LOCATION_IDS or name not in PANEL_LOCATION_IDS[room]:
163 raise Exception(f"Location ID for panel {room} - {name} not found in ids.yaml.")
164
165 return PANEL_LOCATION_IDS[room][name]
166
167
168def get_door_location_id(room: str, name: str):
169 if room not in DOOR_LOCATION_IDS or name not in DOOR_LOCATION_IDS[room]:
170 raise Exception(f"Location ID for door {room} - {name} not found in ids.yaml.")
171
172 return DOOR_LOCATION_IDS[room][name]
173
174
175def get_door_item_id(room: str, name: str):
176 if room not in DOOR_ITEM_IDS or name not in DOOR_ITEM_IDS[room]:
177 raise Exception(f"Item ID for door {room} - {name} not found in ids.yaml.")
178
179 return DOOR_ITEM_IDS[room][name]
180
181
182def get_door_group_item_id(name: str):
183 if name not in DOOR_GROUP_ITEM_IDS:
184 raise Exception(f"Item ID for door group {name} not found in ids.yaml.")
185
186 return DOOR_GROUP_ITEM_IDS[name]
187
188
189def get_progressive_item_id(name: str):
190 if name not in PROGRESSIVE_ITEM_IDS:
191 raise Exception(f"Item ID for progressive item {name} not found in ids.yaml.")
192
193 return PROGRESSIVE_ITEM_IDS[name]
194
195
196def process_entrance(source_room, doors, room_obj):
197 global PAINTING_ENTRANCES, PAINTING_EXIT_ROOMS
198
199 # If the value of an entrance is just True, that means that the entrance is always accessible.
200 if doors is True:
201 room_obj.entrances.append(RoomEntrance(source_room, None, False))
202 elif isinstance(doors, dict):
203 # If the value of an entrance is a dictionary, that means the entrance requires a door to be accessible, is a
204 # painting-based entrance, or both.
205 if "painting" in doors and "door" not in doors:
206 PAINTING_EXIT_ROOMS.add(room_obj.name)
207 PAINTING_ENTRANCES += 1
208
209 room_obj.entrances.append(RoomEntrance(source_room, None, True))
210 else:
211 if "painting" in doors and doors["painting"]:
212 PAINTING_EXIT_ROOMS.add(room_obj.name)
213 PAINTING_ENTRANCES += 1
214
215 room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor(
216 doors["room"] if "room" in doors else None,
217 doors["door"]
218 ), doors["painting"] if "painting" in doors else False))
219 else:
220 # If the value of an entrance is a list, then there are multiple possible doors that can give access to the
221 # entrance.
222 for door in doors:
223 if "painting" in door and door["painting"]:
224 PAINTING_EXIT_ROOMS.add(room_obj.name)
225 PAINTING_ENTRANCES += 1
226
227 room_obj.entrances.append(RoomEntrance(source_room, RoomAndDoor(
228 door["room"] if "room" in door else None,
229 door["door"]
230 ), door["painting"] if "painting" in door else False))
231
232
233def process_panel(room_name, panel_name, panel_data):
234 global PANELS, PANELS_BY_ROOM
235
236 full_name = f"{room_name} - {panel_name}"
237
238 # required_room can either be a single room or a list of rooms.
239 if "required_room" in panel_data:
240 if isinstance(panel_data["required_room"], list):
241 required_rooms = panel_data["required_room"]
242 else:
243 required_rooms = [panel_data["required_room"]]
244 else:
245 required_rooms = []
246
247 # required_door can either be a single door or a list of doors. For convenience, the room key for each door does not
248 # need to be specified if the door is in this room.
249 required_doors = list()
250 if "required_door" in panel_data:
251 if isinstance(panel_data["required_door"], dict):
252 door = panel_data["required_door"]
253 required_doors.append(RoomAndDoor(
254 door["room"] if "room" in door else None,
255 door["door"]
256 ))
257 else:
258 for door in panel_data["required_door"]:
259 required_doors.append(RoomAndDoor(
260 door["room"] if "room" in door else None,
261 door["door"]
262 ))
263
264 # required_panel can either be a single panel or a list of panels. For convenience, the room key for each panel does
265 # not need to be specified if the panel is in this room.
266 required_panels = list()
267 if "required_panel" in panel_data:
268 if isinstance(panel_data["required_panel"], dict):
269 other_panel = panel_data["required_panel"]
270 required_panels.append(RoomAndPanel(
271 other_panel["room"] if "room" in other_panel else None,
272 other_panel["panel"]
273 ))
274 else:
275 for other_panel in panel_data["required_panel"]:
276 required_panels.append(RoomAndPanel(
277 other_panel["room"] if "room" in other_panel else None,
278 other_panel["panel"]
279 ))
280
281 # colors can either be a single color or a list of colors.
282 if "colors" in panel_data:
283 if isinstance(panel_data["colors"], list):
284 colors = panel_data["colors"]
285 else:
286 colors = [panel_data["colors"]]
287 else:
288 colors = []
289
290 if "check" in panel_data:
291 check = panel_data["check"]
292 else:
293 check = False
294
295 if "event" in panel_data:
296 event = panel_data["event"]
297 else:
298 event = False
299
300 if "achievement" in panel_data:
301 achievement = True
302 else:
303 achievement = False
304
305 if "exclude_reduce" in panel_data:
306 exclude_reduce = panel_data["exclude_reduce"]
307 else:
308 exclude_reduce = False
309
310 if "non_counting" in panel_data:
311 non_counting = panel_data["non_counting"]
312 else:
313 non_counting = False
314
315 if "id" in panel_data:
316 if isinstance(panel_data["id"], list):
317 internal_ids = panel_data["id"]
318 else:
319 internal_ids = [panel_data["id"]]
320 else:
321 internal_ids = []
322
323 panel_obj = Panel(required_rooms, required_doors, required_panels, colors, check, event, internal_ids,
324 exclude_reduce, achievement, non_counting)
325 PANELS[full_name] = panel_obj
326 PANELS_BY_ROOM[room_name][panel_name] = panel_obj
327
328
329def process_door(room_name, door_name, door_data):
330 global DOORS, DOORS_BY_ROOM
331
332 # The item name associated with a door can be explicitly specified in the configuration. If it is not, it is
333 # generated from the room and door name.
334 if "item_name" in door_data:
335 item_name = door_data["item_name"]
336 else:
337 item_name = f"{room_name} - {door_name}"
338
339 if "skip_location" in door_data:
340 skip_location = door_data["skip_location"]
341 else:
342 skip_location = False
343
344 if "skip_item" in door_data:
345 skip_item = door_data["skip_item"]
346 else:
347 skip_item = False
348
349 if "event" in door_data:
350 event = door_data["event"]
351 else:
352 event = False
353
354 if "include_reduce" in door_data:
355 include_reduce = door_data["include_reduce"]
356 else:
357 include_reduce = False
358
359 if "junk_item" in door_data:
360 junk_item = door_data["junk_item"]
361 else:
362 junk_item = False
363
364 if "group" in door_data:
365 group = door_data["group"]
366 else:
367 group = None
368
369 # panels is a list of panels. Each panel can either be a simple string (the name of a panel in the current room) or
370 # a dictionary specifying a panel in a different room.
371 if "panels" in door_data:
372 panels = list()
373 for panel in door_data["panels"]:
374 if isinstance(panel, dict):
375 panels.append(RoomAndPanel(panel["room"], panel["panel"]))
376 else:
377 panels.append(RoomAndPanel(None, panel))
378 else:
379 skip_location = True
380 panels = None
381
382 # The location name associated with a door can be explicitly specified in the configuration. If it is not, then the
383 # name is generated using a combination of all of the panels that would ordinarily open the door. This can get quite
384 # messy if there are a lot of panels, especially if panels from multiple rooms are involved, so in these cases it
385 # would be better to specify a name.
386 if "location_name" in door_data:
387 location_name = door_data["location_name"]
388 elif skip_location is False:
389 panel_per_room = dict()
390 for panel in panels:
391 panel_room_name = room_name if panel.room is None else panel.room
392 panel_per_room.setdefault(panel_room_name, []).append(panel.panel)
393
394 room_strs = list()
395 for door_room_str, door_panels_str in panel_per_room.items():
396 room_strs.append(door_room_str + " - " + ", ".join(door_panels_str))
397
398 location_name = " and ".join(room_strs)
399 else:
400 location_name = None
401
402 # The id field can be a single item, or a list of door IDs, in the event that the item for this logical door should
403 # open more than one actual in-game door.
404 if "id" in door_data:
405 if isinstance(door_data["id"], list):
406 door_ids = door_data["id"]
407 else:
408 door_ids = [door_data["id"]]
409 else:
410 door_ids = []
411
412 # The painting_id field can be a single item, or a list of painting IDs, in the event that the item for this logical
413 # door should move more than one actual in-game painting.
414 if "painting_id" in door_data:
415 if isinstance(door_data["painting_id"], list):
416 painting_ids = door_data["painting_id"]
417 else:
418 painting_ids = [door_data["painting_id"]]
419 else:
420 painting_ids = []
421
422 door_obj = Door(door_name, item_name, location_name, panels, skip_location, skip_item, door_ids,
423 painting_ids, event, group, include_reduce, junk_item)
424
425 DOORS[door_obj.item_name] = door_obj
426 DOORS_BY_ROOM[room_name][door_name] = door_obj
427
428
429def process_painting(room_name, painting_data):
430 global PAINTINGS, PAINTINGS_BY_ROOM, REQUIRED_PAINTING_ROOMS, REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
431
432 # Read in information about this painting and store it in an object.
433 painting_id = painting_data["id"]
434
435 if "orientation" in painting_data:
436 orientation = painting_data["orientation"]
437 else:
438 orientation = ""
439
440 if "disable" in painting_data:
441 disable_painting = painting_data["disable"]
442 else:
443 disable_painting = False
444
445 if "required" in painting_data:
446 required_painting = painting_data["required"]
447 if required_painting:
448 REQUIRED_PAINTING_ROOMS.append(room_name)
449 else:
450 required_painting = False
451
452 if "move" in painting_data:
453 move_painting = painting_data["move"]
454 else:
455 move_painting = False
456
457 if "required_when_no_doors" in painting_data:
458 rwnd = painting_data["required_when_no_doors"]
459 if rwnd:
460 REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS.append(room_name)
461 else:
462 rwnd = False
463
464 if "exit_only" in painting_data:
465 exit_only = painting_data["exit_only"]
466 else:
467 exit_only = False
468
469 if "enter_only" in painting_data:
470 enter_only = painting_data["enter_only"]
471 else:
472 enter_only = False
473
474 required_door = None
475 if "required_door" in painting_data:
476 door = painting_data["required_door"]
477 required_door = RoomAndDoor(
478 door["room"] if "room" in door else room_name,
479 door["door"]
480 )
481
482 painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation,
483 required_painting, rwnd, required_door, disable_painting, move_painting)
484 PAINTINGS[painting_id] = painting_obj
485 PAINTINGS_BY_ROOM[room_name].append(painting_obj)
486
487
488def process_progression(room_name, progression_name, progression_doors):
489 global PROGRESSIVE_ITEMS, PROGRESSION_BY_ROOM
490
491 # Progressive items are configured as a list of doors.
492 PROGRESSIVE_ITEMS.append(progression_name)
493
494 progression_index = 1
495 for door in progression_doors:
496 if isinstance(door, Dict):
497 door_room = door["room"]
498 door_door = door["door"]
499 else:
500 door_room = room_name
501 door_door = door
502
503 room_progressions = PROGRESSION_BY_ROOM.setdefault(door_room, {})
504 room_progressions[door_door] = Progression(progression_name, progression_index)
505 progression_index += 1
506
507
508def process_room(room_name, room_data):
509 global ROOMS, ALL_ROOMS
510
511 room_obj = Room(room_name, [])
512
513 if "entrances" in room_data:
514 for source_room, doors in room_data["entrances"].items():
515 process_entrance(source_room, doors, room_obj)
516
517 if "panels" in room_data:
518 PANELS_BY_ROOM[room_name] = dict()
519
520 for panel_name, panel_data in room_data["panels"].items():
521 process_panel(room_name, panel_name, panel_data)
522
523 if "doors" in room_data:
524 DOORS_BY_ROOM[room_name] = dict()
525
526 for door_name, door_data in room_data["doors"].items():
527 process_door(room_name, door_name, door_data)
528
529 if "paintings" in room_data:
530 PAINTINGS_BY_ROOM[room_name] = []
531
532 for painting_data in room_data["paintings"]:
533 process_painting(room_name, painting_data)
534
535 if "progression" in room_data:
536 for progression_name, progression_doors in room_data["progression"].items():
537 process_progression(room_name, progression_name, progression_doors)
538
539 ROOMS[room_name] = room_obj
540 ALL_ROOMS.append(room_obj)
541
542
543# Initialize the static data at module scope.
544load_static_data()