SoE: minor typing and style fixes (#2724)

* SoE: fix typing for tests

* SoE: explicitly export pyevermizer

To support loading the module from source (rather than module) we import
pyevermizer from `__init__.py` in other files. This has been an implicit export
and `mypy --strict` disables implicit exports, so we export it explicitly now.

* SoE: fix style in patch.py

* SoE: remove unused imports

* SoE: fix format mistakes

* SoE: cleaner typing in SoEOptions.flags

as suggested by beauxq
This commit is contained in:
black-sliver 2024-01-15 09:17:46 +01:00 committed by GitHub
parent d10f8f66c7
commit 518b04c08e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 28 additions and 24 deletions

View File

@ -13,12 +13,15 @@ from Utils import output_path
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from worlds.generic.Rules import add_item_rule, set_rule from worlds.generic.Rules import add_item_rule, set_rule
from .logic import SoEPlayerLogic from .logic import SoEPlayerLogic
from .options import AvailableFragments, Difficulty, EnergyCore, RequiredFragments, SoEOptions, TrapChance from .options import Difficulty, EnergyCore, SoEOptions
from .patch import SoEDeltaPatch, get_base_rom_path from .patch import SoEDeltaPatch, get_base_rom_path
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from BaseClasses import MultiWorld, CollectionState from BaseClasses import MultiWorld, CollectionState
__all__ = ["pyevermizer", "SoEWorld"]
""" """
In evermizer: In evermizer:
@ -370,8 +373,6 @@ class SoEWorld(World):
self.evermizer_seed = self.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando? self.evermizer_seed = self.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
def generate_output(self, output_directory: str) -> None: def generate_output(self, output_directory: str) -> None:
from dataclasses import asdict
player_name = self.multiworld.get_player_name(self.player) player_name = self.multiworld.get_player_name(self.player)
self.connect_name = player_name[:32] self.connect_name = player_name[:32]
while len(self.connect_name.encode('utf-8')) > 32: while len(self.connect_name.encode('utf-8')) > 32:

View File

@ -1,8 +1,8 @@
from dataclasses import dataclass, fields from dataclasses import dataclass, fields
from typing import Any, cast, Dict, Iterator, List, Tuple, Protocol from typing import Any, cast, Dict, Iterator, List, Tuple, Protocol
from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, PerGameCommonOptions, ProgressionBalancing, \ from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
Range, Toggle ProgressionBalancing, Range, Toggle
# typing boilerplate # typing boilerplate
@ -294,5 +294,7 @@ class SoEOptions(PerGameCommonOptions):
for field in fields(self): for field in fields(self):
option = getattr(self, field.name) option = getattr(self, field.name)
if isinstance(option, (EvermizerFlag, EvermizerFlags)): if isinstance(option, (EvermizerFlag, EvermizerFlags)):
flags += getattr(self, field.name).to_flag() assert isinstance(option, Option)
# noinspection PyUnresolvedReferences
flags += option.to_flag()
return flags return flags

View File

@ -30,7 +30,7 @@ def get_base_rom_path(file_name: Optional[str] = None) -> str:
return file_name return file_name
def read_rom(stream: BinaryIO, strip_header: bool=True) -> bytes: def read_rom(stream: BinaryIO, strip_header: bool = True) -> bytes:
"""Reads rom into bytearray and optionally strips off any smc header""" """Reads rom into bytearray and optionally strips off any smc header"""
data = stream.read() data = stream.read()
if strip_header and len(data) % 0x400 == 0x200: if strip_header and len(data) % 0x400 == 0x200:

View File

@ -6,7 +6,7 @@ class SoETestBase(WorldTestBase):
game = "Secret of Evermore" game = "Secret of Evermore"
def assertLocationReachability(self, reachable: Iterable[str] = (), unreachable: Iterable[str] = (), def assertLocationReachability(self, reachable: Iterable[str] = (), unreachable: Iterable[str] = (),
satisfied=True) -> None: satisfied: bool = True) -> None:
""" """
Tests that unreachable can't be reached. Tests that reachable can be reached if satisfied=True. Tests that unreachable can't be reached. Tests that reachable can be reached if satisfied=True.
Usage: test with satisfied=False, collect requirements into state, test again with satisfied=True Usage: test with satisfied=False, collect requirements into state, test again with satisfied=True
@ -19,7 +19,7 @@ class SoETestBase(WorldTestBase):
self.assertFalse(self.can_reach_location(location), self.assertFalse(self.can_reach_location(location),
f"{location} is reachable but shouldn't be") f"{location} is reachable but shouldn't be")
def testRocketPartsExist(self): def testRocketPartsExist(self) -> None:
"""Tests that rocket parts exist and are unique""" """Tests that rocket parts exist and are unique"""
self.assertEqual(len(self.get_items_by_name("Gauge")), 1) self.assertEqual(len(self.get_items_by_name("Gauge")), 1)
self.assertEqual(len(self.get_items_by_name("Wheel")), 1) self.assertEqual(len(self.get_items_by_name("Wheel")), 1)

View File

@ -4,10 +4,10 @@ from . import SoETestBase
class AccessTest(SoETestBase): class AccessTest(SoETestBase):
@staticmethod @staticmethod
def _resolveGourds(gourds: typing.Dict[str, typing.Iterable[int]]): def _resolveGourds(gourds: typing.Mapping[str, typing.Iterable[int]]) -> typing.List[str]:
return [f"{name} #{number}" for name, numbers in gourds.items() for number in numbers] return [f"{name} #{number}" for name, numbers in gourds.items() for number in numbers]
def test_bronze_axe(self): def test_bronze_axe(self) -> None:
gourds = { gourds = {
"Pyramid bottom": (118, 121, 122, 123, 124, 125), "Pyramid bottom": (118, 121, 122, 123, 124, 125),
"Pyramid top": (140,) "Pyramid top": (140,)
@ -16,7 +16,7 @@ class AccessTest(SoETestBase):
items = [["Bronze Axe"]] items = [["Bronze Axe"]]
self.assertAccessDependency(locations, items) self.assertAccessDependency(locations, items)
def test_bronze_spear_plus(self): def test_bronze_spear_plus(self) -> None:
locations = ["Megataur"] locations = ["Megataur"]
items = [["Bronze Spear"], ["Lance (Weapon)"], ["Laser Lance"]] items = [["Bronze Spear"], ["Lance (Weapon)"], ["Laser Lance"]]
self.assertAccessDependency(locations, items) self.assertAccessDependency(locations, items)

View File

@ -8,7 +8,7 @@ class TestFragmentGoal(SoETestBase):
"required_fragments": 20, "required_fragments": 20,
} }
def test_fragments(self): def test_fragments(self) -> None:
self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"]) self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"])
self.assertBeatable(False) # 0 fragments self.assertBeatable(False) # 0 fragments
fragments = self.get_items_by_name("Energy Core Fragment") fragments = self.get_items_by_name("Energy Core Fragment")
@ -24,11 +24,11 @@ class TestFragmentGoal(SoETestBase):
self.assertEqual(self.count("Energy Core Fragment"), 21) self.assertEqual(self.count("Energy Core Fragment"), 21)
self.assertBeatable(True) self.assertBeatable(True)
def test_no_weapon(self): def test_no_weapon(self) -> None:
self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core Fragment"]) self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core Fragment"])
self.assertBeatable(False) self.assertBeatable(False)
def test_no_rocket(self): def test_no_rocket(self) -> None:
self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core Fragment"]) self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core Fragment"])
self.assertBeatable(False) self.assertBeatable(False)
@ -38,16 +38,16 @@ class TestShuffleGoal(SoETestBase):
"energy_core": "shuffle", "energy_core": "shuffle",
} }
def test_core(self): def test_core(self) -> None:
self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"]) self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"])
self.assertBeatable(False) self.assertBeatable(False)
self.collect_by_name(["Energy Core"]) self.collect_by_name(["Energy Core"])
self.assertBeatable(True) self.assertBeatable(True)
def test_no_weapon(self): def test_no_weapon(self) -> None:
self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core"]) self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core"])
self.assertBeatable(False) self.assertBeatable(False)
def test_no_rocket(self): def test_no_rocket(self) -> None:
self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core"]) self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core"])
self.assertBeatable(False) self.assertBeatable(False)

