about summary refs log tree commit diff stats
path: root/.clang-format
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-08-30 18:36:12 -0400
committerStar Rauchenberger <fefferburbia@gmail.com>2025-08-30 18:36:12 -0400
commitdba2528e0833050f81175358976fc7cac31c867d (patch)
treeb2754225612b96fbdfb86d4d23da413d2ef0b7d0 /.clang-format
parent8ad846ce1ea9e12026512ea81f4baf1309d2b6d8 (diff)
downloadlingo2-archipelago-dba2528e0833050f81175358976fc7cac31c867d.tar.gz
lingo2-archipelago-dba2528e0833050f81175358976fc7cac31c867d.tar.bz2
lingo2-archipelago-dba2528e0833050f81175358976fc7cac31c867d.zip
[Client] Handle locations needing specific answers
Diffstat (limited to '.clang-format')
0 files changed, 0 insertions, 0 deletions
113' href='#n113'>113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
from .generated import data_pb2 as data_pb2
from typing import TYPE_CHECKING, NamedTuple

if TYPE_CHECKING:
    from . import Lingo2World


def calculate_letter_histogram(solution: str) -> dict[str, int]:
    histogram = dict()
    for l in solution:
        if l.isalpha():
            real_l = l.upper()
            histogram[real_l] = min(histogram.get(l, 0) + 1, 2)

    return histogram


class AccessRequirements:
    items: set[str]
    rooms: set[str]
    symbols: set[str]
    letters: dict[str, int]

    # This is an AND of ORs.
    or_logic: list[list["AccessRequirements"]]

    def __init__(self):
        self.items = set()
        self.rooms = set()
        self.symbols = set()
        self.letters = dict()
        self.or_logic = list()

    def add_solution(self, solution: str):
        histogram = calculate_letter_histogram(solution)

        for l, a in histogram.items():
            self.letters[l] = max(self.letters.get(l, 0), histogram.get(l))

    def merge(self, other: "AccessRequirements"):
        for item in other.items:
            self.items.add(item)

        for room in other.rooms:
            self.rooms.add(room)

        for symbol in other.symbols:
            self.symbols.add(symbol)

        for letter, level in other.letters.items():
            self.letters[letter] = max(self.letters.get(letter, 0), level)

        for disjunction in other.or_logic:
            self.or_logic.append(disjunction)


class PlayerLocation(NamedTuple):
    code: int | None
    reqs: AccessRequirements


class Lingo2PlayerLogic:
    world: "Lingo2World"

    locations_by_room: dict[int, list[PlayerLocation]]

    panel_reqs: dict[int, AccessRequirements]
    proxy_reqs: dict[int, dict[str, AccessRequirements]]
    door_reqs: dict[int, AccessRequirements]

    real_items: list[str]

    def __init__(self, world: "Lingo2World"):
        self.world = world
        self.locations_by_room = {}
        self.panel_reqs = dict()
        self.proxy_reqs = dict()
        self.door_reqs = dict()
        self.real_items = list()

        for door in world.static_logic.objects.doors:
            if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY]:
                self.locations_by_room.setdefault(door.room_id, []).append(PlayerLocation(door.ap_id,
                                                                                          self.get_door_reqs(door.id)))

            if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors:
                self.real_items.append(self.world.static_logic.get_door_item_name(door.id))

        for letter in world.static_logic.objects.letters:
            self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id,
                                                                                        AccessRequirements()))

        for mastery in world.static_logic.objects.masteries:
            self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
                                                                                         AccessRequirements()))

    def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
        if answer is None:
            if panel_id not in self.panel_reqs:
                self.panel_reqs[panel_id] = self.calculate_panel_reqs(panel_id, answer)

            return self.panel_reqs.get(panel_id)
        else:
            if panel_id not in self.proxy_reqs or answer not in self.proxy_reqs.get(panel_id):
                self.proxy_reqs.setdefault(panel_id, {})[answer] = self.calculate_panel_reqs(panel_id, answer)

            return self.proxy_reqs.get(panel_id).get(answer)

    def calculate_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
        panel = self.world.static_logic.objects.panels[panel_id]
        reqs = AccessRequirements()

        reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.room_id))

        if answer is not None:
            reqs.add_solution(answer)
        elif len(panel.proxies) > 0:
            for proxy in panel.proxies:
                proxy_reqs = AccessRequirements()
                proxy_reqs.add_solution(proxy.answer)

                reqs.or_logic.append([proxy_reqs])
        else:
            reqs.add_solution(panel.answer)

        for symbol in panel.symbols:
            reqs.symbols.add(symbol)

        if panel.HasField("required_door"):
            door_reqs = self.get_door_reqs(panel.required_door)
            reqs.merge(door_reqs)

        if panel.HasField("required_room"):
            reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.required_room))

        return reqs

    def get_door_reqs(self, door_id: int) -> AccessRequirements:
        if door_id not in self.door_reqs:
            self.door_reqs[door_id] = self.calculate_door_reqs(door_id)

        return self.door_reqs.get(door_id)

    def calculate_door_reqs(self, door_id: int) -> AccessRequirements:
        door = self.world.static_logic.objects.doors[door_id]
        reqs = AccessRequirements()

        use_item = False
        if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors:
            use_item = True

        if use_item:
            reqs.items.add(self.world.static_logic.get_door_item_name(door.id))
        else:
            # TODO: complete_at, control_center_color, switches, keyholders
            for proxy in door.panels:
                panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
                reqs.merge(panel_reqs)

            for room in door.rooms:
                reqs.rooms.add(self.world.static_logic.get_room_region_name(room))

        return reqs