from enum import IntEnum, auto from .generated import data_pb2 as data_pb2 from .items import SYMBOL_ITEMS from typing import TYPE_CHECKING, NamedTuple from .options import ShuffleLetters, CyanDoorBehavior 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(real_l, 0) + 1, 2) 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:
extends Node
var apworld_reader
func _init(path):
apworld_reader = ZIPReader.new()
apworld_reader.open(path)
func _get_true_path(path):
if path.begins_with("../"):
return "lingo2/%s" % path.substr(3)
else:
return "lingo2/client/%s" % path
func load_script(path):
var true_path = _get_true_path(path)
var script = GDScript.new()
script.source_code = apworld_reader.read_file(true_path).get_string_from_utf8()
script.reload()
return script
func read_path(path):
var true_path = _get_true_path(path)
return apworld_reader.read_file(true_path)
func load_script_as_scene(path, scene_name):
var script = load_script(path)
var instance = script.new()
instance.name = scene_name
get_tree().unload_current_scene()
_load_scene.call_deferred(instance)
func _load_scene(instance):
get_tree().get_root().add_child(instance)
get_tree().current_scene = instance