Skip to content

Commit

Permalink
The Witness: mypy compliance (ArchipelagoMW#3112)
Browse files Browse the repository at this point in the history
* Make witness apworld mostly pass mypy

* Fix all remaining mypy errors except the core ones

* I'm a goofy stupid poopoo head

* Two more fixes

* ruff after merge

* Mypy for new stuff

* Oops

* Stricter ruff rules (that I already comply with :3)

* Deprecated ruff thing

* wait no i lied

* lol super nevermind

* I can actually be slightly more specific

* lint
  • Loading branch information
NewSoupVi authored Jul 2, 2024
1 parent b6925c5 commit 93617fa
Show file tree
Hide file tree
Showing 12 changed files with 299 additions and 269 deletions.
28 changes: 15 additions & 13 deletions worlds/witness/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from worlds.AutoWorld import WebWorld, World

from .data import static_items as static_witness_items
from .data import static_locations as static_witness_locations
from .data import static_logic as static_witness_logic
from .data.item_definition_classes import DoorItemDefinition, ItemData
from .data.utils import get_audio_logs
from .hints import CompactItemData, create_all_hints, make_compact_hint_data, make_laser_hints
from .locations import WitnessPlayerLocations, static_witness_locations
from .locations import WitnessPlayerLocations
from .options import TheWitnessOptions, witness_option_groups
from .player_items import WitnessItem, WitnessPlayerItems
from .player_logic import WitnessPlayerLogic
Expand Down Expand Up @@ -53,7 +54,8 @@ class WitnessWorld(World):
options: TheWitnessOptions

item_name_to_id = {
name: data.ap_code for name, data in static_witness_items.ITEM_DATA.items()
# ITEM_DATA doesn't have any event items in it
name: cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
}
location_name_to_id = static_witness_locations.ALL_LOCATIONS_TO_ID
item_name_groups = static_witness_items.ITEM_GROUPS
Expand Down Expand Up @@ -142,7 +144,7 @@ def generate_early(self) -> None:
)
self.player_regions: WitnessPlayerRegions = WitnessPlayerRegions(self.player_locations, self)

self.log_ids_to_hints = dict()
self.log_ids_to_hints = {}

self.determine_sufficient_progression()

Expand Down Expand Up @@ -279,7 +281,7 @@ def create_items(self) -> None:
remaining_item_slots = pool_size - sum(item_pool.values())

# Add puzzle skips.
num_puzzle_skips = self.options.puzzle_skip_amount
num_puzzle_skips = self.options.puzzle_skip_amount.value

if num_puzzle_skips > remaining_item_slots:
warning(f"{self.multiworld.get_player_name(self.player)}'s Witness world has insufficient locations"
Expand All @@ -301,21 +303,21 @@ def create_items(self) -> None:
if self.player_items.item_data[item_name].local_only:
self.options.local_items.value.add(item_name)

def fill_slot_data(self) -> dict:
self.log_ids_to_hints: Dict[int, CompactItemData] = dict()
self.laser_ids_to_hints: Dict[int, CompactItemData] = dict()
def fill_slot_data(self) -> Dict[str, Any]:
self.log_ids_to_hints: Dict[int, CompactItemData] = {}
self.laser_ids_to_hints: Dict[int, CompactItemData] = {}

already_hinted_locations = set()

# Laser hints

if self.options.laser_hints:
laser_hints = make_laser_hints(self, static_witness_items.ITEM_GROUPS["Lasers"])
laser_hints = make_laser_hints(self, sorted(static_witness_items.ITEM_GROUPS["Lasers"]))

for item_name, hint in laser_hints.items():
item_def = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name])
self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player)
already_hinted_locations.add(hint.location)
already_hinted_locations.add(cast(Location, hint.location))

# Audio Log Hints

Expand Down Expand Up @@ -378,13 +380,13 @@ class WitnessLocation(Location):
game: str = "The Witness"
entity_hex: int = -1

def __init__(self, player: int, name: str, address: Optional[int], parent, ch_hex: int = -1) -> None:
def __init__(self, player: int, name: str, address: Optional[int], parent: Region, ch_hex: int = -1) -> None:
super().__init__(player, name, address, parent)
self.entity_hex = ch_hex


