diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index e689f2f55c58..5a4e2bb48f18 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -3,11 +3,12 @@ import functools import settings import threading -import typing -from typing import Any, Dict, List, Set, Tuple, Optional, Union +from typing import Any, ClassVar import os import logging +from typing_extensions import override + from BaseClasses import ItemClassification, LocationProgressType, \ MultiWorld, Item, CollectionState, Entrance, Tutorial @@ -76,7 +77,7 @@ class ZillionWorld(World): options_dataclass = ZillionOptions options: ZillionOptions # type: ignore - settings: typing.ClassVar[ZillionSettings] # type: ignore + settings: ClassVar[ZillionSettings] # type: ignore # these type: ignore are because of this issue: https://github.com/python/typing/discussions/1486 topology_present = True # indicate if world type has any meaningful layout/pathing @@ -89,14 +90,14 @@ class ZillionWorld(World): class LogStreamInterface: logger: logging.Logger - buffer: List[str] + buffer: list[str] def __init__(self, logger: logging.Logger) -> None: self.logger = logger self.buffer = [] def write(self, msg: str) -> None: - if msg.endswith('\n'): + if msg.endswith("\n"): self.buffer.append(msg[:-1]) self.logger.debug("".join(self.buffer)) self.buffer = [] @@ -108,21 +109,21 @@ def flush(self) -> None: lsi: LogStreamInterface - id_to_zz_item: Optional[Dict[int, ZzItem]] = None + id_to_zz_item: dict[int, ZzItem] | None = None zz_system: System - _item_counts: "Counter[str]" = Counter() + _item_counts: Counter[str] = Counter() """ These are the items counts that will be in the game, which might be different from the item counts the player asked for in options (if the player asked for something invalid). """ - my_locations: List[ZillionLocation] = [] + my_locations: list[ZillionLocation] = [] """ This is kind of a cache to avoid iterating through all the multiworld locations in logic. """ slot_data_ready: threading.Event """ This event is set in `generate_output` when the data is ready for `fill_slot_data` """ - logic_cache: Union[ZillionLogicCache, None] = None + logic_cache: ZillionLogicCache | None = None - def __init__(self, world: MultiWorld, player: int): + def __init__(self, world: MultiWorld, player: int) -> None: super().__init__(world, player) self.logger = logging.getLogger("Zillion") self.lsi = ZillionWorld.LogStreamInterface(self.logger) @@ -133,6 +134,7 @@ def _make_item_maps(self, start_char: Chars) -> None: _id_to_name, _id_to_zz_id, id_to_zz_item = make_id_to_others(start_char) self.id_to_zz_item = id_to_zz_item + @override def generate_early(self) -> None: zz_op, item_counts = validate(self.options) @@ -150,12 +152,13 @@ def generate_early(self) -> None: # just in case the options changed anything (I don't think they do) assert self.zz_system.randomizer, "init failed" for zz_name in self.zz_system.randomizer.locations: - if zz_name != 'main': + if zz_name != "main": assert self.zz_system.randomizer.loc_name_2_pretty[zz_name] in self.location_name_to_id, \ f"{self.zz_system.randomizer.loc_name_2_pretty[zz_name]} not in location map" self._make_item_maps(zz_op.start_char) + @override def create_regions(self) -> None: assert self.zz_system.randomizer, "generate_early hasn't been called" assert self.id_to_zz_item, "generate_early hasn't been called" @@ -177,23 +180,23 @@ def create_regions(self) -> None: zz_loc.req.gun = 1 assert len(self.zz_system.randomizer.get_locations(Req(gun=1, jump=1))) != 0 - start = self.zz_system.randomizer.regions['start'] + start = self.zz_system.randomizer.regions["start"] - all: Dict[str, ZillionRegion] = {} + all_regions: dict[str, ZillionRegion] = {} for here_zz_name, zz_r in self.zz_system.randomizer.regions.items(): here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name) - all[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w) - self.multiworld.regions.append(all[here_name]) + all_regions[here_name] = ZillionRegion(zz_r, here_name, here_name, p, w) + self.multiworld.regions.append(all_regions[here_name]) limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126) queue = deque([start]) - done: Set[str] = set() + done: set[str] = set() while len(queue): zz_here = queue.popleft() here_name = "Menu" if zz_here.name == "start" else zz_reg_name_to_reg_name(zz_here.name) if here_name in done: continue - here = all[here_name] + here = all_regions[here_name] for zz_loc in zz_here.locations: # if local gun reqs didn't place "keyword" item @@ -217,15 +220,16 @@ def access_rule_wrapped(zz_loc_local: ZzLocation, self.my_locations.append(loc) for zz_dest in zz_here.connections.keys(): - dest_name = "Menu" if zz_dest.name == 'start' else zz_reg_name_to_reg_name(zz_dest.name) - dest = all[dest_name] - exit = Entrance(p, f"{here_name} to {dest_name}", here) - here.exits.append(exit) - exit.connect(dest) + dest_name = "Menu" if zz_dest.name == "start" else zz_reg_name_to_reg_name(zz_dest.name) + dest = all_regions[dest_name] + exit_ = Entrance(p, f"{here_name} to {dest_name}", here) + here.exits.append(exit_) + exit_.connect(dest) queue.append(zz_dest) done.add(here.name) + @override def create_items(self) -> None: if not self.id_to_zz_item: self._make_item_maps("JJ") @@ -249,14 +253,11 @@ def create_items(self) -> None: self.logger.debug(f"Zillion Items: {item_name} 1") self.multiworld.itempool.append(self.create_item(item_name)) - def set_rules(self) -> None: - # logic for this game is in create_regions - pass - + @override def generate_basic(self) -> None: assert self.zz_system.randomizer, "generate_early hasn't been called" # main location name is an alias - main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name] + main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations["main"].name] self.multiworld.get_location(main_loc_name, self.player)\ .place_locked_item(self.create_item("Win")) @@ -264,22 +265,18 @@ def generate_basic(self) -> None: lambda state: state.has("Win", self.player) @staticmethod - def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: + def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: # noqa: ANN401 # item link pools are about to be created in main # JJ can't be an item link unless all the players share the same start_char # (The reason for this is that the JJ ZillionItem will have a different ZzItem depending # on whether the start char is Apple or Champ, and the logic depends on that ZzItem.) for group in multiworld.groups.values(): - # TODO: remove asserts on group when we can specify which members of TypedDict are optional - assert "game" in group - if group["game"] == "Zillion": - assert "item_pool" in group + if group["game"] == "Zillion" and "item_pool" in group: item_pool = group["item_pool"] to_stay: Chars = "JJ" if "JJ" in item_pool: - assert "players" in group - group_players = group["players"] - players_start_chars: List[Tuple[int, Chars]] = [] + group["players"] = group_players = set(group["players"]) + players_start_chars: list[tuple[int, Chars]] = [] for player in group_players: z_world = multiworld.worlds[player] assert isinstance(z_world, ZillionWorld) @@ -291,17 +288,17 @@ def stage_generate_basic(multiworld: MultiWorld, *args: Any) -> None: elif start_char_counts["Champ"] > start_char_counts["Apple"]: to_stay = "Champ" else: # equal - choices: Tuple[Chars, ...] = ("Apple", "Champ") + choices: tuple[Chars, ...] = ("Apple", "Champ") to_stay = multiworld.random.choice(choices) for p, sc in players_start_chars: if sc != to_stay: group_players.remove(p) - assert "world" in group group_world = group["world"] assert isinstance(group_world, ZillionWorld) group_world._make_item_maps(to_stay) + @override def post_fill(self) -> None: """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. This happens before progression balancing, so the items may not be in their final locations yet.""" @@ -317,10 +314,10 @@ def finalize_item_locations(self) -> GenData: assert self.zz_system.randomizer, "generate_early hasn't been called" - # debug_zz_loc_ids: Dict[str, int] = {} + # debug_zz_loc_ids: dict[str, int] = {} empty = zz_items[4] multi_item = empty # a different patcher method differentiates empty from ap multi item - multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name) + multi_items: dict[str, tuple[str, str]] = {} # zz_loc_name to (item_name, player_name) for z_loc in self.multiworld.get_locations(self.player): assert isinstance(z_loc, ZillionLocation) # debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc) @@ -343,7 +340,7 @@ def finalize_item_locations(self) -> GenData: # print(id_) # print("size:", len(debug_zz_loc_ids)) - # debug_loc_to_id: Dict[str, int] = {} + # debug_loc_to_id: dict[str, int] = {} # regions = self.zz_randomizer.regions # for region in regions.values(): # for loc in region.locations: @@ -358,10 +355,11 @@ def finalize_item_locations(self) -> GenData: f"in world {self.player} didn't get an item" ) - game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode() + game_id = self.multiworld.player_name[self.player].encode() + b"\x00" + self.multiworld.seed_name[-6:].encode() return GenData(multi_items, self.zz_system.get_game(), game_id) + @override def generate_output(self, output_directory: str) -> None: """This method gets called from a threadpool, do not use multiworld.random here. If you need any last-second randomization, use self.random instead.""" @@ -383,6 +381,7 @@ def generate_output(self, output_directory: str) -> None: self.logger.debug(f"Zillion player {self.player} finished generate_output") + @override def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot """Fill in the `slot_data` field in the `Connected` network package. This is a way the generator can give custom data to the client. @@ -400,6 +399,7 @@ def fill_slot_data(self) -> ZillionSlotInfo: # json of WebHostLib.models.Slot # end of ordered Main.py calls + @override def create_item(self, name: str) -> Item: """Create an item for this world type and player. Warning: this may be called with self.multiworld = None, for example by MultiServer""" @@ -420,6 +420,7 @@ def create_item(self, name: str) -> Item: z_item = ZillionItem(name, classification, item_id, self.player, zz_item) return z_item + @override def get_filler_item_name(self) -> str: """Called when the item pool needs to be filled with additional items to match location count.""" return "Empty" diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index 09d0565e1c5e..d629df583a81 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -3,7 +3,7 @@ import io import pkgutil import platform -from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast +from typing import Any, ClassVar, Coroutine, Protocol, cast from CommonClient import CommonContext, server_loop, gui_enabled, \ ClientCommandProcessor, logger, get_base_parser @@ -11,6 +11,7 @@ from Utils import async_start import colorama +from typing_extensions import override from zilliandomizer.zri.memory import Memory, RescueInfo from zilliandomizer.zri import events @@ -35,11 +36,11 @@ def _cmd_map(self) -> None: class ToggleCallback(Protocol): - def __call__(self) -> None: ... + def __call__(self) -> object: ... class SetRoomCallback(Protocol): - def __call__(self, rooms: List[List[int]]) -> None: ... + def __call__(self, rooms: list[list[int]]) -> object: ... class ZillionContext(CommonContext): @@ -47,7 +48,7 @@ class ZillionContext(CommonContext): command_processor = ZillionCommandProcessor items_handling = 1 # receive items from other players - known_name: Optional[str] + known_name: str | None """ This is almost the same as `auth` except `auth` is reset to `None` when server disconnects, and this isn't. """ from_game: "asyncio.Queue[events.EventFromGame]" @@ -56,11 +57,11 @@ class ZillionContext(CommonContext): """ local checks watched by server """ next_item: int """ index in `items_received` """ - ap_id_to_name: Dict[int, str] - ap_id_to_zz_id: Dict[int, int] + ap_id_to_name: dict[int, str] + ap_id_to_zz_id: dict[int, int] start_char: Chars = "JJ" - rescues: Dict[int, RescueInfo] = {} - loc_mem_to_id: Dict[int, int] = {} + rescues: dict[int, RescueInfo] = {} + loc_mem_to_id: dict[int, int] = {} got_room_info: asyncio.Event """ flag for connected to server """ got_slot_data: asyncio.Event @@ -119,22 +120,22 @@ def reset_game_state(self) -> None: self.finished_game = False self.items_received.clear() - # override - def on_deathlink(self, data: Dict[str, Any]) -> None: + @override + def on_deathlink(self, data: dict[str, Any]) -> None: self.to_game.put_nowait(events.DeathEventToGame()) return super().on_deathlink(data) - # override + @override async def server_auth(self, password_requested: bool = False) -> None: if password_requested and not self.password: await super().server_auth(password_requested) if not self.auth: - logger.info('waiting for connection to game...') + logger.info("waiting for connection to game...") return logger.info("logging in to server...") await self.send_connect() - # override + @override def run_gui(self) -> None: from kvui import GameManager from kivy.core.text import Label as CoreLabel @@ -154,10 +155,10 @@ class MapPanel(Widget): MAP_WIDTH: ClassVar[int] = 281 map_background: CoreImage - _number_textures: List[Texture] = [] - rooms: List[List[int]] = [] + _number_textures: list[Texture] = [] + rooms: list[list[int]] = [] - def __init__(self, **kwargs: Any) -> None: + def __init__(self, **kwargs: Any) -> None: # noqa: ANN401 super().__init__(**kwargs) FILE_NAME = "empty-zillion-map-row-col-labels-281.png" @@ -183,7 +184,7 @@ def _make_numbers(self) -> None: label.refresh() self._number_textures.append(label.texture) - def update_map(self, *args: Any) -> None: + def update_map(self, *args: Any) -> None: # noqa: ANN401 self.canvas.clear() with self.canvas: @@ -203,6 +204,7 @@ def update_map(self, *args: Any) -> None: num_texture = self._number_textures[num] Rectangle(texture=num_texture, size=num_texture.size, pos=pos) + @override def build(self) -> Layout: container = super().build() self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=ZillionManager.MapPanel.MAP_WIDTH) @@ -216,17 +218,18 @@ def toggle_map_width(self) -> None: self.map_widget.width = 0 self.container.do_layout() - def set_rooms(self, rooms: List[List[int]]) -> None: + def set_rooms(self, rooms: list[list[int]]) -> None: self.map_widget.rooms = rooms self.map_widget.update_map() self.ui = ZillionManager(self) - self.ui_toggle_map = lambda: self.ui.toggle_map_width() - self.ui_set_rooms = lambda rooms: self.ui.set_rooms(rooms) + self.ui_toggle_map = lambda: isinstance(self.ui, ZillionManager) and self.ui.toggle_map_width() + self.ui_set_rooms = lambda rooms: isinstance(self.ui, ZillionManager) and self.ui.set_rooms(rooms) run_co: Coroutine[Any, Any, None] = self.ui.async_run() self.ui_task = asyncio.create_task(run_co, name="UI") - def on_package(self, cmd: str, args: Dict[str, Any]) -> None: + @override + def on_package(self, cmd: str, args: dict[str, Any]) -> None: self.room_item_numbers_to_ui() if cmd == "Connected": logger.info("logged in to Archipelago server") @@ -238,7 +241,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: if "start_char" not in slot_data: logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `start_char`") return - self.start_char = slot_data['start_char'] + self.start_char = slot_data["start_char"] if self.start_char not in {"Apple", "Champ", "JJ"}: logger.warning("invalid Zillion `Connected` packet, " f"`slot_data` `start_char` has invalid value: {self.start_char}") @@ -259,7 +262,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: self.rescues[0 if rescue_id == "0" else 1] = ri if "loc_mem_to_id" not in slot_data: - logger.warn("invalid Zillion `Connected` packet, `slot_data` missing `loc_mem_to_id`") + logger.warning("invalid Zillion `Connected` packet, `slot_data` missing `loc_mem_to_id`") return loc_mem_to_id = slot_data["loc_mem_to_id"] self.loc_mem_to_id = {} @@ -286,7 +289,7 @@ def on_package(self, cmd: str, args: Dict[str, Any]) -> None: if "keys" not in args: logger.warning(f"invalid Retrieved packet to ZillionClient: {args}") return - keys = cast(Dict[str, Optional[str]], args["keys"]) + keys = cast(dict[str, str | None], args["keys"]) doors_b64 = keys.get(f"zillion-{self.auth}-doors", None) if doors_b64: logger.info("received door data from server") @@ -321,9 +324,9 @@ def process_from_game_queue(self) -> None: if server_id in self.missing_locations: self.ap_local_count += 1 n_locations = len(self.missing_locations) + len(self.checked_locations) - 1 # -1 to ignore win - logger.info(f'New Check: {loc_name} ({self.ap_local_count}/{n_locations})') + logger.info(f"New Check: {loc_name} ({self.ap_local_count}/{n_locations})") async_start(self.send_msgs([ - {"cmd": 'LocationChecks', "locations": [server_id]} + {"cmd": "LocationChecks", "locations": [server_id]} ])) else: # This will happen a lot in Zillion, @@ -334,7 +337,7 @@ def process_from_game_queue(self) -> None: elif isinstance(event_from_game, events.WinEventFromGame): if not self.finished_game: async_start(self.send_msgs([ - {"cmd": 'LocationChecks', "locations": [loc_name_to_id["J-6 bottom far left"]]}, + {"cmd": "LocationChecks", "locations": [loc_name_to_id["J-6 bottom far left"]]}, {"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL} ])) self.finished_game = True @@ -362,24 +365,24 @@ def process_items_received(self) -> None: ap_id = self.items_received[index].item from_name = self.player_names[self.items_received[index].player] # TODO: colors in this text, like sni client? - logger.info(f'received {self.ap_id_to_name[ap_id]} from {from_name}') + logger.info(f"received {self.ap_id_to_name[ap_id]} from {from_name}") self.to_game.put_nowait( events.ItemEventToGame(zz_item_ids) ) self.next_item = len(self.items_received) -def name_seed_from_ram(data: bytes) -> Tuple[str, str]: +def name_seed_from_ram(data: bytes) -> tuple[str, str]: """ returns player name, and end of seed string """ if len(data) == 0: # no connection to game return "", "xxx" - null_index = data.find(b'\x00') + null_index = data.find(b"\x00") if null_index == -1: logger.warning(f"invalid game id in rom {repr(data)}") null_index = len(data) name = data[:null_index].decode() - null_index_2 = data.find(b'\x00', null_index + 1) + null_index_2 = data.find(b"\x00", null_index + 1) if null_index_2 == -1: null_index_2 = len(data) seed_name = data[null_index + 1:null_index_2].decode() @@ -479,8 +482,8 @@ def log_no_spam(msg: str) -> None: async def main() -> None: parser = get_base_parser() - parser.add_argument('diff_file', default="", type=str, nargs="?", - help='Path to a .apzl Archipelago Binary Patch file') + parser.add_argument("diff_file", default="", type=str, nargs="?", + help="Path to a .apzl Archipelago Binary Patch file") # SNI parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical']) args = parser.parse_args() print(args) diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py index 13cbee9ced20..214073396153 100644 --- a/worlds/zillion/gen_data.py +++ b/worlds/zillion/gen_data.py @@ -1,6 +1,5 @@ from dataclasses import dataclass import json -from typing import Dict, Tuple from zilliandomizer.game import Game as ZzGame @@ -9,7 +8,7 @@ class GenData: """ data passed from generation to patcher """ - multi_items: Dict[str, Tuple[str, str]] + multi_items: dict[str, tuple[str, str]] """ zz_loc_name to (item_name, player_name) """ zz_game: ZzGame game_id: bytes diff --git a/worlds/zillion/id_maps.py b/worlds/zillion/id_maps.py index 32d71fc79b30..25762f99cd6b 100644 --- a/worlds/zillion/id_maps.py +++ b/worlds/zillion/id_maps.py @@ -1,5 +1,6 @@ from collections import defaultdict -from typing import Dict, Iterable, Mapping, Tuple, TypedDict +from collections.abc import Iterable, Mapping +from typing import TypedDict from zilliandomizer.logic_components.items import ( Item as ZzItem, @@ -40,13 +41,13 @@ _zz_empty = zz_item_name_to_zz_item["empty"] -def make_id_to_others(start_char: Chars) -> Tuple[ - Dict[int, str], Dict[int, int], Dict[int, ZzItem] +def make_id_to_others(start_char: Chars) -> tuple[ + dict[int, str], dict[int, int], dict[int, ZzItem] ]: """ returns id_to_name, id_to_zz_id, id_to_zz_item """ - id_to_name: Dict[int, str] = {} - id_to_zz_id: Dict[int, int] = {} - id_to_zz_item: Dict[int, ZzItem] = {} + id_to_name: dict[int, str] = {} + id_to_zz_id: dict[int, int] = {} + id_to_zz_item: dict[int, ZzItem] = {} if start_char == "JJ": name_to_zz_item = { @@ -91,14 +92,14 @@ def make_room_name(row: int, col: int) -> str: return f"{chr(ord('A') + row - 1)}-{col + 1}" -loc_name_to_id: Dict[str, int] = { +loc_name_to_id: dict[str, int] = { name: id_ + base_id for name, id_ in pretty_loc_name_to_id.items() } def zz_reg_name_to_reg_name(zz_reg_name: str) -> str: - if zz_reg_name[0] == 'r' and zz_reg_name[3] == 'c': + if zz_reg_name[0] == "r" and zz_reg_name[3] == "c": row, col = parse_reg_name(zz_reg_name) end = zz_reg_name[5:] return f"{make_room_name(row, col)} {end.upper()}" @@ -113,17 +114,17 @@ class ClientRescue(TypedDict): class ZillionSlotInfo(TypedDict): start_char: Chars - rescues: Dict[str, ClientRescue] - loc_mem_to_id: Dict[int, int] + rescues: dict[str, ClientRescue] + loc_mem_to_id: dict[int, int] """ memory location of canister to Archipelago location id number """ def get_slot_info(regions: Iterable[RegionData], start_char: Chars, loc_name_to_pretty: Mapping[str, str]) -> ZillionSlotInfo: - items_placed_in_map_index: Dict[int, int] = defaultdict(int) - rescue_locations: Dict[int, RescueInfo] = {} - loc_memory_to_loc_id: Dict[int, int] = {} + items_placed_in_map_index: dict[int, int] = defaultdict(int) + rescue_locations: dict[int, RescueInfo] = {} + loc_memory_to_loc_id: dict[int, int] = {} for region in regions: for loc in region.locations: assert loc.item, ("There should be an item placed in every location before " @@ -142,7 +143,7 @@ def get_slot_info(regions: Iterable[RegionData], loc_memory_to_loc_id[loc_memory] = pretty_loc_name_to_id[loc_name_to_pretty[loc.name]] items_placed_in_map_index[map_index] += 1 - rescues: Dict[str, ClientRescue] = {} + rescues: dict[str, ClientRescue] = {} for i in (0, 1): if i in rescue_locations: ri = rescue_locations[i] diff --git a/worlds/zillion/logic.py b/worlds/zillion/logic.py index a14910a200e5..f3d1814a9e9b 100644 --- a/worlds/zillion/logic.py +++ b/worlds/zillion/logic.py @@ -1,4 +1,5 @@ -from typing import Dict, FrozenSet, Mapping, Tuple, List, Counter as _Counter +from collections import Counter +from collections.abc import Mapping from BaseClasses import CollectionState @@ -35,7 +36,7 @@ def set_randomizer_locs(cs: CollectionState, p: int, zz_r: Randomizer) -> int: return _hash -def item_counts(cs: CollectionState, p: int) -> Tuple[Tuple[str, int], ...]: +def item_counts(cs: CollectionState, p: int) -> tuple[tuple[str, int], ...]: """ the zilliandomizer items that player p has collected @@ -44,11 +45,11 @@ def item_counts(cs: CollectionState, p: int) -> Tuple[Tuple[str, int], ...]: return tuple((item_name, cs.count(item_name, p)) for item_name in item_name_to_id) -_cache_miss: Tuple[None, FrozenSet[Location]] = (None, frozenset()) +_cache_miss: tuple[None, frozenset[Location]] = (None, frozenset()) class ZillionLogicCache: - _cache: Dict[int, Tuple[_Counter[str], FrozenSet[Location]]] + _cache: dict[int, tuple[Counter[str], frozenset[Location]]] """ `{ hash: (counter_from_prog_items, accessible_zz_locations) }` """ _player: int _zz_r: Randomizer @@ -60,7 +61,7 @@ def __init__(self, player: int, zz_r: Randomizer, id_to_zz_item: Mapping[int, It self._zz_r = zz_r self._id_to_zz_item = id_to_zz_item - def cs_to_zz_locs(self, cs: CollectionState) -> FrozenSet[Location]: + def cs_to_zz_locs(self, cs: CollectionState) -> frozenset[Location]: """ given an Archipelago `CollectionState`, returns frozenset of accessible zilliandomizer locations @@ -76,7 +77,7 @@ def cs_to_zz_locs(self, cs: CollectionState) -> FrozenSet[Location]: return locs # print("cache miss") - have_items: List[Item] = [] + have_items: list[Item] = [] for name, count in counts: have_items.extend([self._id_to_zz_item[item_name_to_id[name]]] * count) # have_req is the result of converting AP CollectionState to zilliandomizer collection state diff --git a/worlds/zillion/options.py b/worlds/zillion/options.py index ec0fdb0b22e1..22a698472265 100644 --- a/worlds/zillion/options.py +++ b/worlds/zillion/options.py @@ -1,6 +1,6 @@ from collections import Counter from dataclasses import dataclass -from typing import ClassVar, Dict, Literal, Tuple, TypeGuard +from typing import ClassVar, Literal, TypeGuard from Options import Choice, DefaultOnToggle, NamedRange, OptionGroup, PerGameCommonOptions, Range, Removed, Toggle @@ -107,7 +107,7 @@ class ZillionStartChar(Choice): display_name = "start character" default = "random" - _name_capitalization: ClassVar[Dict[int, Chars]] = { + _name_capitalization: ClassVar[dict[int, Chars]] = { option_jj: "JJ", option_apple: "Apple", option_champ: "Champ", @@ -263,7 +263,7 @@ class ZillionMapGen(Choice): option_full = 2 default = 0 - def zz_value(self) -> Literal['none', 'rooms', 'full']: + def zz_value(self) -> Literal["none", "rooms", "full"]: if self.value == ZillionMapGen.option_none: return "none" if self.value == ZillionMapGen.option_rooms: @@ -305,7 +305,7 @@ class ZillionOptions(PerGameCommonOptions): ] -def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts: +def convert_item_counts(ic: Counter[str]) -> ZzItemCounts: tr: ZzItemCounts = { ID.card: ic["ID Card"], ID.red: ic["Red ID Card"], @@ -319,7 +319,7 @@ def convert_item_counts(ic: "Counter[str]") -> ZzItemCounts: return tr -def validate(options: ZillionOptions) -> "Tuple[ZzOptions, Counter[str]]": +def validate(options: ZillionOptions) -> tuple[ZzOptions, Counter[str]]: """ adjusts options to make game completion possible diff --git a/worlds/zillion/patch.py b/worlds/zillion/patch.py index 6bc6d04dd663..0eee3315f4a1 100644 --- a/worlds/zillion/patch.py +++ b/worlds/zillion/patch.py @@ -1,5 +1,5 @@ import os -from typing import Any, BinaryIO, Optional, cast +from typing import BinaryIO import zipfile from typing_extensions import override @@ -11,11 +11,11 @@ from .gen_data import GenData -USHASH = 'd4bf9e7bcf9a48da53785d2ae7bc4270' +US_HASH = "d4bf9e7bcf9a48da53785d2ae7bc4270" class ZillionPatch(APAutoPatchInterface): - hash = USHASH + hash = US_HASH game = "Zillion" patch_file_ending = ".apzl" result_file_ending = ".sms" @@ -23,8 +23,14 @@ class ZillionPatch(APAutoPatchInterface): gen_data_str: str """ JSON encoded """ - def __init__(self, *args: Any, gen_data_str: str = "", **kwargs: Any) -> None: - super().__init__(*args, **kwargs) + def __init__(self, + path: str | None = None, + player: int | None = None, + player_name: str = "", + server: str = "", + *, + gen_data_str: str = "") -> None: + super().__init__(path=path, player=player, player_name=player_name, server=server) self.gen_data_str = gen_data_str @classmethod @@ -44,15 +50,17 @@ def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: super().read_contents(opened_zipfile) self.gen_data_str = opened_zipfile.read("gen_data.json").decode() + @override def patch(self, target: str) -> None: self.read() write_rom_from_gen_data(self.gen_data_str, target) -def get_base_rom_path(file_name: Optional[str] = None) -> str: - options = Utils.get_options() +def get_base_rom_path(file_name: str | None = None) -> str: + from . import ZillionSettings, ZillionWorld + settings: ZillionSettings = ZillionWorld.settings if not file_name: - file_name = cast(str, options["zillion_options"]["rom_file"]) + file_name = settings.rom_file if not os.path.exists(file_name): file_name = Utils.user_path(file_name) return file_name diff --git a/worlds/zillion/region.py b/worlds/zillion/region.py index cf5aa6588950..40565f008263 100644 --- a/worlds/zillion/region.py +++ b/worlds/zillion/region.py @@ -1,9 +1,11 @@ -from typing import Optional -from BaseClasses import MultiWorld, Region, Location, Item, CollectionState +from typing_extensions import override + from zilliandomizer.logic_components.regions import Region as ZzRegion from zilliandomizer.logic_components.locations import Location as ZzLocation from zilliandomizer.logic_components.items import RESCUE +from BaseClasses import MultiWorld, Region, Location, Item, CollectionState + from .id_maps import loc_name_to_id from .item import ZillionItem @@ -28,12 +30,12 @@ def __init__(self, zz_loc: ZzLocation, player: int, name: str, - parent: Optional[Region] = None) -> None: + parent: Region | None = None) -> None: loc_id = loc_name_to_id[name] super().__init__(player, name, loc_id, parent) self.zz_loc = zz_loc - # override + @override def can_fill(self, state: CollectionState, item: Item, check_access: bool = True) -> bool: saved_gun_req = -1 if isinstance(item, ZillionItem) \ diff --git a/worlds/zillion/test/TestReproducibleRandom.py b/worlds/zillion/test/TestReproducibleRandom.py index a92fae240709..352165449a8b 100644 --- a/worlds/zillion/test/TestReproducibleRandom.py +++ b/worlds/zillion/test/TestReproducibleRandom.py @@ -1,4 +1,3 @@ -from typing import cast from . import ZillionTestBase from .. import ZillionWorld @@ -9,7 +8,8 @@ class SeedTest(ZillionTestBase): def test_reproduce_seed(self) -> None: self.world_setup(42) - z_world = cast(ZillionWorld, self.multiworld.worlds[1]) + z_world = self.multiworld.worlds[1] + assert isinstance(z_world, ZillionWorld) r = z_world.zz_system.randomizer assert r randomized_requirements_first = tuple( @@ -18,7 +18,8 @@ def test_reproduce_seed(self) -> None: ) self.world_setup(42) - z_world = cast(ZillionWorld, self.multiworld.worlds[1]) + z_world = self.multiworld.worlds[1] + assert isinstance(z_world, ZillionWorld) r = z_world.zz_system.randomizer assert r randomized_requirements_second = tuple( diff --git a/worlds/zillion/test/__init__.py b/worlds/zillion/test/__init__.py index fe62bae34c9e..a669442364fe 100644 --- a/worlds/zillion/test/__init__.py +++ b/worlds/zillion/test/__init__.py @@ -1,4 +1,3 @@ -from typing import cast from test.bases import WorldTestBase from .. import ZillionWorld @@ -13,8 +12,9 @@ def ensure_gun_3_requirement(self) -> None: This makes sure that gun 3 is required by making all the canisters in O-7 (including key word canisters) require gun 3. """ - zz_world = cast(ZillionWorld, self.multiworld.worlds[1]) - assert zz_world.zz_system.randomizer - for zz_loc_name, zz_loc in zz_world.zz_system.randomizer.locations.items(): + z_world = self.multiworld.worlds[1] + assert isinstance(z_world, ZillionWorld) + assert z_world.zz_system.randomizer + for zz_loc_name, zz_loc in z_world.zz_system.randomizer.locations.items(): if zz_loc_name.startswith("r15c6"): zz_loc.req.gun = 3