diff options
Diffstat (limited to 'apworld')
| -rw-r--r-- | apworld/__init__.py | 1 | ||||
| -rw-r--r-- | apworld/items.py | 24 | ||||
| -rw-r--r-- | apworld/options.py | 9 | ||||
| -rw-r--r-- | apworld/player_logic.py | 54 | ||||
| -rw-r--r-- | apworld/rules.py | 2 | ||||
| -rw-r--r-- | apworld/static_logic.py | 4 |
6 files changed, 75 insertions, 19 deletions
| diff --git a/apworld/__init__.py b/apworld/__init__.py index 6eeee74..fc263c0 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
| @@ -82,6 +82,7 @@ class Lingo2World(World): | |||
| 82 | "shuffle_control_center_colors", | 82 | "shuffle_control_center_colors", |
| 83 | "shuffle_doors", | 83 | "shuffle_doors", |
| 84 | "shuffle_letters", | 84 | "shuffle_letters", |
| 85 | "shuffle_symbols", | ||
| 85 | "victory_condition", | 86 | "victory_condition", |
| 86 | ] | 87 | ] |
| 87 | 88 | ||
| diff --git a/apworld/items.py b/apworld/items.py index 971a709..32568a3 100644 --- a/apworld/items.py +++ b/apworld/items.py | |||
| @@ -1,5 +1,29 @@ | |||
| 1 | from .generated import data_pb2 as data_pb2 | ||
| 1 | from BaseClasses import Item | 2 | from BaseClasses import Item |
| 2 | 3 | ||
| 3 | 4 | ||
| 4 | class Lingo2Item(Item): | 5 | class Lingo2Item(Item): |
| 5 | game: str = "Lingo 2" | 6 | game: str = "Lingo 2" |
| 7 | |||
| 8 | |||
| 9 | SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { | ||
| 10 | data_pb2.PuzzleSymbol.SUN: "Sun Symbol", | ||
| 11 | data_pb2.PuzzleSymbol.SPARKLES: "Sparkles Symbol", | ||
| 12 | data_pb2.PuzzleSymbol.ZERO: "Zero Symbol", | ||
| 13 | data_pb2.PuzzleSymbol.EXAMPLE: "Example Symbol", | ||
| 14 | data_pb2.PuzzleSymbol.BOXES: "Boxes Symbol", | ||
| 15 | data_pb2.PuzzleSymbol.PLANET: "Planet Symbol", | ||
| 16 | data_pb2.PuzzleSymbol.PYRAMID: "Pyramid Symbol", | ||
| 17 | data_pb2.PuzzleSymbol.CROSS: "Cross Symbol", | ||
| 18 | data_pb2.PuzzleSymbol.SWEET: "Sweet Symbol", | ||
| 19 | data_pb2.PuzzleSymbol.GENDER: "Gender Symbol", | ||
| 20 | data_pb2.PuzzleSymbol.AGE: "Age Symbol", | ||
| 21 | data_pb2.PuzzleSymbol.SOUND: "Sound Symbol", | ||
| 22 | data_pb2.PuzzleSymbol.ANAGRAM: "Anagram Symbol", | ||
| 23 | data_pb2.PuzzleSymbol.JOB: "Job Symbol", | ||
| 24 | data_pb2.PuzzleSymbol.STARS: "Stars Symbol", | ||
| 25 | data_pb2.PuzzleSymbol.NULL: "Null Symbol", | ||
| 26 | data_pb2.PuzzleSymbol.EVAL: "Eval Symbol", | ||
| 27 | data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol", | ||
| 28 | data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", | ||
| 29 | } | ||
| diff --git a/apworld/options.py b/apworld/options.py index 2197b0f..f72e826 100644 --- a/apworld/options.py +++ b/apworld/options.py | |||
| @@ -39,6 +39,14 @@ class ShuffleLetters(Choice): | |||
| 39 | option_item_cyan = 4 | 39 | option_item_cyan = 4 |
| 40 | 40 | ||
| 41 | 41 | ||
| 42 | class ShuffleSymbols(Toggle): | ||
| 43 | """ | ||
| 44 | If enabled, 19 items will be added to the pool, representing the different symbols that can appear on a panel. | ||
| 45 | Players will be prevented from solving puzzles with symbols on them until all of the required symbols are unlocked. | ||
| 46 | """ | ||
| 47 | display_name = "Shuffle Symbols" | ||
| 48 | |||
| 49 | |||
| 42 | class KeyholderSanity(Toggle): | 50 | class KeyholderSanity(Toggle): |
| 43 | """ | 51 | """ |
| 44 | If enabled, 26 locations will be created for placing each key into its respective Green Ending keyholder. | 52 | If enabled, 26 locations will be created for placing each key into its respective Green Ending keyholder. |
| @@ -102,6 +110,7 @@ class Lingo2Options(PerGameCommonOptions): | |||
| 102 | shuffle_doors: ShuffleDoors | 110 | shuffle_doors: ShuffleDoors |
| 103 | shuffle_control_center_colors: ShuffleControlCenterColors | 111 | shuffle_control_center_colors: ShuffleControlCenterColors |
| 104 | shuffle_letters: ShuffleLetters | 112 | shuffle_letters: ShuffleLetters |
| 113 | shuffle_symbols: ShuffleSymbols | ||
| 105 | keyholder_sanity: KeyholderSanity | 114 | keyholder_sanity: KeyholderSanity |
| 106 | cyan_door_behavior: CyanDoorBehavior | 115 | cyan_door_behavior: CyanDoorBehavior |
| 107 | daedalus_roof_access: DaedalusRoofAccess | 116 | daedalus_roof_access: DaedalusRoofAccess |
| diff --git a/apworld/player_logic.py b/apworld/player_logic.py index dbd340c..42b36e6 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | from enum import IntEnum, auto | 1 | from enum import IntEnum, auto |
| 2 | 2 | ||
| 3 | from .generated import data_pb2 as data_pb2 | 3 | from .generated import data_pb2 as data_pb2 |
| 4 | from .items import SYMBOL_ITEMS | ||
| 4 | from typing import TYPE_CHECKING, NamedTuple | 5 | from typing import TYPE_CHECKING, NamedTuple |
| 5 | 6 | ||
| 6 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior | 7 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior |
| @@ -23,7 +24,6 @@ class AccessRequirements: | |||
| 23 | items: set[str] | 24 | items: set[str] |
| 24 | progressives: dict[str, int] | 25 | progressives: dict[str, int] |
| 25 | rooms: set[str] | 26 | rooms: set[str] |
| 26 | symbols: set[str] | ||
| 27 | letters: dict[str, int] | 27 | letters: dict[str, int] |
| 28 | cyans: bool | 28 | cyans: bool |
| 29 | 29 | ||
| @@ -34,7 +34,6 @@ class AccessRequirements: | |||
| 34 | self.items = set() | 34 | self.items = set() |
| 35 | self.progressives = dict() | 35 | self.progressives = dict() |
| 36 | self.rooms = set() | 36 | self.rooms = set() |
| 37 | self.symbols = set() | ||
| 38 | self.letters = dict() | 37 | self.letters = dict() |
| 39 | self.cyans = False | 38 | self.cyans = False |
| 40 | self.or_logic = list() | 39 | self.or_logic = list() |
| @@ -49,9 +48,6 @@ class AccessRequirements: | |||
| 49 | for room in other.rooms: | 48 | for room in other.rooms: |
| 50 | self.rooms.add(room) | 49 | self.rooms.add(room) |
| 51 | 50 | ||
| 52 | for symbol in other.symbols: | ||
| 53 | self.symbols.add(symbol) | ||
| 54 | |||
| 55 | for letter, level in other.letters.items(): | 51 | for letter, level in other.letters.items(): |
| 56 | self.letters[letter] = max(self.letters.get(letter, 0), level) | 52 | self.letters[letter] = max(self.letters.get(letter, 0), level) |
| 57 | 53 | ||
| @@ -60,6 +56,10 @@ class AccessRequirements: | |||
| 60 | for disjunction in other.or_logic: | 56 | for disjunction in other.or_logic: |
| 61 | self.or_logic.append(disjunction) | 57 | self.or_logic.append(disjunction) |
| 62 | 58 | ||
| 59 | def is_empty(self) -> bool: | ||
| 60 | return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0 | ||
| 61 | and not self.cyans and len(self.or_logic) == 0) | ||
| 62 | |||
| 63 | def __repr__(self): | 63 | def __repr__(self): |
| 64 | parts = [] | 64 | parts = [] |
| 65 | if len(self.items) > 0: | 65 | if len(self.items) > 0: |
| @@ -68,8 +68,6 @@ class AccessRequirements: | |||
| 68 | parts.append(f"progressives={self.progressives}") | 68 | parts.append(f"progressives={self.progressives}") |
| 69 | if len(self.rooms) > 0: | 69 | if len(self.rooms) > 0: |
| 70 | parts.append(f"rooms={self.rooms}") | 70 | parts.append(f"rooms={self.rooms}") |
| 71 | if len(self.symbols) > 0: | ||
| 72 | parts.append(f"symbols={self.symbols}") | ||
| 73 | if len(self.letters) > 0: | 71 | if len(self.letters) > 0: |
| 74 | parts.append(f"letters={self.letters}") | 72 | parts.append(f"letters={self.letters}") |
| 75 | if self.cyans: | 73 | if self.cyans: |
| @@ -231,6 +229,10 @@ class Lingo2PlayerLogic: | |||
| 231 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, | 229 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, |
| 232 | reqs)) | 230 | reqs)) |
| 233 | 231 | ||
| 232 | if self.world.options.shuffle_symbols: | ||
| 233 | for symbol_name in SYMBOL_ITEMS.values(): | ||
| 234 | self.real_items.append(symbol_name) | ||
| 235 | |||
| 234 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: | 236 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: |
| 235 | if answer is None: | 237 | if answer is None: |
| 236 | if panel_id not in self.panel_reqs: | 238 | if panel_id not in self.panel_reqs: |
| @@ -253,25 +255,35 @@ class Lingo2PlayerLogic: | |||
| 253 | self.add_solution_reqs(reqs, answer) | 255 | self.add_solution_reqs(reqs, answer) |
| 254 | elif len(panel.proxies) > 0: | 256 | elif len(panel.proxies) > 0: |
| 255 | possibilities = [] | 257 | possibilities = [] |
| 258 | already_filled = False | ||
| 256 | 259 | ||
| 257 | for proxy in panel.proxies: | 260 | for proxy in panel.proxies: |
| 258 | proxy_reqs = AccessRequirements() | 261 | proxy_reqs = AccessRequirements() |
| 259 | self.add_solution_reqs(proxy_reqs, proxy.answer) | 262 | self.add_solution_reqs(proxy_reqs, proxy.answer) |
| 260 | 263 | ||
| 261 | possibilities.append(proxy_reqs) | 264 | if not proxy_reqs.is_empty(): |
| 265 | possibilities.append(proxy_reqs) | ||
| 266 | else: | ||
| 267 | already_filled = True | ||
| 268 | break | ||
| 262 | 269 | ||
| 263 | if not any(proxy.answer == panel.answer for proxy in panel.proxies): | 270 | if not already_filled and not any(proxy.answer == panel.answer for proxy in panel.proxies): |
| 264 | proxy_reqs = AccessRequirements() | 271 | proxy_reqs = AccessRequirements() |
| 265 | self.add_solution_reqs(proxy_reqs, panel.answer) | 272 | self.add_solution_reqs(proxy_reqs, panel.answer) |
| 266 | 273 | ||
| 267 | possibilities.append(proxy_reqs) | 274 | if not proxy_reqs.is_empty(): |
| 275 | possibilities.append(proxy_reqs) | ||
| 276 | else: | ||
| 277 | already_filled = True | ||
| 268 | 278 | ||
| 269 | reqs.or_logic.append(possibilities) | 279 | if not already_filled: |
| 280 | reqs.or_logic.append(possibilities) | ||
| 270 | else: | 281 | else: |
| 271 | self.add_solution_reqs(reqs, panel.answer) | 282 | self.add_solution_reqs(reqs, panel.answer) |
| 272 | 283 | ||
| 273 | for symbol in panel.symbols: | 284 | if self.world.options.shuffle_symbols: |
| 274 | reqs.symbols.add(symbol) | 285 | for symbol in panel.symbols: |
| 286 | reqs.items.add(SYMBOL_ITEMS.get(symbol)) | ||
| 275 | 287 | ||
| 276 | if panel.HasField("required_door"): | 288 | if panel.HasField("required_door"): |
| 277 | door_reqs = self.get_door_open_reqs(panel.required_door) | 289 | door_reqs = self.get_door_open_reqs(panel.required_door) |
| @@ -300,9 +312,16 @@ class Lingo2PlayerLogic: | |||
| 300 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) | 312 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
| 301 | reqs.merge(panel_reqs) | 313 | reqs.merge(panel_reqs) |
| 302 | elif door.complete_at == 1: | 314 | elif door.complete_at == 1: |
| 303 | reqs.or_logic.append([self.get_panel_reqs(proxy.panel, | 315 | disjunction = [] |
| 304 | proxy.answer if proxy.HasField("answer") else None) | 316 | for proxy in door.panels: |
| 305 | for proxy in door.panels]) | 317 | proxy_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
| 318 | if proxy_reqs.is_empty(): | ||
| 319 | disjunction.clear() | ||
| 320 | break | ||
| 321 | else: | ||
| 322 | disjunction.append(proxy_reqs) | ||
| 323 | if len(disjunction) > 0: | ||
| 324 | reqs.or_logic.append(disjunction) | ||
| 306 | else: | 325 | else: |
| 307 | # TODO: Handle complete_at > 1 | 326 | # TODO: Handle complete_at > 1 |
| 308 | pass | 327 | pass |
| @@ -316,7 +335,8 @@ class Lingo2PlayerLogic: | |||
| 316 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: | 335 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: |
| 317 | reqs.rooms.add("The Repetitive - Main Room") | 336 | reqs.rooms.add("The Repetitive - Main Room") |
| 318 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: | 337 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: |
| 319 | reqs.cyans = True | 338 | if self.world.options.shuffle_letters != ShuffleLetters.option_unlocked: |
| 339 | reqs.cyans = True | ||
| 320 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: | 340 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: |
| 321 | # There shouldn't be any locations that are cyan doors. | 341 | # There shouldn't be any locations that are cyan doors. |
| 322 | pass | 342 | pass |
| diff --git a/apworld/rules.py b/apworld/rules.py index 56486fa..0bff056 100644 --- a/apworld/rules.py +++ b/apworld/rules.py | |||
| @@ -18,8 +18,6 @@ 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): | 18 | if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): |
| 19 | return False | 19 | return False |
| 20 | 20 | ||
| 21 | # TODO: symbols | ||
| 22 | |||
| 23 | for letter_key, letter_level in reqs.letters.items(): | 21 | for letter_key, letter_level in reqs.letters.items(): |
| 24 | if not state.has(letter_key, world.player, letter_level): | 22 | if not state.has(letter_key, world.player, letter_level): |
| 25 | return False | 23 | return False |
| diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 0cc7e55..c112d8e 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py | |||
| @@ -1,4 +1,5 @@ | |||
| 1 | from .generated import data_pb2 as data_pb2 | 1 | from .generated import data_pb2 as data_pb2 |
| 2 | from .items import SYMBOL_ITEMS | ||
| 2 | import pkgutil | 3 | import pkgutil |
| 3 | 4 | ||
| 4 | class Lingo2StaticLogic: | 5 | class Lingo2StaticLogic: |
| @@ -64,6 +65,9 @@ class Lingo2StaticLogic: | |||
| 64 | 65 | ||
| 65 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" | 66 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" |
| 66 | 67 | ||
| 68 | for symbol_name in SYMBOL_ITEMS.values(): | ||
| 69 | self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name | ||
| 70 | |||
| 67 | self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} | 71 | self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} |
| 68 | self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} | 72 | self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} |
| 69 | 73 | ||
