Lingo: Various generation optimizations (#2479)

Almost all of the events have been eradicated, which significantly improves both generation speed and playthrough calculation.

Previously, checking for access to a location involved checking for access to each panel in the location, as well as recursively checking for access to any panels required by those panels. This potentially performed the same check multiple times. The access requirements for locations are now calculated and flattened in generate_early, so that the access function can directly check for the required rooms, doors, and colors.

These flattened access requirements are also used for Entrance checking, and register_indirect_condition is used to make sure that can_reach(Region) is safe to use.

The Mastery and Level 2 rules now just run a bunch of access rules and count the number of them that succeed, instead of relying on event items.

Finally: the Level 2 panel hunt is now enabled even when Level 2 is not the victory condition, as I feel that generation is fast enough now for that to be acceptable.
This commit is contained in:
Star Rauchenberger
2023-11-25 07:09:08 -05:00
committed by GitHub
parent 8a852abdc4
commit 6dccf36f88
7 changed files with 330 additions and 169 deletions

View File

@@ -1,23 +1,23 @@
from typing import TYPE_CHECKING
from BaseClasses import CollectionState
from .options import VictoryCondition
from .player_logic import LingoPlayerLogic, PlayerLocation
from .static_logic import PANELS_BY_ROOM, PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, RoomAndDoor
from .player_logic import AccessRequirements, LingoPlayerLogic, PlayerLocation
from .static_logic import PROGRESSION_BY_ROOM, PROGRESSIVE_ITEMS, RoomAndDoor
if TYPE_CHECKING:
from . import LingoWorld
def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, player: int,
def lingo_can_use_entrance(state: CollectionState, room: str, door: RoomAndDoor, world: "LingoWorld",
player_logic: LingoPlayerLogic):
if door is None:
return True
return _lingo_can_open_door(state, room, room if door.room is None else door.room, door.door, player, player_logic)
effective_room = room if door.room is None else door.room
return _lingo_can_open_door(state, effective_room, door.door, world, player_logic)
def lingo_can_use_pilgrimage(state: CollectionState, player: int, player_logic: LingoPlayerLogic):
def lingo_can_use_pilgrimage(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
fake_pilgrimage = [
["Second Room", "Exit Door"], ["Crossroads", "Tower Entrance"],
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
@@ -28,77 +28,77 @@ def lingo_can_use_pilgrimage(state: CollectionState, player: int, player_logic:
["Outside The Agreeable", "Tenacious Entrance"]
]
for entrance in fake_pilgrimage:
if not state.has(player_logic.ITEM_BY_DOOR[entrance[0]][entrance[1]], player):
if not _lingo_can_open_door(state, entrance[0], entrance[1], world, player_logic):
return False
return True
def lingo_can_use_location(state: CollectionState, location: PlayerLocation, room_name: str, world: "LingoWorld",
def lingo_can_use_location(state: CollectionState, location: PlayerLocation, world: "LingoWorld",
player_logic: LingoPlayerLogic):
for panel in location.panels:
panel_room = room_name if panel.room is None else panel.room
if not _lingo_can_solve_panel(state, room_name, panel_room, panel.panel, world, player_logic):
return False
return True
return _lingo_can_satisfy_requirements(state, location.access, world, player_logic)
def lingo_can_use_mastery_location(state: CollectionState, world: "LingoWorld"):
return state.has("Mastery Achievement", world.player, world.options.mastery_achievements.value)
def lingo_can_use_mastery_location(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
satisfied_count = 0
for access_req in player_logic.mastery_reqs:
if _lingo_can_satisfy_requirements(state, access_req, world, player_logic):
satisfied_count += 1
return satisfied_count >= world.options.mastery_achievements.value
def _lingo_can_open_door(state: CollectionState, start_room: str, room: str, door: str, player: int,
player_logic: LingoPlayerLogic):
"""
Determines whether a door can be opened
"""
item_name = player_logic.ITEM_BY_DOOR[room][door]
if item_name in PROGRESSIVE_ITEMS:
progression = PROGRESSION_BY_ROOM[room][door]
return state.has(item_name, player, progression.index)
return state.has(item_name, player)
def lingo_can_use_level_2_location(state: CollectionState, world: "LingoWorld", player_logic: LingoPlayerLogic):
counted_panels = 0
state.update_reachable_regions(world.player)
for region in state.reachable_regions[world.player]:
for access_req, panel_count in player_logic.counting_panel_reqs.get(region.name, []):
if _lingo_can_satisfy_requirements(state, access_req, world, player_logic):
counted_panels += panel_count
if counted_panels >= world.options.level_2_requirement.value - 1:
return True
return False
def _lingo_can_solve_panel(state: CollectionState, start_room: str, room: str, panel: str, world: "LingoWorld",
player_logic: LingoPlayerLogic):
"""
Determines whether a panel can be solved
"""
if start_room != room and not state.can_reach(room, "Region", world.player):
return False
if room == "Second Room" and panel == "ANOTHER TRY" \
and world.options.victory_condition == VictoryCondition.option_level_2 \
and not state.has("Counting Panel Solved", world.player, world.options.level_2_requirement.value - 1):
return False
panel_object = PANELS_BY_ROOM[room][panel]
for req_room in panel_object.required_rooms:
def _lingo_can_satisfy_requirements(state: CollectionState, access: AccessRequirements, world: "LingoWorld",
player_logic: LingoPlayerLogic):
for req_room in access.rooms:
if not state.can_reach(req_room, "Region", world.player):
return False
for req_door in panel_object.required_doors:
if not _lingo_can_open_door(state, start_room, room if req_door.room is None else req_door.room,
req_door.door, world.player, player_logic):
for req_door in access.doors:
if not _lingo_can_open_door(state, req_door.room, req_door.door, world, player_logic):
return False
for req_panel in panel_object.required_panels:
if not _lingo_can_solve_panel(state, start_room, room if req_panel.room is None else req_panel.room,
req_panel.panel, world, player_logic):
return False
if len(panel_object.colors) > 0 and world.options.shuffle_colors:
for color in panel_object.colors:
if len(access.colors) > 0 and world.options.shuffle_colors:
for color in access.colors:
if not state.has(color.capitalize(), world.player):
return False
return True
def make_location_lambda(location: PlayerLocation, room_name: str, world: "LingoWorld", player_logic: LingoPlayerLogic):
if location.name == player_logic.MASTERY_LOCATION:
return lambda state: lingo_can_use_mastery_location(state, world)
def _lingo_can_open_door(state: CollectionState, room: str, door: str, world: "LingoWorld",
player_logic: LingoPlayerLogic):
"""
Determines whether a door can be opened
"""
if door not in player_logic.item_by_door.get(room, {}):
return _lingo_can_satisfy_requirements(state, player_logic.door_reqs[room][door], world, player_logic)
return lambda state: lingo_can_use_location(state, location, room_name, world, player_logic)
item_name = player_logic.item_by_door[room][door]
if item_name in PROGRESSIVE_ITEMS:
progression = PROGRESSION_BY_ROOM[room][door]
return state.has(item_name, world.player, progression.index)
return state.has(item_name, world.player)
def make_location_lambda(location: PlayerLocation, world: "LingoWorld", player_logic: LingoPlayerLogic):
if location.name == player_logic.mastery_location:
return lambda state: lingo_can_use_mastery_location(state, world, player_logic)
if world.options.level_2_requirement > 1\
and (location.name == "Second Room - ANOTHER TRY" or location.name == player_logic.level_2_location):
return lambda state: lingo_can_use_level_2_location(state, world, player_logic)
return lambda state: lingo_can_use_location(state, location, world, player_logic)