View File

@ -6,7 +6,7 @@ class OoBTest(SoETestBase):
"""Tests that 'on' doesn't put out-of-bounds in logic. This is also the test base for OoB in logic.""" """Tests that 'on' doesn't put out-of-bounds in logic. This is also the test base for OoB in logic."""
options: typing.Dict[str, typing.Any] = {"out_of_bounds": "on"} options: typing.Dict[str, typing.Any] = {"out_of_bounds": "on"}
def test_oob_access(self): def test_oob_access(self) -> None:
in_logic = self.options["out_of_bounds"] == "logic" in_logic = self.options["out_of_bounds"] == "logic"
# some locations that just need a weapon + OoB # some locations that just need a weapon + OoB
@ -37,7 +37,7 @@ class OoBTest(SoETestBase):
self.collect_by_name("Diamond Eye") self.collect_by_name("Diamond Eye")
self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic) self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic)
def test_oob_goal(self): def test_oob_goal(self) -> None:
# still need Energy Core with OoB if sequence breaks are not in logic # still need Energy Core with OoB if sequence breaks are not in logic
for item in ["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"]: for item in ["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"]:
self.collect_by_name(item) self.collect_by_name(item)

View File

@ -6,7 +6,7 @@ class SequenceBreaksTest(SoETestBase):
"""Tests that 'on' doesn't put sequence breaks in logic. This is also the test base for in-logic.""" """Tests that 'on' doesn't put sequence breaks in logic. This is also the test base for in-logic."""
options: typing.Dict[str, typing.Any] = {"sequence_breaks": "on"} options: typing.Dict[str, typing.Any] = {"sequence_breaks": "on"}
def test_sequence_breaks_access(self): def test_sequence_breaks_access(self) -> None:
in_logic = self.options["sequence_breaks"] == "logic" in_logic = self.options["sequence_breaks"] == "logic"
# some locations that just need any weapon + sequence break # some locations that just need any weapon + sequence break
@ -30,7 +30,7 @@ class SequenceBreaksTest(SoETestBase):
self.collect_by_name("Bronze Spear") # Escape now just needs either Megataur or Rimsala dead self.collect_by_name("Bronze Spear") # Escape now just needs either Megataur or Rimsala dead
self.assertEqual(self.can_reach_location("Escape"), in_logic) self.assertEqual(self.can_reach_location("Escape"), in_logic)
def test_sequence_breaks_goal(self): def test_sequence_breaks_goal(self) -> None:
in_logic = self.options["sequence_breaks"] == "logic" in_logic = self.options["sequence_breaks"] == "logic"
# don't need Energy Core with sequence breaks in logic # don't need Energy Core with sequence breaks in logic

View File

@ -32,7 +32,8 @@ class Bases:
def test_trap_count(self) -> None: def test_trap_count(self) -> None:
"""Test that total trap count is correct""" """Test that total trap count is correct"""
self.assertEqual(self.options["trap_count"], len(self.get_items_by_name(self.option_name_to_item_name.values()))) self.assertEqual(self.options["trap_count"],
len(self.get_items_by_name(self.option_name_to_item_name.values())))
class TestTrapAllZeroChance(Bases.TrapTestBase): class TestTrapAllZeroChance(Bases.TrapTestBase):