Lingo: Fix edge case painting shuffle accessibility issues ()

* 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.
This commit is contained in:
Star Rauchenberger 2023-11-10 14:07:56 -05:00 committed by GitHub
parent b5bd95771d
commit f444d570d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 58 additions and 30 deletions

View File

@ -97,6 +97,11 @@
# Use "required_when_no_doors" instead if it would be
# possible to enter the room without the painting in door
# shuffle mode.
# - req_blocked: Marks that a painting cannot be an entrance leading to a
# required painting. Paintings within a room that has a
# required painting are automatically req blocked.
# Use "req_blocked_when_no_doors" instead if it would be
# fine in door shuffle mode.
# - move: Denotes that the painting is able to move.
Starting Room:
entrances:
@ -2210,6 +2215,7 @@
- id: map_painting2
orientation: north
enter_only: True # otherwise you might just skip the whole game!
req_blocked_when_no_doors: True # owl hallway in vanilla doors
Roof:
entrances:
Orange Tower Seventh Floor: True
@ -2276,6 +2282,7 @@
paintings:
- id: arrows_painting_11
orientation: east
req_blocked_when_no_doors: True # owl hallway in vanilla doors
Courtyard:
entrances:
Roof: True
@ -5755,11 +5762,13 @@
move: True
required_door:
door: Exit
req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors
- id: symmetry_painting_a_6
orientation: west
exit_only: True
- id: symmetry_painting_b_6
orientation: north
req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors
Arrow Garden:
entrances:
The Wondrous:
@ -6914,6 +6923,7 @@
paintings:
- id: clock_painting_3
orientation: east
req_blocked: True # outside the wise (with or without door shuffle)
The Red:
entrances:
Roof: True
@ -7362,6 +7372,7 @@
paintings:
- id: hi_solved_painting4
orientation: south
req_blocked_when_no_doors: True # owl hallway in vanilla doors
Challenge Room:
entrances:
Welcome Back Area:

View File

@ -241,43 +241,46 @@ class LingoPlayerLogic:
door_shuffle = world.options.shuffle_doors
# Determine the set of exit paintings. All required-exit paintings are included, as are all
# required-when-no-doors paintings if door shuffle is off. We then fill the set with random other paintings.
chosen_exits = []
# First, assign mappings to the required-exit paintings. We ensure that req-blocked paintings do not lead to
# required paintings.
req_exits = []
required_painting_rooms = REQUIRED_PAINTING_ROOMS
if door_shuffle == ShuffleDoors.option_none:
chosen_exits = [painting_id for painting_id, painting in PAINTINGS.items()
if painting.required_when_no_doors]
chosen_exits += [painting_id for painting_id, painting in PAINTINGS.items()
if painting.exit_only and painting.required]
required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors]
req_enterable = [painting_id for painting_id, painting in PAINTINGS.items()
if not painting.exit_only and not painting.disable and not painting.req_blocked and
not painting.req_blocked_when_no_doors and painting.room not in required_painting_rooms]
else:
req_enterable = [painting_id for painting_id, painting in PAINTINGS.items()
if not painting.exit_only and not painting.disable and not painting.req_blocked and
painting.room not in required_painting_rooms]
req_exits += [painting_id for painting_id, painting in PAINTINGS.items()
if painting.exit_only and painting.required]
req_entrances = world.random.sample(req_enterable, len(req_exits))
self.PAINTING_MAPPING = dict(zip(req_entrances, req_exits))
# Next, determine the rest of the exit paintings.
exitable = [painting_id for painting_id, painting in PAINTINGS.items()
if not painting.enter_only and not painting.disable and not painting.required]
chosen_exits += world.random.sample(exitable, PAINTING_EXITS - len(chosen_exits))
if not painting.enter_only and not painting.disable and painting_id not in req_exits and
painting_id not in req_entrances]
nonreq_exits = world.random.sample(exitable, PAINTING_EXITS - len(req_exits))
chosen_exits = req_exits + nonreq_exits
# Determine the set of entrance paintings.
# Determine the rest of the entrance paintings.
enterable = [painting_id for painting_id, painting in PAINTINGS.items()
if not painting.exit_only and not painting.disable and painting_id not in chosen_exits]
chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES)
if not painting.exit_only and not painting.disable and painting_id not in chosen_exits and
painting_id not in req_entrances]
chosen_entrances = world.random.sample(enterable, PAINTING_ENTRANCES - len(req_entrances))
# Create a mapping from entrances to exits.
for warp_exit in chosen_exits:
# Assign one entrance to each non-required exit, to ensure that the total number of exits is achieved.
for warp_exit in nonreq_exits:
warp_enter = world.random.choice(chosen_entrances)
# Check whether this is a warp from a required painting room to another (or the same) required painting
# room. This could cause a cycle that would make certain regions inaccessible.
warp_exit_room = PAINTINGS[warp_exit].room
warp_enter_room = PAINTINGS[warp_enter].room
required_painting_rooms = REQUIRED_PAINTING_ROOMS
if door_shuffle == ShuffleDoors.option_none:
required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
if warp_exit_room in required_painting_rooms and warp_enter_room in required_painting_rooms:
# This shuffling is non-workable. Start over.
return False
chosen_entrances.remove(warp_enter)
self.PAINTING_MAPPING[warp_enter] = warp_exit
# Assign each of the remaining entrances to any required or non-required exit.
for warp_enter in chosen_entrances:
warp_exit = world.random.choice(chosen_exits)
self.PAINTING_MAPPING[warp_enter] = warp_exit
@ -292,7 +295,8 @@ class LingoPlayerLogic:
# Just for sanity's sake, ensure that all required painting rooms are accessed.
for painting_id, painting in PAINTINGS.items():
if painting_id not in self.PAINTING_MAPPING.values() \
and (painting.required or (painting.required_when_no_doors and door_shuffle == 0)):
and (painting.required or (painting.required_when_no_doors and
door_shuffle == ShuffleDoors.option_none)):
return False
return True

View File

@ -63,6 +63,8 @@ class Painting(NamedTuple):
required_door: Optional[RoomAndDoor]
disable: bool
move: bool
req_blocked: bool
req_blocked_when_no_doors: bool
class Progression(NamedTuple):
@ -471,6 +473,16 @@ def process_painting(room_name, painting_data):
else:
enter_only = False
if "req_blocked" in painting_data:
req_blocked = painting_data["req_blocked"]
else:
req_blocked = False
if "req_blocked_when_no_doors" in painting_data:
req_blocked_when_no_doors = painting_data["req_blocked_when_no_doors"]
else:
req_blocked_when_no_doors = False
required_door = None
if "required_door" in painting_data:
door = painting_data["required_door"]
@ -480,7 +492,8 @@ def process_painting(room_name, painting_data):
)
painting_obj = Painting(painting_id, room_name, enter_only, exit_only, orientation,
required_painting, rwnd, required_door, disable_painting, move_painting)
required_painting, rwnd, required_door, disable_painting, move_painting, req_blocked,
req_blocked_when_no_doors)
PAINTINGS[painting_id] = painting_obj
PAINTINGS_BY_ROOM[room_name].append(painting_obj)