Skip to content

Commit

Permalink
Lingo: Fix edge case painting shuffle accessibility issues (#2441)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
hatkirby authored Nov 10, 2023
1 parent b5bd957 commit f444d57
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 30 deletions.
11 changes: 11 additions & 0 deletions worlds/lingo/LL1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
62 changes: 33 additions & 29 deletions worlds/lingo/player_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,43 +241,46 @@ def randomize_paintings(self, world: "LingoWorld") -> bool:

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
Expand All @@ -292,7 +295,8 @@ def randomize_paintings(self, world: "LingoWorld") -> bool:
# 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
15 changes: 14 additions & 1 deletion worlds/lingo/static_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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"]
Expand All @@ -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)

Expand Down

0 comments on commit f444d57

Please sign in to comment.