def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlayerLocations,
region_locations=None, exits=None) -> Region:
region_locations: Optional[List[str]] = None, exits: Optional[List[str]] = None) -> Region:
"""
Create an Archipelago Region for The Witness
"""
Expand All @@ -399,11 +401,11 @@ def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlaye
entity_hex = int(
static_witness_logic.ENTITIES_BY_NAME[location]["entity_hex"], 0
)
location = WitnessLocation(
location_obj = WitnessLocation(
world.player, location, loc_id, ret, entity_hex
)

ret.locations.append(location)
ret.locations.append(location_obj)
if exits:
for single_exit in exits:
ret.exits.append(Entrance(world.player, single_exit, ret))
Expand Down
12 changes: 6 additions & 6 deletions worlds/witness/data/static_items.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, List
from typing import Dict, List, Set

from BaseClasses import ItemClassification

Expand All @@ -7,7 +7,7 @@
from .static_locations import ID_START

ITEM_DATA: Dict[str, ItemData] = {}
ITEM_GROUPS: Dict[str, List[str]] = {}
ITEM_GROUPS: Dict[str, Set[str]] = {}

# Useful items that are treated specially at generation time and should not be automatically added to the player's
# item list during get_progression_items.
Expand All @@ -22,13 +22,13 @@ def populate_items() -> None:

if definition.category is ItemCategory.SYMBOL:
classification = ItemClassification.progression
ITEM_GROUPS.setdefault("Symbols", []).append(item_name)
ITEM_GROUPS.setdefault("Symbols", set()).add(item_name)
elif definition.category is ItemCategory.DOOR:
classification = ItemClassification.progression
ITEM_GROUPS.setdefault("Doors", []).append(item_name)
ITEM_GROUPS.setdefault("Doors", set()).add(item_name)
elif definition.category is ItemCategory.LASER:
classification = ItemClassification.progression_skip_balancing
ITEM_GROUPS.setdefault("Lasers", []).append(item_name)
ITEM_GROUPS.setdefault("Lasers", set()).add(item_name)
elif definition.category is ItemCategory.USEFUL:
classification = ItemClassification.useful
elif definition.category is ItemCategory.FILLER:
Expand All @@ -47,7 +47,7 @@ def populate_items() -> None:
def get_item_to_door_mappings() -> Dict[int, List[int]]:
output: Dict[int, List[int]] = {}
for item_name, item_data in ITEM_DATA.items():
if not isinstance(item_data.definition, DoorItemDefinition):
if not isinstance(item_data.definition, DoorItemDefinition) or item_data.ap_code is None:
continue
output[item_data.ap_code] = [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes]
return output
Expand Down
14 changes: 8 additions & 6 deletions worlds/witness/data/static_locations.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Dict, Set, cast

from . import static_logic as static_witness_logic

ID_START = 158000
Expand Down Expand Up @@ -441,17 +443,17 @@
"Town Obelisk Side 6",
}

ALL_LOCATIONS_TO_ID = dict()
ALL_LOCATIONS_TO_ID: Dict[str, int] = {}

AREA_LOCATION_GROUPS = dict()
AREA_LOCATION_GROUPS: Dict[str, Set[str]] = {}


def get_id(entity_hex: str) -> str:
def get_id(entity_hex: str) -> int:
"""
Calculates the location ID for any given location
"""

return static_witness_logic.ENTITIES_BY_HEX[entity_hex]["id"]
return cast(int, static_witness_logic.ENTITIES_BY_HEX[entity_hex]["id"])


def get_event_name(entity_hex: str) -> str:
Expand All @@ -461,7 +463,7 @@ def get_event_name(entity_hex: str) -> str:

action = " Opened" if static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] == "Door" else " Solved"

return static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"] + action
return cast(str, static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"]) + action


ALL_LOCATIONS_TO_IDS = {
Expand All @@ -479,4 +481,4 @@ def get_event_name(entity_hex: str) -> str:

for loc in ALL_LOCATIONS_TO_IDS:
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"]
AREA_LOCATION_GROUPS.setdefault(area, []).append(loc)
AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc)
68 changes: 34 additions & 34 deletions worlds/witness/data/static_logic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections import defaultdict
from typing import Dict, List, Set, Tuple
from typing import Any, Dict, List, Optional, Set, Tuple

from Utils import cache_argsless

Expand All @@ -24,13 +24,37 @@


class StaticWitnessLogicObj:
def read_logic_file(self, lines) -> None:
def __init__(self, lines: Optional[List[str]] = None) -> None:
if lines is None:
lines = get_sigma_normal_logic()

# All regions with a list of panels in them and the connections to other regions, before logic adjustments
self.ALL_REGIONS_BY_NAME: Dict[str, Dict[str, Any]] = {}
self.ALL_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {}
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set))
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {}

self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {}
self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {}
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = {}

self.OBELISK_SIDE_ID_TO_EP_HEXES: Dict[int, Set[int]] = {}

self.EP_TO_OBELISK_SIDE: Dict[str, str] = {}

self.ENTITY_ID_TO_NAME: Dict[str, str] = {}

self.read_logic_file(lines)
self.reverse_connections()
self.combine_connections()

def read_logic_file(self, lines: List[str]) -> None:
"""
Reads the logic file and does the initial population of data structures
"""

current_region = dict()
current_area = {
current_region = {}
current_area: Dict[str, Any] = {
"name": "Misc",
"regions": [],
}
Expand Down Expand Up @@ -155,7 +179,7 @@ def read_logic_file(self, lines) -> None:
current_region["entities"].append(entity_hex)
current_region["physical_entities"].append(entity_hex)

def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]):
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
target = connection[0]
traversal_options = connection[1]

