diff options
| author | Star Rauchenberger <fefferburbia@gmail.com> | 2023-11-10 14:07:56 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-10 13:07:56 -0600 | 
| commit | a43fb727a292bd9476dc8de5685c5b6c38a6a919 (patch) | |
| tree | 9e702125b47c95fcaf2accf548aba241a9d50282 | |
| parent | bbbbc71bee25cfd22c5304f98f5a7881383585a3 (diff) | |
| download | lingo-apworld-a43fb727a292bd9476dc8de5685c5b6c38a6a919.tar.gz lingo-apworld-a43fb727a292bd9476dc8de5685c5b6c38a6a919.tar.bz2 lingo-apworld-a43fb727a292bd9476dc8de5685c5b6c38a6a919.zip | |
Lingo: Fix edge case painting shuffle accessibility issues (#2441)
* Lingo: Fix painting shuffle logic issue in The Wise * Lingo: More generic painting cycle prevention * Lingo: okay how about now * Lingo: Consider Owl Hallway blocked painting areas in vanilla doors * Lingo: so honestly I should've seen this one coming * Lingo: Refined req_blocked for vanilla doors * Lingo: Orange Tower Basement is also owl-blocked * Lingo: Rewrite randomize_paintings to eliminate rerolls Now, mapping is done in two phases, rather than assigning everything at once and then rerolling if the mapping is non-viable.
| -rw-r--r-- | LL1.yaml | 11 | ||||
| -rw-r--r-- | player_logic.py | 62 | ||||
| -rw-r--r-- | static_logic.py | 15 | 
3 files changed, 58 insertions, 30 deletions
| diff --git a/LL1.yaml b/LL1.yaml index 7ae015d..db1418f 100644 --- a/LL1.yaml +++ b/LL1.yaml | |||
| @@ -97,6 +97,11 @@ | |||
| 97 | # Use "required_when_no_doors" instead if it would be | 97 | # Use "required_when_no_doors" instead if it would be | 
| 98 | # possible to enter the room without the painting in door | 98 | # possible to enter the room without the painting in door | 
| 99 | # shuffle mode. | 99 | # shuffle mode. | 
| 100 | # - req_blocked: Marks that a painting cannot be an entrance leading to a | ||
| 101 | # required painting. Paintings within a room that has a | ||
| 102 | # required painting are automatically req blocked. | ||
| 103 | # Use "req_blocked_when_no_doors" instead if it would be | ||
| 104 | # fine in door shuffle mode. | ||
| 100 | # - move: Denotes that the painting is able to move. | 105 | # - move: Denotes that the painting is able to move. | 
| 101 | Starting Room: | 106 | Starting Room: | 
| 102 | entrances: | 107 | entrances: | 
| @@ -2210,6 +2215,7 @@ | |||
| 2210 | - id: map_painting2 | 2215 | - id: map_painting2 | 
| 2211 | orientation: north | 2216 | orientation: north | 
| 2212 | enter_only: True # otherwise you might just skip the whole game! | 2217 | enter_only: True # otherwise you might just skip the whole game! | 
| 2218 | req_blocked_when_no_doors: True # owl hallway in vanilla doors | ||
| 2213 | Roof: | 2219 | Roof: | 
| 2214 | entrances: | 2220 | entrances: | 
| 2215 | Orange Tower Seventh Floor: True | 2221 | Orange Tower Seventh Floor: True | 
| @@ -2276,6 +2282,7 @@ | |||
| 2276 | paintings: | 2282 | paintings: | 
| 2277 | - id: arrows_painting_11 | 2283 | - id: arrows_painting_11 | 
| 2278 | orientation: east | 2284 | orientation: east | 
| 2285 | req_blocked_when_no_doors: True # owl hallway in vanilla doors | ||
| 2279 | Courtyard: | 2286 | Courtyard: | 
| 2280 | entrances: | 2287 | entrances: | 
| 2281 | Roof: True | 2288 | Roof: True | 
| @@ -5755,11 +5762,13 @@ | |||
| 5755 | move: True | 5762 | move: True | 
| 5756 | required_door: | 5763 | required_door: | 
| 5757 | door: Exit | 5764 | door: Exit | 
| 5765 | req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors | ||
| 5758 | - id: symmetry_painting_a_6 | 5766 | - id: symmetry_painting_a_6 | 
| 5759 | orientation: west | 5767 | orientation: west | 
| 5760 | exit_only: True | 5768 | exit_only: True | 
| 5761 | - id: symmetry_painting_b_6 | 5769 | - id: symmetry_painting_b_6 | 
| 5762 | orientation: north | 5770 | orientation: north | 
| 5771 | req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors | ||
| 5763 | Arrow Garden: | 5772 | Arrow Garden: | 
| 5764 | entrances: | 5773 | entrances: | 
| 5765 | The Wondrous: | 5774 | The Wondrous: | 
| @@ -6914,6 +6923,7 @@ | |||
| 6914 | paintings: | 6923 | paintings: | 
| 6915 | - id: clock_painting_3 | 6924 | - id: clock_painting_3 | 
| 6916 | orientation: east | 6925 | orientation: east | 
| 6926 | req_blocked: True # outside the wise (with or without door shuffle) | ||
| 6917 | The Red: | 6927 | The Red: | 
| 6918 | entrances: | 6928 | entrances: | 
| 6919 | Roof: True | 6929 | Roof: True | 
| @@ -7362,6 +7372,7 @@ | |||
| 7362 | paintings: | 7372 | paintings: | 
| 7363 | - id: hi_solved_painting4 | 7373 | - id: hi_solved_painting4 | 
| 7364 | orientation: south | 7374 | orientation: south | 
| 7375 | req_blocked_when_no_doors: True # owl hallway in vanilla doors | ||
| 7365 | Challenge Room: | 7376 | Challenge Room: | 
| 7366 | entrances: | 7377 | entrances: | 
| 7367 | Welcome Back Area: | 7378 | Welcome Back Area: | 
| diff --git a/player_logic.py b/player_logic.py index 217ad91..66fe317 100644 --- a/player_logic.py +++ b/player_logic.py | |||
| @@ -241,43 +241,46 @@ class LingoPlayerLogic: | |||
| 241 | 241 | ||
| 242 | door_shuffle = world.options.shuffle_doors | 242 | door_shuffle = world.options.shuffle_doors | 
| 243 | 243 | ||
| 244 | # Determine the set of exit paintings. All required-exit paintings are included, as are all | 244 | # First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to | 
| 245 | # required-when-no-doors paintings if door shuffle is off. We then fill the set with random other paintings. | 245 | # required paintings. | 
| 246 | chosen_exits = [] | 246 | req_exits = [] | 
| 247 | required_painting_rooms = REQUIRED_PAINTING_ROOMS | ||
| 247 | if door_shuffle == ShuffleDoors.option_none: | 248 | if door_shuffle == ShuffleDoors.option_none: | 
| 248 | chosen_exits = [painting_id for painting_id, painting in PAINTINGS.items() | 249 | required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS | 
| 249 | if painting.required_when_no_doors] | 250 | req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors] | 
| 250 | chosen_exits += [painting_id for painting_id, painting in PAINTINGS.items() | 251 | req_enterable = [painting_id for painting_id, painting in PAINTINGS.items() | 
| 251 | if painting.exit_only and painting.required] | 252 | if not painting.exit_only and not painting.disable and not painting.req_blocked and | 
| 253 | not painting.req_blocked_when_no_doors and painting.room not in required_painting_rooms] | ||
| 254 | else: | ||
| 255 | req_enterable = [painting_id for painting_id, painting in PAINTINGS.items() | ||
| 256 | if not painting.exit_only and not painting.disable and not painting.req_blocked and | ||
| 257 | painting.room not in required_painting_rooms] | ||
| 258 | req_exits += [painting_id for painting_id, painting in PAINTINGS.items() | ||
| 259 | if painting.exit_only and painting.required] | ||
| 260 | req_entrances = world.random.sample(req_enterable, len(req_exits)) | ||
| 261 | |||
| 262 | self.PAINTING_MAPPING = dict(zip(req_entrances, req_exits)) | ||
| 263 | |||
| 264 | # Next, determine the rest of the exit paintings. | ||
| 252 | exitable = [painting_id for painting_id, painting in PAINTINGS.items() | 265 | exitable = [painting_id for painting_id, painting in PAINTINGS.items() | 
| 253 | if not painting.enter_only and not painting.disable and not painting.required] | 266 | if not painting.enter_only and not painting.disable and painting_id not in req_exits and | 
| 254 | chosen_exits += world.random.sample(exitable, PAINTING_EXITS - len(chosen_exits)) | 267 | painting_id not in req_entrances] | 
| 268 | nonreq_exits = world.random.sample(exitable, PAINTING_EXITS - len(req_exits)) | ||
| 269 | chosen_exits = req_exits + nonreq_exits | ||
| 255 | 270 | ||
| 256 | # Determine the set of entrance paintings. | 271 | # Determine the rest of the entrance paintings. | 
| 257 | enterable = [painting_id for painting_id, painting in PAINTINGS.items() | 272 | enterable = [painting_id for painting_id, painting in PAINTINGS.items() | 
| 258 | if not painting.exit_only and not painting.disable and painting_id not in chosen_exits] | 273 | if not painting.exit_only and not painting.disable and painting_id not in chosen_exits and | 
| 259 | chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES) | 274 | painting_id not in req_entrances] | 
| 275 | chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES - len(req_entrances)) | ||
| 260 | 276 | ||
| 261 | # Create a mapping from entrances to exits. | 277 | # Assign one entrance to each non-required exit, to ensure that the total number of exits is achieved. | 
| 262 | for warp_exit in chosen_exits: | 278 | for warp_exit in nonreq_exits: | 
| 263 | warp_enter = world.random.choice(chosen_entrances) | 279 | warp_enter = world.random.choice(chosen_entrances) | 
| 264 | |||
| 265 | # Check whether this is a warp from a required painting room to another (or the same) required painting | ||
| 266 | # room. This could cause a cycle that would make certain regions inaccessible. | ||
| 267 | warp_exit_room = PAINTINGS[warp_exit].room | ||
| 268 | warp_enter_room = PAINTINGS[warp_enter].room | ||
| 269 | |||
| 270 | required_painting_rooms = REQUIRED_PAINTING_ROOMS | ||
| 271 | if door_shuffle == ShuffleDoors.option_none: | ||
| 272 | required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS | ||
| 273 | |||
| 274 | if warp_exit_room in required_painting_rooms and warp_enter_room in required_painting_rooms: | ||
| 275 | # This shuffling is non-workable. Start over. | ||
| 276 | return False | ||
| 277 | |||
| 278 | chosen_entrances.remove(warp_enter) | 280 | chosen_entrances.remove(warp_enter) | 
| 279 | self.PAINTING_MAPPING[warp_enter] = warp_exit | 281 | self.PAINTING_MAPPING[warp_enter] = warp_exit | 
| 280 | 282 | ||
| 283 | # Assign each of the remaining entrances to any required or non-required exit. | ||
| 281 | for warp_enter in chosen_entrances: | 284 | for warp_enter in chosen_entrances: | 
| 282 | warp_exit = world.random.choice(chosen_exits) | 285 | warp_exit = world.random.choice(chosen_exits) | 
| 283 | self.PAINTING_MAPPING[warp_enter] = warp_exit | 286 | self.PAINTING_MAPPING[warp_enter] = warp_exit | 
| @@ -292,7 +295,8 @@ class LingoPlayerLogic: | |||
| 292 | # Just for sanity's sake, ensure that all required painting rooms are accessed. | 295 | # Just for sanity's sake, ensure that all required painting rooms are accessed. | 
| 293 | for painting_id, painting in PAINTINGS.items(): | 296 | for painting_id, painting in PAINTINGS.items(): | 
| 294 | if painting_id not in self.PAINTING_MAPPING.values() \ | 297 | if painting_id not in self.PAINTING_MAPPING.values() \ | 
| 295 | and (painting.required or (painting.required_when_no_doors and door_shuffle == 0)): | 298 | and (painting.required or (painting.required_when_no_doors and | 
| 299 | door_shuffle == ShuffleDoors.option_none)): | ||
| 296 | return False | 300 | return False | 
| 297 | 301 | ||
| 298 | return True | 302 | return True | 
| diff --git a/static_logic.py b/static_logic.py index d122169..f6690f9 100644 --- a/static_logic.py +++ b/static_logic.py | |||
| @@ -63,6 +63,8 @@ class Painting(NamedTuple): | |||
| 63 | required_door: Optional[RoomAndDoor] | 63 | required_door: Optional[RoomAndDoor] | 
| 64 | disable: bool | 64 | disable: bool | 
| 65 | move: bool | 65 | move: bool | 
| 66 | req_blocked: bool | ||
| 67 | req_blocked_when_no_doors: bool | ||
| 66 | 68 | ||
| 67 | 69 | ||
| 68 | class Progression(NamedTuple): | 70 | class Progression(NamedTuple): | 
| @@ -471,6 +473,16 @@ def process_painting(room_name, painting_data): | |||
| 471 | else: | 473 | else: | 
| 472 | enter_only = False | 474 | enter_only = False | 
| 473 | 475 | ||
| 476 | if "req_blocked" in painting_data: | ||
| 477 | req_blocked = painting_data["req_blocked"] | ||
| 478 | else: | ||
| 479 | req_blocked = False | ||
| 480 | |||
| 481 | if "req_blocked_when_no_doors" in painting_data: | ||
| 482 | req_blocked_when_no_doors = painting_data["req_blocked_when_no_doors"] | ||
| 483 | else: | ||
| 484 | req_blocked_when_no_doors = False | ||
| 485 | |||
| 474 | required_door = None | 486 | required_door = None | 
| 475 | if "required_door" in painting_data: | 487 | if "required_door" in painting_data: | 
| 476 | door = painting_data["required_door"] | 488 | door = painting_data["required_door"] | 
| @@ -480,7 +492,8 @@ def process_painting(room_name, painting_data): | |||
| 480 | ) | 492 | ) | 
| 481 | 493 | ||
| 482 | painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation, | 494 | painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation, | 
| 483 | required_painting, rwnd, required_door, disable_painting, move_painting) | 495 | required_painting, rwnd, required_door, disable_painting, move_painting, req_blocked, | 
| 496 | req_blocked_when_no_doors) | ||
| 484 | PAINTINGS[painting_id] = painting_obj | 497 | PAINTINGS[painting_id] = painting_obj | 
| 485 | PAINTINGS_BY_ROOM[room_name].append(painting_obj) | 498 | PAINTINGS_BY_ROOM[room_name].append(painting_obj) | 
| 486 | 499 | ||
