From c5a564bfc9bcf422d04c9016f56d65260b007c67 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 13 Dec 2025 07:10:05 -0500 Subject: Refactor AccessRequirements --- apworld/player_logic.py | 169 +----------------------------------------------- 1 file changed, 1 insertion(+), 168 deletions(-) (limited to 'apworld/player_logic.py') diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 3ee8f38..892dac5 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py @@ -5,6 +5,7 @@ from .items import SYMBOL_ITEMS from typing import TYPE_CHECKING, NamedTuple from .options import ShuffleLetters, CyanDoorBehavior +from .rules import AccessRequirements if TYPE_CHECKING: from . import Lingo2World @@ -20,174 +21,6 @@ def calculate_letter_histogram(solution: str) -> dict[str, int]: return histogram -class AccessRequirements: - items: set[str] - progressives: dict[str, int] - rooms: set[str] - letters: dict[str, int] - cyans: bool - - # This is an AND of ORs. - or_logic: list[list["AccessRequirements"]] - - # When complete_at is set, at least that many of the requirements in possibilities must be accessible. This should - # only be used for doors with complete_at > 1, as or_logic is more efficient for complete_at == 1. - complete_at: int | None - possibilities: list["AccessRequirements"] - - def __init__(self): - self.items = set() - self.progressives = dict() - self.rooms = set() - self.letters = dict() - self.cyans = False - self.or_logic = list() - self.complete_at = None - self.possibilities = list() - - def copy(self) -> "AccessRequirements": - reqs = AccessRequirements() - reqs.items = self.items.copy() - reqs.progressives = self.progressives.copy() - reqs.rooms = self.rooms.copy() - reqs.letters = self.letters.copy() - reqs.cyans = self.cyans - reqs.or_logic = [[other_req.copy() for other_req in disjunction] for disjunction in self.or_logic] - reqs.complete_at = self.complete_at - reqs.possibilities = self.possibilities.copy() - return reqs - - def merge(self, other: "AccessRequirements"): - for item in other.items: - self.items.add(item) - - for item, amount in other.progressives.items(): - self.progressives[item] = max(amount, self.progressives.get(item, 0)) - - for room in other.rooms: - self.rooms.add(room) - - for letter, level in other.letters.items(): - self.letters[letter] = max(self.letters.get(letter, 0), level) - - self.cyans = self.cyans or other.cyans - - for disjunction in other.or_logic: - self.or_logic.append([sub_req.copy() for sub_req in disjunction]) - - if other.complete_at is not None: - # Merging multiple requirements that use complete_at sucks, and is part of why we want to minimize use of - # it. If both requirements use complete_at, we will cheat by using the or_logic field, which supports - # conjunctions of requirements. - if self.complete_at is not None: - print("Merging requirements with complete_at > 1. This is messy and should be avoided!") - - left_req = AccessRequirements() - left_req.complete_at = self.complete_at - left_req.possibilities = [sub_req.copy() for sub_req in self.possibilities] - self.or_logic.append([left_req]) - - self.complete_at = None - self.possibilities = list() - - right_req = AccessRequirements() - right_req.complete_at = other.complete_at - right_req.possibilities = [sub_req.copy() for sub_req in other.possibilities] - self.or_logic.append([right_req]) - else: - self.complete_at = other.complete_at - self.possibilities = [sub_req.copy() for sub_req in other.possibilities] - - def is_empty(self) -> bool: - return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0 - and not self.cyans and len(self.or_logic) == 0 and self.complete_at is None) - - def __eq__(self, other: "AccessRequirements"): - return (self.items == other.items and self.progressives == other.progressives and self.rooms == other.rooms and - self.letters == other.letters and self.cyans == other.cyans and self.or_logic == other.or_logic and - self.complete_at == other.complete_at and self.possibilities == other.possibilities) - - def simplify(self): - resimplify = False - - if len(self.or_logic) > 0: - old_or_logic = self.or_logic - - def remove_redundant(sub_reqs: "AccessRequirements"): - new_reqs = sub_reqs.copy() - new_reqs.letters = {l: v for l, v in new_reqs.letters.items() if self.letters.get(l, 0) < v} - if new_reqs != sub_reqs: - return new_reqs - else: - return sub_reqs - - self.or_logic = [] - for disjunction in old_or_logic: - new_disjunction = [] - for ssr in disjunction: - new_ssr = remove_redundant(ssr) - if not new_ssr.is_empty(): - new_disjunction.append(new_ssr) - else: - new_disjunction.clear() - break - if len(new_disjunction) == 1: - self.merge(new_disjunction[0]) - resimplify = True - elif len(new_disjunction) > 1: - if all(cjr == new_disjunction[0] for cjr in new_disjunction): - self.merge(new_disjunction[0]) - resimplify = True - else: - self.or_logic.append(new_disjunction) - - if resimplify: - self.simplify() - - def get_referenced_rooms(self): - result = set(self.rooms) - - for disjunction in self.or_logic: - for sub_req in disjunction: - result = result.union(sub_req.get_referenced_rooms()) - - for sub_req in self.possibilities: - result = result.union(sub_req.get_referenced_rooms()) - - return result - - def remove_room(self, room: str): - if room in self.rooms: - self.rooms.remove(room) - - for disjunction in self.or_logic: - for sub_req in disjunction: - sub_req.remove_room(room) - - for sub_req in self.possibilities: - sub_req.remove_room(room) - - def __repr__(self): - parts = [] - if len(self.items) > 0: - parts.append(f"items={self.items}") - if len(self.progressives) > 0: - parts.append(f"progressives={self.progressives}") - if len(self.rooms) > 0: - parts.append(f"rooms={self.rooms}") - if len(self.letters) > 0: - parts.append(f"letters={self.letters}") - if self.cyans: - parts.append(f"cyans=True") - if len(self.or_logic) > 0: - parts.append(f"or_logic={self.or_logic}") - if self.complete_at is not None: - parts.append(f"complete_at={self.complete_at}") - if len(self.possibilities) > 0: - parts.append(f"possibilities={self.possibilities}") - return "AccessRequirements(" + ", ".join(parts) + ")" - - class PlayerLocation(NamedTuple): code: int | None reqs: AccessRequirements -- cgit 1.4.1