Expand All @@ -169,13 +193,13 @@ def reverse_connection(self, source_region: str, connection: Tuple[str, Set[Witn
if remaining_options:
self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options))

def reverse_connections(self):
def reverse_connections(self) -> None:
# Iterate all connections
for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()):
for connection in connections.items():
self.reverse_connection(region_name, connection)

def combine_connections(self):
def combine_connections(self) -> None:
# All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice.
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: set() for region_name in self.ALL_REGIONS_BY_NAME}

Expand All @@ -184,30 +208,6 @@ def combine_connections(self):
combined_req = logical_or_witness_rules(requirement)
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req))

def __init__(self, lines=None) -> None:
if lines is None:
lines = get_sigma_normal_logic()

# All regions with a list of panels in them and the connections to other regions, before logic adjustments
self.ALL_REGIONS_BY_NAME = dict()
self.ALL_AREAS_BY_NAME = dict()
self.CONNECTIONS_WITH_DUPLICATES = defaultdict(lambda: defaultdict(lambda: set()))
self.STATIC_CONNECTIONS_BY_REGION_NAME = dict()

self.ENTITIES_BY_HEX = dict()
self.ENTITIES_BY_NAME = dict()
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()

self.OBELISK_SIDE_ID_TO_EP_HEXES = dict()

self.EP_TO_OBELISK_SIDE = dict()

self.ENTITY_ID_TO_NAME = dict()

self.read_logic_file(lines)
self.reverse_connections()
self.combine_connections()


# Item data parsed from WitnessItems.txt
ALL_ITEMS: Dict[str, ItemDefinition] = {}
Expand Down Expand Up @@ -276,12 +276,12 @@ def get_sigma_expert() -> StaticWitnessLogicObj:
return StaticWitnessLogicObj(get_sigma_expert_logic())


def __getattr__(name):
def __getattr__(name: str) -> StaticWitnessLogicObj:
if name == "vanilla":
return get_vanilla()
elif name == "sigma_normal":
if name == "sigma_normal":
return get_sigma_normal()
elif name == "sigma_expert":
if name == "sigma_expert":
return get_sigma_expert()
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

Expand Down
30 changes: 16 additions & 14 deletions worlds/witness/data/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from math import floor
from pkgutil import get_data
from random import random
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple
from random import Random
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple, TypeVar

T = TypeVar("T")

# A WitnessRule is just an or-chain of and-conditions.
# It represents the set of all options that could fulfill this requirement.
Expand All @@ -11,9 +13,9 @@
WitnessRule = FrozenSet[FrozenSet[str]]


def weighted_sample(world_random: random, population: List, weights: List[float], k: int) -> List:
def weighted_sample(world_random: Random, population: List[T], weights: List[float], k: int) -> List[T]:
positions = range(len(population))
indices = []
indices: List[int] = []
while True:
needed = k - len(indices)
if not needed:
Expand Down Expand Up @@ -82,13 +84,13 @@ def define_new_region(region_string: str) -> Tuple[Dict[str, Any], Set[Tuple[str
region_obj = {
"name": region_name,
"shortName": region_name_simple,
"entities": list(),
"physical_entities": list(),
"entities": [],
"physical_entities": [],
}
return region_obj, options


def parse_lambda(lambda_string) -> WitnessRule:
def parse_lambda(lambda_string: str) -> WitnessRule:
"""
Turns a lambda String literal like this: a | b & c
into a set of sets like this: {{a}, {b, c}}
Expand All @@ -97,18 +99,18 @@ def parse_lambda(lambda_string) -> WitnessRule:
if lambda_string == "True":
return frozenset([frozenset()])
split_ands = set(lambda_string.split(" | "))
lambda_set = frozenset({frozenset(a.split(" & ")) for a in split_ands})

return lambda_set
return frozenset({frozenset(a.split(" & ")) for a in split_ands})


_adjustment_file_cache = dict()
_adjustment_file_cache = {}


def get_adjustment_file(adjustment_file: str) -> List[str]:
if adjustment_file not in _adjustment_file_cache:
data = get_data(__name__, adjustment_file).decode("utf-8")
_adjustment_file_cache[adjustment_file] = [line.strip() for line in data.split("\n")]
data = get_data(__name__, adjustment_file)
if data is None:
raise FileNotFoundError(f"Could not find {adjustment_file}")
_adjustment_file_cache[adjustment_file] = [line.strip() for line in data.decode("utf-8").split("\n")]

return _adjustment_file_cache[adjustment_file]

Expand Down Expand Up @@ -237,7 +239,7 @@ def logical_and_witness_rules(witness_rules: Iterable[WitnessRule]) -> WitnessRu
A logical formula might look like this: {{a, b}, {c, d}}, which would mean "a & b | c & d".
These can be easily and-ed by just using the boolean distributive law: (a | b) & c = a & c | a & b.
"""
current_overall_requirement = frozenset({frozenset()})
current_overall_requirement: FrozenSet[FrozenSet[str]] = frozenset({frozenset()})

for next_dnf_requirement in witness_rules:
new_requirement: Set[FrozenSet[str]] = set()
Expand Down
Loading

0 comments on commit 93617fa

Please sign in to comment.