diff --git a/.github/workflows/python-coverage.yml b/.github/workflows/python-coverage.yml index 3b2034e0..b27b6d45 100644 --- a/.github/workflows/python-coverage.yml +++ b/.github/workflows/python-coverage.yml @@ -19,6 +19,7 @@ jobs: git submodule update --init --recursive python -m pip install --upgrade pip pip install -e .[all,dev] + pip install --force https://github.com/plaans/aries/releases/download/latest/up_aries.tar.gz - name: Setup java for ENHSP uses: actions/setup-java@v2 with: diff --git a/.github/workflows/python-tests-all-optdeps.yml b/.github/workflows/python-tests-all-optdeps.yml index 5284bb44..d5a72af4 100644 --- a/.github/workflows/python-tests-all-optdeps.yml +++ b/.github/workflows/python-tests-all-optdeps.yml @@ -26,6 +26,7 @@ jobs: git submodule update --init --recursive python -m pip install --upgrade pip pip install .[all,dev] + pip install --force https://github.com/plaans/aries/releases/download/latest/up_aries.tar.gz - name: Setup java for ENHSP uses: actions/setup-java@v2 with: diff --git a/src/hcraft/__init__.py b/src/hcraft/__init__.py index 432cc219..8c0d916d 100644 --- a/src/hcraft/__init__.py +++ b/src/hcraft/__init__.py @@ -37,6 +37,8 @@ """ +from __future__ import annotations + import hcraft.state as state import hcraft.solving_behaviors as solving_behaviors import hcraft.purpose as purpose diff --git a/src/hcraft/behaviors/behaviors.py b/src/hcraft/behaviors/behaviors.py index 660a70b7..c6901f1b 100644 --- a/src/hcraft/behaviors/behaviors.py +++ b/src/hcraft/behaviors/behaviors.py @@ -1,6 +1,6 @@ """ Module for handcrafted Behavior with HEBGraph in any HierarchyCraft environment. """ -from typing import TYPE_CHECKING, Dict, List, Optional, Union +from typing import TYPE_CHECKING import numpy as np from hebg import Behavior, HEBGraph @@ -29,7 +29,7 @@ def __init__( self, item: Item, env: "HcraftEnv", - all_behaviors: Dict[str, Behavior], + all_behaviors: dict[str, Behavior], ): super().__init__(name=self.get_name(item)) self.env = env @@ -66,7 +66,7 @@ def __init__( self, item: Item, env: "HcraftEnv", - all_behaviors: Dict[str, Behavior], + all_behaviors: dict[str, Behavior], ): super().__init__(name=self.get_name(item)) self.env = env @@ -107,8 +107,8 @@ def __init__( self, item: Item, env: "HcraftEnv", - all_behaviors: Dict[str, Behavior], - zone: Optional[Zone] = None, + all_behaviors: dict[str, Behavior], + zone: Zone | None = None, ): self.item = item self.zone = zone @@ -117,7 +117,7 @@ def __init__( self.all_behaviors = all_behaviors @staticmethod - def get_name(item: Item, zone: Optional[Zone] = None) -> str: + def get_name(item: Item, zone: Zone | None = None) -> str: """Get the name of the behavior to reach a zone.""" return f"Place {item.name}{_zones_str(zone)}" @@ -163,11 +163,11 @@ def _zone_item_is_required(self, transformation: "Transformation") -> bool: def _zone_item_is_in( self, - zone_items: Optional[List[Stack]], - destination_items: Optional[List[Stack]], - zones_items: Optional[Dict[Zone, List[Stack]]], - zone: Optional[Zone], - destination: Optional[Zone], + zone_items: list[Stack] | None, + destination_items: list[Stack] | None, + zones_items: dict[Zone, list[Stack]] | None, + zone: Zone | None, + destination: Zone | None, ) -> bool: zone_is_valid = self.zone is None or zone is None or zone == self.zone if zone_is_valid and self._item_is_in_stack(zone_items): @@ -183,7 +183,7 @@ def _zone_item_is_in( return False def _zone_item_in_dict_of_stacks( - self, dict_of_stack: Optional[Dict["Zone", List["Stack"]]] + self, dict_of_stack: dict["Zone", list["Stack"]] | None ) -> bool: if dict_of_stack is None: return False @@ -194,7 +194,7 @@ def _zone_item_in_dict_of_stacks( stacks.extend(zone_stacks) return self._item_is_in_stack(stacks) - def _item_is_in_stack(self, stacks: Optional[List["Stack"]]) -> bool: + def _item_is_in_stack(self, stacks: list["Stack"] | None) -> bool: return stacks is not None and self.item in [stack.item for stack in stacks] @@ -206,7 +206,7 @@ def __init__( self, zone: Zone, env: "HcraftEnv", - all_behaviors: Dict[str, Behavior], + all_behaviors: dict[str, Behavior], ): super().__init__(name=self.get_name(zone)) self.env = env @@ -239,7 +239,7 @@ def __init__( self, env: "HcraftEnv", transformation: "Transformation", - all_behaviors: Dict[str, Behavior], + all_behaviors: dict[str, Behavior], ): super().__init__(name=self.get_name(transformation)) self.env = env @@ -339,7 +339,7 @@ def _add_place_item( graph: HEBGraph, env: "HcraftEnv", stack: Stack, - zone: Optional[Zone] = None, + zone: Zone | None = None, ) -> HasZoneItem: has_item_in_zone = HasZoneItem(env, stack, zone) image = np.array(load_or_create_image(stack, env.world.resources_path)) diff --git a/src/hcraft/behaviors/feature_conditions.py b/src/hcraft/behaviors/feature_conditions.py index c2fa9786..7ff9ecb9 100644 --- a/src/hcraft/behaviors/feature_conditions.py +++ b/src/hcraft/behaviors/feature_conditions.py @@ -1,6 +1,6 @@ """ Module to define FeatureCondition nodes for the HEBGraph of the HierarchyCraft environment. """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import numpy as np from hebg import FeatureCondition @@ -86,7 +86,7 @@ class HasZoneItem(FeatureCondition): """FeatureCondition to check if a Zone has the given property.""" def __init__( - self, env: "HcraftEnv", stack: Stack, zone: Optional[Zone] = None + self, env: "HcraftEnv", stack: Stack, zone: Zone | None = None ) -> None: image = np.array(load_or_create_image(stack, env.world.resources_path)) super().__init__(name=self.get_name(stack, zone), image=image, complexity=1) @@ -101,7 +101,7 @@ def __init__( self.state = env.state @staticmethod - def get_name(stack: Stack, zone: Optional[Zone] = None) -> str: + def get_name(stack: Stack, zone: Zone | None = None) -> str: """Name of the HasZoneItem feature condition given stack and optional zone.""" zone_str = "Current zone" if zone is None else zone.name.capitalize() quantity_str = _quantity_str(stack.quantity) diff --git a/src/hcraft/behaviors/utils.py b/src/hcraft/behaviors/utils.py index 7f078316..e8edf30b 100644 --- a/src/hcraft/behaviors/utils.py +++ b/src/hcraft/behaviors/utils.py @@ -1,6 +1,5 @@ """ Module for utility functions to apply on handcrafted Behavior. """ -from typing import Dict, Set, Union from hebg import Behavior, HEBGraph @@ -15,15 +14,15 @@ def get_items_in_graph( graph: HEBGraph, - all_behaviors: Dict[str, Union[GetItem, ReachZone]] = None, -) -> Set[Item]: + all_behaviors: dict[str, GetItem | ReachZone] = None, +) -> set[Item]: """Get items in a HierarchyCraft HEBGraph. Args: graph (HEBGraph): An of the HierarchyCraft environment. Returns: - Set[Item]: Set of items that appears in the given graph. + set[Item]: Set of items that appears in the given graph. """ all_behaviors = all_behaviors if all_behaviors is not None else {} items_in_graph = set() @@ -42,16 +41,16 @@ def get_items_in_graph( def get_zones_items_in_graph( graph: HEBGraph, - all_behaviors: Dict[str, Union[GetItem, ReachZone]] = None, -) -> Set[Item]: + all_behaviors: dict[str, GetItem | ReachZone] = None, +) -> set[Item]: """Get properties in a HierarchyCraft HEBGraph. Args: - graph (HEBGraph): An HEBehavior graph of the HierarchyCraft environment. - all_behaviors (Dict[str, Union[GetItem, ReachZone]): References to all known behaviors. + graph: An HEBehavior graph of the HierarchyCraft environment. + all_behaviors: References to all known behaviors. Returns: - Set[Item]: Set of zone items that appears in the given graph. + set[Item]: Set of zone items that appears in the given graph. """ all_behaviors = all_behaviors if all_behaviors is not None else {} zone_items_in_graph = set() diff --git a/src/hcraft/cli.py b/src/hcraft/cli.py index 4a514254..4368ad54 100644 --- a/src/hcraft/cli.py +++ b/src/hcraft/cli.py @@ -1,5 +1,4 @@ from argparse import ArgumentParser, Namespace, _SubParsersAction -from typing import List, Optional from hcraft.elements import Item from hcraft.env import HcraftEnv @@ -19,7 +18,7 @@ from hcraft.task import GetItemTask -def hcraft_cli(args: Optional[List[str]] = None) -> HcraftEnv: +def hcraft_cli(args: list[str] | None = None) -> HcraftEnv: """Parse arguments to build a hcraft environment. Args: @@ -289,7 +288,7 @@ def _random_sub_parser(subparsers: "_SubParsersAction[ArgumentParser]") -> None: def _randomhcraft_from_cli(args: Namespace) -> RandomHcraftEnv: window = _window_from_cli(args) - n_items_per_n_inputs: Optional[dict] = { + n_items_per_n_inputs: dict | None = { n_inputs: getattr(args, f"n_items_{n_inputs}") for n_inputs in range(5) } if n_items_per_n_inputs is not None and sum(n_items_per_n_inputs.values()) == 1: diff --git a/src/hcraft/env.py b/src/hcraft/env.py index c2485792..1288bdd0 100644 --- a/src/hcraft/env.py +++ b/src/hcraft/env.py @@ -262,7 +262,7 @@ """ import collections -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Union import numpy as np @@ -303,11 +303,11 @@ class HcraftEnv(Env): def __init__( self, world: "World", - purpose: Optional[Union[Purpose, List["Task"], "Task"]] = None, + purpose: Union[Purpose, list["Task"], "Task"] | None = None, invalid_reward: float = -1.0, - render_window: Optional[HcraftWindow] = None, + render_window: HcraftWindow | None = None, name: str = "HierarchyCraft", - max_step: Optional[int] = None, + max_step: int | None = None, ) -> None: """ Args: @@ -318,14 +318,14 @@ def __init__( Defaults to -1.0. render_window: Window using to render the environment with pygame. name: Name of the environement. Defaults to 'HierarchyCraft'. - max_step: (Optional[int], optional): Maximum number of steps before episode truncation. + max_step: (int | None, optional): Maximum number of steps before episode truncation. If None, never truncates the episode. Defaults to None. """ self.world = world self.invalid_reward = invalid_reward self.max_step = max_step self.name = name - self._all_behaviors: Optional[Dict[str, Behavior]] = None + self._all_behaviors: dict[str, Behavior] | None = None self.render_window = render_window self.render_mode = "rgb_array" @@ -335,8 +335,8 @@ def __init__( self.current_score = 0.0 self.cumulated_score = 0.0 self.episodes = 0 - self.task_successes: Optional[SuccessCounter] = None - self.terminal_successes: Optional[SuccessCounter] = None + self.task_successes: SuccessCounter | None = None + self.terminal_successes: SuccessCounter | None = None if purpose is None: purpose = Purpose(None) @@ -358,7 +358,7 @@ def terminated(self) -> bool: return self.purpose.is_terminal(self.state) @property - def observation_space(self) -> Union[BoxSpace, TupleSpace]: # type: ignore [override] + def observation_space(self) -> BoxSpace | TupleSpace: # type: ignore [override] """Observation space for the Agent.""" obs_space = BoxSpace( low=np.array( @@ -387,7 +387,7 @@ def action_masks(self) -> np.ndarray: """Return boolean mask of valid actions.""" return np.array([t.is_valid(self.state) for t in self.world.transformations]) - def step(self, action: int) -> Tuple[Any, float, bool, dict]: # type: ignore [override] + def step(self, action: int) -> tuple[Any, float, bool, dict]: # type: ignore [override] """Perform one step in the environment given the index of a wanted transformation. If the selected transformation can be performed, the state is updated and @@ -428,8 +428,8 @@ def step(self, action: int) -> Tuple[Any, float, bool, dict]: # type: ignore [o return self._step_output(reward, terminated, truncated) def render( # type: ignore [override] - self, mode: Optional[str] = None, **_kwargs: dict - ) -> Union[str, np.ndarray]: + self, mode: str | None = None, **_kwargs: dict + ) -> str | np.ndarray: """Render the observation of the agent in a format depending on `render_mode`.""" if mode is not None: self.render_mode = mode @@ -443,8 +443,8 @@ def render( # type: ignore [override] def reset( # type: ignore [override] self, *, - seed: Optional[int] = None, - options: Optional[dict] = None, + seed: int | None = None, + options: dict | None = None, ) -> np.ndarray: """Resets the state of the environement. @@ -478,7 +478,7 @@ def close(self): self.render_window.close() @property - def all_behaviors(self) -> Dict[str, "Behavior"]: + def all_behaviors(self) -> dict[str, "Behavior"]: """All solving behaviors using hebg.""" if self._all_behaviors is None: self._all_behaviors = build_all_solving_behaviors(self) @@ -550,7 +550,7 @@ def planning_problem( def _step_output( self, reward: float, terminated: bool, truncated: bool - ) -> Tuple[Any, float, bool, dict]: + ) -> tuple[Any, float, bool, dict]: infos = { "action_is_legal": self.action_masks(), "score": self.current_score, diff --git a/src/hcraft/examples/light_recursive.py b/src/hcraft/examples/light_recursive.py index 09d400d8..f5961bfc 100644 --- a/src/hcraft/examples/light_recursive.py +++ b/src/hcraft/examples/light_recursive.py @@ -24,7 +24,7 @@ from hcraft.world import world_from_transformations -from typing import Any, List, Union +from typing import Any # gym is an optional dependency @@ -58,7 +58,7 @@ def __init__(self, n_items: int = 6, n_required_previous: int = 2, **kwargs: Any kwargs["purpose"] = GetItemTask(items[-1]) super().__init__(world, name=env_name, **kwargs) - def _transformations(self, items: List[Item]) -> List[Transformation]: + def _transformations(self, items: list[Item]) -> list[Transformation]: """Build recipes to make every item accessible. Args: @@ -72,7 +72,7 @@ def _transformations(self, items: List[Item]) -> List[Transformation]: for index, item in enumerate(items): low_id = max(0, index - self.n_required_previous) - inventory_changes: List[Union[Use, Yield]] = [Yield(PLAYER, item)] + inventory_changes: list[Use | Yield] = [Yield(PLAYER, item)] if index > 0: inventory_changes += [ Use(PLAYER, items[item_id], consume=1) diff --git a/src/hcraft/examples/minecraft/__init__.py b/src/hcraft/examples/minecraft/__init__.py index d6c0fe05..de9cb41e 100644 --- a/src/hcraft/examples/minecraft/__init__.py +++ b/src/hcraft/examples/minecraft/__init__.py @@ -7,7 +7,6 @@ .. include:: ../../../../docs/images/requirements_graphs/MineHcraft.html """ -from typing import Optional from hcraft.elements import Item import hcraft.examples.minecraft.items as items @@ -47,7 +46,7 @@ def _to_camel_case(name: str) -> str: def _register_minehcraft_single_item( item: Item, - name: Optional[str] = None, + name: str | None = None, success_reward: float = 10.0, timestep_reward: float = -0.1, reward_shaping: RewardShaping = RewardShaping.REQUIREMENTS_ACHIVEMENTS, diff --git a/src/hcraft/examples/minecraft/items.py b/src/hcraft/examples/minecraft/items.py index 0a2a4292..55a4f995 100644 --- a/src/hcraft/examples/minecraft/items.py +++ b/src/hcraft/examples/minecraft/items.py @@ -6,7 +6,6 @@ """ from dataclasses import dataclass -from typing import List, Optional, Union from hcraft.elements import Item, Zone from hcraft.examples.minecraft.tools import Material, ToolType @@ -88,9 +87,9 @@ class McItem: """Minecraft item with its specific properties.""" item: Item - zones: List[Zone] - required_tool_types: Optional[List[Union[None, ToolType]]] = None - required_tool_material: Optional[List[Material]] = None + zones: list[Zone] + required_tool_types: list[None | ToolType] | None = None + required_tool_material: list[Material] | None = None hardness: float = 1.0 diff --git a/src/hcraft/examples/minecraft/tools.py b/src/hcraft/examples/minecraft/tools.py index 8daa77ba..ecb11c70 100644 --- a/src/hcraft/examples/minecraft/tools.py +++ b/src/hcraft/examples/minecraft/tools.py @@ -5,7 +5,6 @@ """ from enum import Enum -from typing import Dict, List from hcraft.elements import Item @@ -29,8 +28,8 @@ class ToolType(Enum): SWORD = "sword" -MC_TOOLS: List[Item] = [] -MC_TOOLS_BY_TYPE_AND_MATERIAL: Dict[ToolType, Dict[Material, Item]] = {} +MC_TOOLS: list[Item] = [] +MC_TOOLS_BY_TYPE_AND_MATERIAL: dict[ToolType, dict[Material, Item]] = {} def build_tools(): diff --git a/src/hcraft/examples/minecraft/transformations.py b/src/hcraft/examples/minecraft/transformations.py index f176b61a..8ad30367 100644 --- a/src/hcraft/examples/minecraft/transformations.py +++ b/src/hcraft/examples/minecraft/transformations.py @@ -4,7 +4,6 @@ """ -from typing import List, Optional from hcraft.elements import Item, Zone import hcraft.examples.minecraft.items as items @@ -24,7 +23,7 @@ ) -def build_minehcraft_transformations() -> List[Transformation]: +def build_minehcraft_transformations() -> list[Transformation]: transformations = [] transformations += _building() transformations += _recipes() @@ -34,11 +33,11 @@ def build_minehcraft_transformations() -> List[Transformation]: return transformations -def _move_to_zones() -> List[Transformation]: +def _move_to_zones() -> list[Transformation]: def move_name( destination: Zone, - from_zone: Optional[Zone] = None, - with_item: Optional[Item] = None, + from_zone: Zone | None = None, + with_item: Item | None = None, ) -> str: base_name = f"move-to-{destination.name}" if from_zone is not None: @@ -143,7 +142,7 @@ def move_name( return walk + dig + nether + stronghold + end -def _zones_search() -> List[Transformation]: +def _zones_search() -> list[Transformation]: """Build the transformations to search for items in zones using tools or not.""" material_speed = { None: 1, @@ -192,7 +191,7 @@ def _zones_search() -> List[Transformation]: return search_item -def _search_name(item: Item, at_zone: Zone, with_item: Optional[Item] = None) -> str: +def _search_name(item: Item, at_zone: Zone, with_item: Item | None = None) -> str: basename = f"search-for-{item.name}" if with_item is not None: basename += f"-with-{with_item.name}" @@ -203,9 +202,9 @@ def _search_name(item: Item, at_zone: Zone, with_item: Optional[Item] = None) -> def _search_for_item_transformations( mc_item: items.McItem, quantity: int, - with_item: Optional[Item] = None, - additional_inventory_changes: Optional[list] = None, -) -> List[Transformation]: + with_item: Item | None = None, + additional_inventory_changes: list | None = None, +) -> list[Transformation]: inventory_changes = [Yield(PLAYER, mc_item.item, quantity)] if additional_inventory_changes is not None: inventory_changes += additional_inventory_changes @@ -219,7 +218,7 @@ def _search_for_item_transformations( ] -def _recipes() -> List[Transformation]: +def _recipes() -> list[Transformation]: """Build the item based on recipes.""" name_prefix = "craft-" return [ @@ -333,7 +332,7 @@ def _recipes() -> List[Transformation]: ] -def _building() -> List[Transformation]: +def _building() -> list[Transformation]: """Build building based transformations""" placable_items = (items.CRAFTING_TABLE, items.FURNACE, items.ENCHANTING_TABLE) @@ -424,7 +423,7 @@ def _building() -> List[Transformation]: return place_items + pickup_items + building_creation + smelting -def _tools_recipes() -> List[Transformation]: +def _tools_recipes() -> list[Transformation]: """Builds the list of transformations for the tools recipes""" tools_recipes = [] diff --git a/src/hcraft/examples/minicraft/__init__.py b/src/hcraft/examples/minicraft/__init__.py index 0cc69388..f81406e4 100644 --- a/src/hcraft/examples/minicraft/__init__.py +++ b/src/hcraft/examples/minicraft/__init__.py @@ -22,7 +22,7 @@ import inspect from pathlib import Path -from typing import List, Type +from typing import Type from hcraft.examples.minicraft.minicraft import MiniCraftEnv @@ -48,7 +48,7 @@ from hcraft.examples.minicraft.keycorridor import MiniHCraftKeyCorridor -MINICRAFT_ENVS: List[Type[MiniCraftEnv]] = [ +MINICRAFT_ENVS: list[Type[MiniCraftEnv]] = [ MiniHCraftEmpty, MiniHCraftFourRooms, MiniHCraftMultiRoom, diff --git a/src/hcraft/examples/minicraft/crossing.py b/src/hcraft/examples/minicraft/crossing.py index 7a7529be..614e9121 100644 --- a/src/hcraft/examples/minicraft/crossing.py +++ b/src/hcraft/examples/minicraft/crossing.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.purpose import Purpose @@ -32,7 +32,7 @@ def __init__(self, **kwargs: Any) -> None: purpose.add_task(die_in_lava, terminal_groups="die") super().__init__(start_zone=self.ROOM, purpose=purpose, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: find_goal = Transformation( "find_goal", inventory_changes=[Yield(CURRENT_ZONE, self.GOAL, max=0)], diff --git a/src/hcraft/examples/minicraft/doorkey.py b/src/hcraft/examples/minicraft/doorkey.py index 851527b5..a33469a7 100644 --- a/src/hcraft/examples/minicraft/doorkey.py +++ b/src/hcraft/examples/minicraft/doorkey.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -34,7 +34,7 @@ def __init__(self, **kwargs: Any) -> None: self.task = GetItemTask(self.GOAL) super().__init__(purpose=self.task, start_zone=self.START, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] # Ensure key cannot be created if anywhere else diff --git a/src/hcraft/examples/minicraft/empty.py b/src/hcraft/examples/minicraft/empty.py index 13ceed99..155e296c 100644 --- a/src/hcraft/examples/minicraft/empty.py +++ b/src/hcraft/examples/minicraft/empty.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -25,7 +25,7 @@ def __init__(self, **kwargs: Any) -> None: self.task = GetItemTask(self.GOAL) super().__init__(purpose=self.task, start_zone=self.ROOM, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: find_goal = Transformation( "find_goal", inventory_changes=[Yield(CURRENT_ZONE, self.GOAL, max=0)], diff --git a/src/hcraft/examples/minicraft/fourrooms.py b/src/hcraft/examples/minicraft/fourrooms.py index 9f1151bb..b59f7681 100644 --- a/src/hcraft/examples/minicraft/fourrooms.py +++ b/src/hcraft/examples/minicraft/fourrooms.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, TypeVar +from typing import Any, TypeVar from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -42,7 +42,7 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(start_zone=self.SOUTH_WEST_ROOM, purpose=self.task, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: find_goal = Transformation( "find-goal", inventory_changes=[Yield(CURRENT_ZONE, self.GOAL, max=0)], @@ -76,7 +76,7 @@ def build_transformations(self) -> List[Transformation]: Room = TypeVar("Room") -def _get_rooms_connections(rooms: List[Room]) -> Dict[Room, List[Room]]: +def _get_rooms_connections(rooms: list[Room]) -> dict[Room, list[Room]]: neighbors = {} for room_id, destination in enumerate(rooms): neighbors[destination] = [rooms[room_id - 1], rooms[(room_id + 1) % len(rooms)]] diff --git a/src/hcraft/examples/minicraft/keycorridor.py b/src/hcraft/examples/minicraft/keycorridor.py index 00f7dab3..08a5ff55 100644 --- a/src/hcraft/examples/minicraft/keycorridor.py +++ b/src/hcraft/examples/minicraft/keycorridor.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -49,7 +49,7 @@ def __init__(self, **kwargs: Any) -> None: **kwargs, ) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] # Ensure key cannot be created if anywhere else diff --git a/src/hcraft/examples/minicraft/minicraft.py b/src/hcraft/examples/minicraft/minicraft.py index c3bb6f40..41956334 100644 --- a/src/hcraft/examples/minicraft/minicraft.py +++ b/src/hcraft/examples/minicraft/minicraft.py @@ -1,6 +1,6 @@ from pathlib import Path -from typing import Any, Optional, List, Dict, Union +from typing import Any from abc import abstractmethod from hcraft.elements import Item, Stack, Zone @@ -15,13 +15,13 @@ class MiniCraftEnv(HcraftEnv): [minigrid environments](https://minigrid.farama.org/environments/minigrid/). """ - MINICRAFT_NAME: Optional[str] = None + MINICRAFT_NAME: str | None = None def __init__( self, - start_zone: Optional[Zone] = None, - start_items: Optional[List[Union[Stack, Item]]] = None, - start_zones_items: Optional[Dict[Zone, List[Union[Stack, Item]]]] = None, + start_zone: Zone | None = None, + start_items: list[Stack | Item] | None = None, + start_zones_items: dict[Zone, list[Stack | Item]] | None = None, **kwargs: Any, ) -> None: """ @@ -43,7 +43,7 @@ def __init__( super().__init__(world, name=f"MiniHCraft{self.MINICRAFT_NAME}", **kwargs) @abstractmethod - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: """Build transformations for this MiniCraft environment""" raise NotImplementedError diff --git a/src/hcraft/examples/minicraft/multiroom.py b/src/hcraft/examples/minicraft/multiroom.py index 7637b6b5..8f785312 100644 --- a/src/hcraft/examples/minicraft/multiroom.py +++ b/src/hcraft/examples/minicraft/multiroom.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -22,7 +22,7 @@ def __init__(self, n_rooms: int = 6, **kwargs: Any) -> None: self.task = GetItemTask(self.GOAL) super().__init__(purpose=self.task, start_zone=self.rooms[0], **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] find_goal = Transformation( "Find goal", @@ -41,7 +41,7 @@ def build_transformations(self) -> List[Transformation]: transformations.append(reach_goal) for i, room in enumerate(self.rooms): - connected_rooms: List[Zone] = [] + connected_rooms: list[Zone] = [] if i > 0: connected_rooms.append(self.rooms[i - 1]) if i < len(self.rooms) - 1: diff --git a/src/hcraft/examples/minicraft/unlock.py b/src/hcraft/examples/minicraft/unlock.py index cdbc33d5..5e94ca6c 100644 --- a/src/hcraft/examples/minicraft/unlock.py +++ b/src/hcraft/examples/minicraft/unlock.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import PlaceItemTask @@ -30,7 +30,7 @@ def __init__(self, **kwargs: Any) -> None: self.task = PlaceItemTask(self.OPEN_DOOR) super().__init__(purpose=self.task, start_zone=self.START, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] search_for_key = Transformation( diff --git a/src/hcraft/examples/minicraft/unlockpickup.py b/src/hcraft/examples/minicraft/unlockpickup.py index 90322588..2097b7fc 100644 --- a/src/hcraft/examples/minicraft/unlockpickup.py +++ b/src/hcraft/examples/minicraft/unlockpickup.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -37,7 +37,7 @@ def __init__(self, **kwargs: Any) -> None: self.task = GetItemTask(self.BOX) super().__init__(purpose=self.task, start_zone=self.START, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] zones = (self.START, self.BOX_ROOM) diff --git a/src/hcraft/examples/minicraft/unlockpickupblocked.py b/src/hcraft/examples/minicraft/unlockpickupblocked.py index 93581548..af569988 100644 --- a/src/hcraft/examples/minicraft/unlockpickupblocked.py +++ b/src/hcraft/examples/minicraft/unlockpickupblocked.py @@ -1,4 +1,4 @@ -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.task import GetItemTask @@ -41,7 +41,7 @@ def __init__(self, **kwargs: Any) -> None: self.task = GetItemTask(self.BOX) super().__init__(purpose=self.task, start_zone=self.START, **kwargs) - def build_transformations(self) -> List[Transformation]: + def build_transformations(self) -> list[Transformation]: transformations = [] zones = (self.START, self.BOX_ROOM) diff --git a/src/hcraft/examples/random_simple/env.py b/src/hcraft/examples/random_simple/env.py index 2c23dd4f..cbdf4449 100644 --- a/src/hcraft/examples/random_simple/env.py +++ b/src/hcraft/examples/random_simple/env.py @@ -6,7 +6,7 @@ """ -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import Any import numpy as np @@ -24,7 +24,7 @@ class RandomHcraftEnv(HcraftEnv): def __init__( self, - n_items_per_n_inputs: Optional[Dict[int, int]] = None, + n_items_per_n_inputs: dict[int, int] | None = None, seed: int = None, **kwargs: Any, ): @@ -48,7 +48,7 @@ def __init__( ] ) name = f"RandomCrafing-{env_characteristics}-S{seed}" - self.items: List[Item] = [] + self.items: list[Item] = [] transformations = self._transformations(n_items_per_n_inputs) world = world_from_transformations(transformations) if "purpose" not in kwargs: @@ -60,8 +60,8 @@ def __init__( def _transformations( self, - n_items_per_n_inputs: Dict[int, int], - ) -> List[Transformation]: + n_items_per_n_inputs: dict[int, int], + ) -> list[Transformation]: """Build transformations for a RandomHcraft environement. Args: @@ -93,9 +93,7 @@ def _transformations( while len(accessible_items) < len(self.items): new_accessible_item = unaccessible_items.pop() - inventory_changes: List[Union[Use, Yield]] = [ - Yield(PLAYER, new_accessible_item) - ] + inventory_changes: list[Use | Yield] = [Yield(PLAYER, new_accessible_item)] n_inputs = int(new_accessible_item.name.split("_")[0]) n_inputs = min(n_inputs, len(accessible_items)) diff --git a/src/hcraft/examples/recursive.py b/src/hcraft/examples/recursive.py index b78eb767..16f99700 100644 --- a/src/hcraft/examples/recursive.py +++ b/src/hcraft/examples/recursive.py @@ -19,7 +19,7 @@ """ -from typing import Any, List, Union +from typing import Any from hcraft.elements import Item from hcraft.env import HcraftEnv @@ -57,7 +57,7 @@ def __init__(self, n_items: int = 6, **kwargs: Any): **kwargs, ) - def build_transformations(self, items: List[Item]) -> List[Transformation]: + def build_transformations(self, items: list[Item]) -> list[Transformation]: """Build transformations to make every item accessible. Args: @@ -70,7 +70,7 @@ def build_transformations(self, items: List[Item]) -> List[Transformation]: transformation = [] for index, item in enumerate(items): - inventory_changes: List[Union[Use, Yield]] = [Yield(PLAYER, item)] + inventory_changes: list[Use | Yield] = [Yield(PLAYER, item)] if index > 0: inventory_changes += [ Use(PLAYER, items[item_id], consume=1) for item_id in range(index) diff --git a/src/hcraft/examples/tower.py b/src/hcraft/examples/tower.py index 88a60215..5e717922 100644 --- a/src/hcraft/examples/tower.py +++ b/src/hcraft/examples/tower.py @@ -32,7 +32,7 @@ """ -from typing import Any, List, Union +from typing import Any from hcraft.elements import Item from hcraft.env import HcraftEnv @@ -86,7 +86,7 @@ def __init__(self, height: int = 2, width: int = 3, **kwargs: Any): kwargs["purpose"] = GetItemTask(self.items[-1]) super().__init__(world, name=name, **kwargs) - def build_transformations(self, items: List[Item]) -> List[Transformation]: + def build_transformations(self, items: list[Item]) -> list[Transformation]: """Build transformations to make every item accessible. Args: @@ -110,7 +110,7 @@ def build_transformations(self, items: List[Item]) -> List[Transformation]: item_id = layer * self.width + item_layer_id item = items[item_id] - inventory_changes: List[Union[Use, Yield]] = [Yield(PLAYER, item)] + inventory_changes: list[Use | Yield] = [Yield(PLAYER, item)] prev_layer_id = (layer - 1) * self.width for prev_item_id in range(self.width): diff --git a/src/hcraft/examples/treasure/__init__.py b/src/hcraft/examples/treasure/__init__.py index 0a584880..1d7c7125 100644 --- a/src/hcraft/examples/treasure/__init__.py +++ b/src/hcraft/examples/treasure/__init__.py @@ -9,7 +9,7 @@ """ from pathlib import Path -from typing import Any, List +from typing import Any from hcraft.elements import Item, Zone from hcraft.env import HcraftEnv @@ -62,7 +62,7 @@ def __init__(self, **kwargs: Any) -> None: world, purpose=GetItemTask(self.GOLD), name="TreasureHcraft", **kwargs ) - def _build_transformations(self) -> List[Transformation]: + def _build_transformations(self) -> list[Transformation]: TAKE_GOLD_FROM_CHEST = Transformation( "take-gold-from-chest", inventory_changes=[ diff --git a/src/hcraft/metrics.py b/src/hcraft/metrics.py index a8200678..4d994789 100644 --- a/src/hcraft/metrics.py +++ b/src/hcraft/metrics.py @@ -1,4 +1,4 @@ -from typing import Dict, Sequence, Union +from typing import Sequence from hcraft.task import Task from hcraft.purpose import TerminalGroup @@ -7,10 +7,10 @@ class SuccessCounter: """Counter of success rates of tasks or terminal groups.""" - def __init__(self, elements: Sequence[Union[Task, TerminalGroup]]) -> None: + def __init__(self, elements: Sequence[Task | TerminalGroup]) -> None: self.elements = elements - self.step_states: Dict[Union[Task, TerminalGroup], bool] = {} - self.successes: Dict[Union[Task, TerminalGroup], Dict[int, bool]] = { + self.step_states: dict[Task | TerminalGroup, bool] = {} + self.successes: dict[Task | TerminalGroup, dict[int, bool]] = { element: {} for element in self.elements } @@ -33,14 +33,14 @@ def update(self, episode: int) -> None: self.successes[element][episode] = True @property - def done_infos(self) -> Dict[str, bool]: + def done_infos(self) -> dict[str, bool]: return { self._is_done_str(self._name(element)): element.terminated for element in self.elements } @property - def rates_infos(self) -> Dict[str, float]: + def rates_infos(self) -> dict[str, float]: return { self._success_str(self._name(element)): self._rate(element) for element in self.elements @@ -54,7 +54,7 @@ def _success_str(name: str) -> str: def _is_done_str(name: str) -> str: return f"{name} is done" - def _name(self, element: Union[Task, TerminalGroup]) -> str: + def _name(self, element: Task | TerminalGroup) -> str: if isinstance(element, Task): return element.name group_name = "Purpose" @@ -62,6 +62,6 @@ def _name(self, element: Union[Task, TerminalGroup]) -> str: group_name = f"Terminal group '{element.name}'" return group_name - def _rate(self, element: Union[Task, TerminalGroup]) -> float: + def _rate(self, element: Task | TerminalGroup) -> float: n_episodes = max(1, len(self.successes[element])) return sum(self.successes[element].values()) / n_episodes diff --git a/src/hcraft/planning.py b/src/hcraft/planning.py index 6184f98f..2ddc4045 100644 --- a/src/hcraft/planning.py +++ b/src/hcraft/planning.py @@ -75,7 +75,7 @@ """ -from typing import TYPE_CHECKING, Any, Dict, Optional, Union, List +from typing import TYPE_CHECKING, Any, Union from copy import deepcopy @@ -118,7 +118,7 @@ from hcraft.world import World from unified_planning.model.fnode import FNode -Statistics = Dict[str, Union[str, float, int]] +Statistics = dict[str, str | float | int] class HcraftPlanningProblem: @@ -128,10 +128,10 @@ def __init__( self, state: "HcraftState", name: str, - purpose: Optional["Purpose"], + purpose: "Purpose | None", hierarchical: bool, timeout: float = 60, - planner_name: Optional[str] = None, + planner_name: str | None = None, ) -> None: """Initialize a HierarchyCraft planning problem on the given state and purpose. @@ -155,13 +155,13 @@ def __init__( self.upf_problem = self._init_flat_problem( state=state, name=name, purpose=purpose ) - self.plan: Optional["SequentialPlan"] = None - self.plans: List["SequentialPlan"] = [] - self.stats: List["Statistics"] = [] + self.plan: "SequentialPlan | None" = None + self.plans: list["SequentialPlan"] = [] + self.stats: list["Statistics"] = [] self.timeout = timeout self.planner_name = planner_name - def action_from_plan(self, state: "HcraftState") -> Optional[int]: + def action_from_plan(self, state: "HcraftState") -> int | None: """Get the next gym action from a given state. If a plan is already existing, just use the next action in the plan. @@ -176,7 +176,7 @@ def action_from_plan(self, state: "HcraftState") -> Optional[int]: if self.plan is None: self.update_problem_to_state(self.upf_problem, state) self.solve() - actions: Optional[list] = self.plan.actions # type: ignore + actions: list | None = self.plan.actions # type: ignore if not actions: # Empty plan, nothing to do return None plan_action_name = str(actions.pop(0)) @@ -241,7 +241,7 @@ def _init_flat_problem( self, state: "HcraftState", name: str, - purpose: Optional["Purpose"], + purpose: "Purpose | None", ) -> "Problem": """Build a unified planning problem from the given world and purpose. @@ -265,7 +265,7 @@ def _init_hierarchical_problem( self, state: "HcraftState", name: str, - purpose: Optional["Purpose"], + purpose: "Purpose | None", ) -> "HierarchicalProblem": upf_problem = HierarchicalProblem(name) self._add_base_flat_problem(upf_problem, state.world) @@ -293,7 +293,7 @@ def _add_tasks_to_hierarchical_problem( self, hproblem: "HierarchicalProblem", world: "World" ) -> None: # Get enough of item tasks - self.get_enough_of_item_task: Dict[Item, "UpfHTask"] = {} + self.get_enough_of_item_task: dict[Item, "UpfHTask"] = {} for item in world.items: task = hproblem.add_task(f"get-enough-of-{item.name}", quantity=IntType()) self.get_enough_of_item_task[item] = task @@ -363,9 +363,9 @@ def _add_base_flat_problem( def _add_actions_to_problem( self, upf_problem: Union["Problem", "HierarchicalProblem"], - transformations: List["Transformation"], + transformations: list["Transformation"], ) -> None: - self.transformation_action: Dict[int, "InstantaneousAction"] = {} + self.transformation_action: dict[int, "InstantaneousAction"] = {} for t_id, transfo in enumerate(transformations): self.transformation_action[t_id] = self._action_from_transformation( transfo, t_id @@ -391,23 +391,23 @@ def _add_fluents_to_problem( def _add_objects_to_problem( self, upf_problem: Union["Problem", "HierarchicalProblem"], - zones: List[Zone], - items: List[Item], - zones_items: List[Item], + zones: list[Zone], + items: list[Item], + zones_items: list[Item], ) -> None: self.zone_type = UserType("zone") self.player_item_type = UserType("player_item") self.zone_item_type = UserType("zone_item") - self.zones_obj: Dict[Zone, "Object"] = {} + self.zones_obj: dict[Zone, "Object"] = {} for zone in zones: self.zones_obj[zone] = Object(zone.name, self.zone_type) - self.items_obj: Dict[Item, "Object"] = {} + self.items_obj: dict[Item, "Object"] = {} for item in items: self.items_obj[item] = Object(item.name, self.player_item_type) - self.zone_items_obj: Dict[Item, "Object"] = {} + self.zone_items_obj: dict[Item, "Object"] = {} for item in zones_items: self.zone_items_obj[item] = Object( f"{item.name}_in_zone", self.zone_item_type @@ -487,7 +487,7 @@ def _add_current_zone_operations( self, action: "InstantaneousAction", transfo: Transformation, - loc: Optional[Any], + loc: Any | None, ) -> None: current = InventoryOwner.CURRENT for stack in transfo.get_changes(current, "add", []): @@ -540,7 +540,7 @@ def _purpose_to_goal(self, purpose: "Purpose") -> "FNode": def _read_statistics(results: "PlanGenerationResult") -> Statistics: - if results.engine_name == "enhsp": + if results.engine_name in ("enhsp", "SAT-enhsp"): return _read_enhsp_stats(results) elif results.engine_name == "aries": return _read_aries_stats(results) diff --git a/src/hcraft/purpose.py b/src/hcraft/purpose.py index ffc264dc..c96c20ad 100644 --- a/src/hcraft/purpose.py +++ b/src/hcraft/purpose.py @@ -96,7 +96,7 @@ from dataclasses import dataclass, field from enum import Enum -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, Union +from typing import TYPE_CHECKING, Any, Sequence, Union import networkx as nx import numpy as np @@ -135,7 +135,7 @@ class TerminalGroup: """ name: str - tasks: List[Task] = field(default_factory=list) + tasks: list[Task] = field(default_factory=list) @property def terminated(self) -> bool: @@ -158,7 +158,7 @@ class Purpose: def __init__( self, - tasks: Optional[Union[Task, List[Task]]] = None, + tasks: Task | list[Task] | None = None, timestep_reward: float = 0.0, default_reward_shaping: RewardShaping = RewardShaping.NONE, shaping_value: float = 1.0, @@ -174,14 +174,14 @@ def __init__( shaping_value: Reward value used in reward shaping if any. Defaults to 1.0. """ - self.tasks: List[Task] = [] + self.tasks: list[Task] = [] self.timestep_reward = timestep_reward self.shaping_value = shaping_value self.default_reward_shaping = default_reward_shaping self.built = False - self.reward_shaping: Dict[Task, RewardShaping] = {} - self.terminal_groups: List[TerminalGroup] = [] + self.reward_shaping: dict[Task, RewardShaping] = {} + self.terminal_groups: list[TerminalGroup] = [] if isinstance(tasks, Task): tasks = [tasks] @@ -190,13 +190,13 @@ def __init__( for task in tasks: self.add_task(task, reward_shaping=default_reward_shaping) - self._best_terminal_group: Optional[TerminalGroup] = None + self._best_terminal_group: TerminalGroup | None = None def add_task( self, task: Task, - reward_shaping: Optional[RewardShaping] = None, - terminal_groups: Optional[Union[str, List[str]]] = "default", + reward_shaping: RewardShaping | None = None, + terminal_groups: str | list[str] | None = "default", ) -> None: """Add a new task to the purpose. @@ -282,7 +282,7 @@ def reset(self) -> None: task.reset() @property - def optional_tasks(self) -> List[Task]: + def optional_tasks(self) -> list[Task]: """List of tasks in no terminal group hence being optinal.""" terminal_tasks = [] for group in self.terminal_groups: @@ -318,7 +318,7 @@ def best_terminal_group(self) -> TerminalGroup: self._best_terminal_group = best_terminal_group return best_terminal_group - def _terminal_group_from_name(self, name: str) -> Optional[TerminalGroup]: + def _terminal_group_from_name(self, name: str) -> TerminalGroup | None: if name not in self.terminal_groups: return None group_id = [group.name for group in self.terminal_groups].index(name) @@ -326,7 +326,7 @@ def _terminal_group_from_name(self, name: str) -> Optional[TerminalGroup]: def _add_reward_shaping_subtasks( self, task: Task, env: "HcraftEnv", reward_shaping: RewardShaping - ) -> List[Task]: + ) -> list[Task]: if reward_shaping == RewardShaping.NONE: return [] if reward_shaping == RewardShaping.ALL_ACHIVEMENTS: @@ -350,7 +350,7 @@ def __str__(self) -> str: joined_groups_str = ", ".join(terminal_groups_str) return f"Purpose({joined_groups_str})" - def _tasks_str(self, tasks: List[Task]) -> str: + def _tasks_str(self, tasks: list[Task]) -> str: tasks_str = [] for task in tasks: shaping = self.reward_shaping[task] @@ -360,9 +360,9 @@ def _tasks_str(self, tasks: List[Task]) -> str: def platinium_purpose( - items: List[Item], - zones: List[Zone], - zones_items: List[Item], + items: list[Item], + zones: list[Zone], + zones_items: list[Item], success_reward: float = 10.0, timestep_reward: float = -0.1, ) -> "Purpose": @@ -376,7 +376,7 @@ def platinium_purpose( return purpose -def _all_subtasks(world: "World", shaping_reward: float) -> List[Task]: +def _all_subtasks(world: "World", shaping_reward: float) -> list[Task]: return _build_reward_shaping_subtasks( world.items, world.zones, world.zones_items, shaping_reward ) @@ -384,10 +384,10 @@ def _all_subtasks(world: "World", shaping_reward: float) -> List[Task]: def _required_subtasks( task: Task, env: "HcraftEnv", shaping_reward: float -) -> List[Task]: - relevant_items: Set[Item] = set() - relevant_zones: Set[Zone] = set() - relevant_zone_items: Set[Item] = set() +) -> list[Task]: + relevant_items: set[Item] = set() + relevant_zones: set[Zone] = set() + relevant_zone_items: set[Item] = set() if isinstance(task, GetItemTask): goal_item = task.item_stack.item @@ -437,7 +437,7 @@ def _required_subtasks( ) -def _inputs_subtasks(task: Task, world: "World", shaping_reward: float) -> List[Task]: +def _inputs_subtasks(task: Task, world: "World", shaping_reward: float) -> list[Task]: relevant_items = set() relevant_zones = set() relevant_zone_items = set() @@ -497,12 +497,12 @@ def _inputs_subtasks(task: Task, world: "World", shaping_reward: float) -> List[ def _build_reward_shaping_subtasks( - items: Optional[Sequence[Item]] = None, - zones: Optional[Sequence[Zone]] = None, - zone_items: Optional[Sequence[Item]] = None, + items: Sequence[Item] | None = None, + zones: Sequence[Zone] | None = None, + zone_items: Sequence[Item] | None = None, shaping_reward: float = 1.0, -) -> List[Task]: - subtasks: List[Task] = [] +) -> list[Task]: + subtasks: list[Task] = [] if items: subtasks += [GetItemTask(item, reward=shaping_reward) for item in items] if zones: diff --git a/src/hcraft/render/human.py b/src/hcraft/render/human.py index afd0e1cc..6afac268 100644 --- a/src/hcraft/render/human.py +++ b/src/hcraft/render/human.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: from pygame.event import Event @@ -8,15 +8,15 @@ def get_human_action( env: "HcraftEnv", - additional_events: List["Event"] = None, + additional_events: list["Event"] = None, can_be_none: bool = False, - fps: Optional[float] = None, -) -> Optional[int]: + fps: float | None = None, +) -> int | None: """Update the environment rendering and gather potential action given by the UI. Args: env: The running HierarchyCraft environment. - additional_events (Optional): Additional simulated pygame events. + additional_events : Additional simulated pygame events. can_be_none: If False, this function will loop on rendering until an action is found. If True, will return None if no action was found after one rendering update. diff --git a/src/hcraft/render/render.py b/src/hcraft/render/render.py index f0e73870..d708dc84 100644 --- a/src/hcraft/render/render.py +++ b/src/hcraft/render/render.py @@ -4,7 +4,7 @@ import os import sys -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING try: @@ -36,7 +36,7 @@ class HcraftWindow: def __init__( self, - window_shape: Tuple[int, int] = (1280, 720), + window_shape: tuple[int, int] = (1280, 720), player_inventory_display: DisplayMode = DisplayMode.CURRENT, zone_inventory_display: DisplayMode = DisplayMode.CURRENT, position_display: DisplayMode = DisplayMode.CURRENT, @@ -46,7 +46,7 @@ def __init__( """Initialize a HierarchyCraft window without building it on a specific environment (yet). Args: - window_shape (Tuple[int, int], optional): Size of the window. Defaults to (1280, 720). + window_shape (tuple[int, int], optional): Size of the window. Defaults to (1280, 720). player_inventory_display (DisplayMode, optional): When to see items in inventory. Defaults to DisplayMode.CURRENT. zone_inventory_display (DisplayMode, optional): When to see current zone items. @@ -69,11 +69,11 @@ def __init__( "Install using 'pip install hcraft[gui]'." ) from error - self.env: Optional["HcraftEnv"] = None - self.clock: Optional[Clock] = None - self.screen: Optional[Surface] = None - self.menus: Dict[ - str, Union[TransformationsWidget, InventoryWidget, PostitionWidget] + self.env: "HcraftEnv | None" = None + self.clock: Clock | None = None + self.screen: Surface | None = None + self.menus: dict[ + str, TransformationsWidget | InventoryWidget | PostitionWidget ] = {} self.window_shape = window_shape @@ -114,13 +114,13 @@ def build(self, env: "HcraftEnv") -> None: def update_rendering( self, - additional_events: Optional[List["Event"]] = None, - fps: Optional[float] = None, - ) -> Union[int, None]: + additional_events: list["Event"] | None = None, + fps: float | None = None, + ) -> int | None: """Update the User Interface returning action if one was found. Args: - additional_events (Optional): Additional pygame events to simulate. + additional_events : Additional pygame events to simulate. fps: frames_per_seconds Returns: @@ -153,7 +153,7 @@ def update_rendering( # Update inventories state = self.env.state - if "player_inventory" is self.menus and self.menus["player_inventory"]: + if "player_inventory" == self.menus and self.menus["player_inventory"]: title = "Inventory" if self.env.purpose.tasks: title = f"Inventory | SCORE: {self.env.current_score}" @@ -181,7 +181,7 @@ def update_rendering( self.menus["actions"].draw(self.screen) # Gather action taken if any - action_taken: Optional[int] = None + action_taken: int | None = None selected_widget = self.menus["actions"].get_selected_widget() if selected_widget is not None and selected_widget.update(events): action_taken = selected_widget.apply() @@ -197,7 +197,7 @@ def make_menus( position_display: DisplayMode, transformation_display_mode: DisplayMode, transformation_content_mode: ContentMode, - ) -> Dict[str, Union[TransformationsWidget, InventoryWidget, PostitionWidget]]: + ) -> dict[str, TransformationsWidget | InventoryWidget | PostitionWidget]: """Build menus for user interface. Args: @@ -290,9 +290,9 @@ def make_menus( ), ) - menus: Dict[ - str, Union[TransformationsWidget, InventoryWidget, PostitionWidget] - ] = {"actions": actions_menu} + menus: dict[str, TransformationsWidget | InventoryWidget | PostitionWidget] = { + "actions": actions_menu + } if player_inventory is not None: menus["player_inventory"] = player_inventory if zone_inventory is not None: @@ -333,8 +333,8 @@ def _loading_screen(self): def menus_sizes( - n_items: int, n_zones_items: int, n_zones: int, window_shape: Tuple[int, int] -) -> Dict[str, Tuple[int, int]]: + n_items: int, n_zones_items: int, n_zones: int, window_shape: tuple[int, int] +) -> dict[str, tuple[int, int]]: actions_size = (int(0.35 * window_shape[0]), window_shape[1]) zone_width = window_shape[0] - actions_size[0] diff --git a/src/hcraft/render/utils.py b/src/hcraft/render/utils.py index fcdb1870..2445e3f2 100644 --- a/src/hcraft/render/utils.py +++ b/src/hcraft/render/utils.py @@ -4,7 +4,7 @@ import os from io import BytesIO from pathlib import Path -from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Sequence import numpy as np from PIL import Image, ImageDraw, ImageFont @@ -30,7 +30,7 @@ logging.getLogger("matplotlib.font_manager").setLevel(logging.INFO) -def obj_image_path(obj: Union[Item, Zone], resources_path: Path) -> Path: +def obj_image_path(obj: Item | Zone, resources_path: Path) -> Path: if isinstance(obj, Item): return resources_path.joinpath("items", f"{obj.name}.png") elif isinstance(obj, Zone): @@ -39,7 +39,7 @@ def obj_image_path(obj: Union[Item, Zone], resources_path: Path) -> Path: raise TypeError(f"Unsupported type for loading images: {type(obj)}") -def load_image(resources_path: Path, obj: Union[Item, Zone]) -> Optional[Image.Image]: +def load_image(resources_path: Path, obj: Item | Zone) -> Image.Image | None: """Load a PIL image for and obj in a world. Args: @@ -82,12 +82,12 @@ def draw_text_on_image( text: str, resources_path: Path, text_relative_size: float = 0.3, -) -> Optional["BaseImage"]: +) -> "BaseImage | None": """Draw on top of an image, converting it to Pygame image. Args: image (Image): image to draw on to of. - text (Optional): Text to draw on top of the image. + text : Text to draw on top of the image. text_relative_size: If a text is given, this is the relative size of the text compared to the image size. @@ -114,7 +114,7 @@ def _to_menu_image(image: "Image.Image", scaling: float) -> "BaseImage": def create_text_image( - text: str, resources_path: Path, width: Optional[int] = None, height: int = 120 + text: str, resources_path: Path, width: int | None = None, height: int = 120 ) -> "Image.Image": """Create a PIL image for an Item or Zone. @@ -143,11 +143,11 @@ def create_text_image( def build_transformation_image( transformation: "Transformation", resources_path: Path, - zone_bg_color: Tuple[int, int, int] = (185, 215, 115), - dest_bg_color: Tuple[int, int, int] = (220, 200, 175), + zone_bg_color: tuple[int, int, int] = (185, 215, 115), + dest_bg_color: tuple[int, int, int] = (220, 200, 175), ) -> "Image.Image": """Build a transformation image from items and zones images.""" - added_images: List[Image.Image] = [] + added_images: list[Image.Image] = [] added_player_items = transformation.get_changes("player", "add") @@ -180,7 +180,7 @@ def build_transformation_image( arrow_image = create_text_image("->", resources_path) - removed_images: List["Image.Image"] = [] + removed_images: list["Image.Image"] = [] removed_player_items = transformation.get_changes("player", "remove") if removed_player_items is not None: if isinstance(removed_player_items, dict): @@ -233,13 +233,13 @@ def build_transformation_image( def load_or_create_image( - stack_or_zone: Union[Item, Stack, Zone], + stack_or_zone: Item | Stack | Zone, resources_path: Path, - bg_color: Optional[Tuple[int, int, int]] = None, + bg_color: tuple[int, int, int] | None = None, ) -> "Image": """Load or create an image for an item or zone.""" - item_or_zone: Union[Item, Zone] + item_or_zone: Item | Zone if isinstance(stack_or_zone, (Zone, Item)): obj_name = stack_or_zone.name quantity = 0 @@ -253,7 +253,7 @@ def load_or_create_image( image = load_image(obj=item_or_zone, resources_path=resources_path) if image is None: - image_size: Tuple[Optional[int], int] = (None, 120) + image_size: tuple[int | None, int] = (None, 120) if isinstance(item_or_zone, Zone): image_size = (699, 394) image = create_text_image( @@ -268,16 +268,16 @@ def load_or_create_image( def load_or_create_images( - objs: Sequence[Union[Stack, Zone]], + objs: Sequence[Stack | Zone], resources_path: Path, - bg_color: Optional[Tuple[int, int, int]] = None, -) -> List["Image.Image"]: + bg_color: tuple[int, int, int] | None = None, +) -> list["Image.Image"]: """Load or create images for the given objects.""" return [load_or_create_image(obj, resources_path, bg_color) for obj in objs] def _add_background_elipsis( - image: "Image.Image", bg_color: Tuple[int, int, int] + image: "Image.Image", bg_color: tuple[int, int, int] ) -> "Image.Image": image_bg = Image.new("RGBA", image.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(image_bg) @@ -309,7 +309,7 @@ def _font_path(resources_path: str) -> str: def _get_scale_ratio( - initial_shape: Tuple[int, int], wanted_shape: Tuple[int, int] + initial_shape: tuple[int, int], wanted_shape: tuple[int, int] ) -> float: if wanted_shape[0] == initial_shape[0] or wanted_shape[1] == initial_shape[1]: return 1 diff --git a/src/hcraft/render/widgets.py b/src/hcraft/render/widgets.py index 4a5e2400..f1c311a7 100644 --- a/src/hcraft/render/widgets.py +++ b/src/hcraft/render/widgets.py @@ -2,7 +2,7 @@ from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Sequence, Union import numpy as np @@ -65,8 +65,8 @@ def __init__( title: str, height: int, width: int, - position: Tuple[int, int, bool], - items: List[Item], + position: tuple[int, int, bool], + items: list[Item], resources_path: Path, display_mode: DisplayMode, rows: int = 7, @@ -92,8 +92,8 @@ def __init__( self.display_mode = DisplayMode(display_mode) self.base_images = _load_base_images(items, resources_path) - self.button_id_to_item: Dict[str, Item] = {} - self.old_quantity: Dict[Item, int] = {} + self.button_id_to_item: dict[str, Item] = {} + self.old_quantity: dict[Item, int] = {} for item in self.items: self._build_button(item) @@ -102,7 +102,7 @@ def update_inventory( inventory: np.ndarray, discovered: np.ndarray, events: Any, - title: Optional[str] = None, + title: str | None = None, ) -> bool: items_buttons = [ widget @@ -171,8 +171,8 @@ def _update_button_image( def _load_base_images( - objs: Sequence[Union[Item, Zone]], resources_path: Path -) -> Dict[Union[Item, Zone], "Image"]: + objs: Sequence[Item | Zone], resources_path: Path +) -> dict[Item | Zone, "Image"]: base_images = {} for obj in objs: base_images[obj] = load_image(resources_path, obj=obj) @@ -185,8 +185,8 @@ def __init__( title: str, height: int, width: int, - position: Tuple[int, int], - transformations: List[Transformation], + position: tuple[int, int], + transformations: list[Transformation], resources_path: Path, display_mode: DisplayMode, content_display_mode: ContentMode, @@ -212,10 +212,10 @@ def __init__( self.display_mode = DisplayMode(display_mode) self.content_display_mode = ContentMode(content_display_mode) self.button_id_to_transfo = {} - self.old_display: Dict[str, bool] = {} - self.old_legal: Dict[str, bool] = {} - self.buttons_base_image: Dict[str, "Image"] = {} - self.buttons_hidden_image: Dict[str, "Image"] = {} + self.old_display: dict[str, bool] = {} + self.old_legal: dict[str, bool] = {} + self.buttons_base_image: dict[str, "Image"] = {} + self.buttons_hidden_image: dict[str, "Image"] = {} for index, transfo in enumerate(self.transformations): button = self._build_transformation_button(transfo, index) self.button_id_to_transfo[button.get_id()] = transfo @@ -318,8 +318,8 @@ def __init__( title: str, height: int, width: int, - position: Tuple[int, int, bool], - zones: List[Zone], + position: tuple[int, int, bool], + zones: list[Zone], resources_path: Path, display_mode: DisplayMode, ): @@ -350,8 +350,8 @@ def __init__( self.base_images = _load_base_images(zones, resources_path) self.resources_path = resources_path self.display_mode = DisplayMode(display_mode) - self.button_id_to_zone: Dict[str, Zone] = {} - self.old_quantity: Dict[str, int] = {} + self.button_id_to_zone: dict[str, Zone] = {} + self.old_quantity: dict[str, int] = {} for zone in self.zones: self._build_button(zone) diff --git a/src/hcraft/requirements.py b/src/hcraft/requirements.py index 67bb79a3..b70f9f12 100644 --- a/src/hcraft/requirements.py +++ b/src/hcraft/requirements.py @@ -77,7 +77,7 @@ import random from PIL import Image, ImageDraw, ImageFont -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Set, Tuple, Union +from typing import TYPE_CHECKING, Any, Sequence, Union import networkx as nx import numpy as np @@ -92,11 +92,11 @@ from hebg.layouts.metabased import leveled_layout_energy import hcraft +from hcraft.elements import Item, Stack, Zone from hcraft.render.utils import load_or_create_image, obj_image_path from hcraft.transformation import InventoryOperation, InventoryOwner if TYPE_CHECKING: - from hcraft.elements import Item, Stack, Zone from hcraft.transformation import Transformation from hcraft.world import World @@ -140,7 +140,7 @@ class RequirementTheme: def __init__( self, default_color: Any = "black", - edge_colors: Optional[List[Union[str, Tuple[int, int, int]]]] = None, + edge_colors: list[Union[str, tuple[int, int, int]]] | None = None, **kwargs: Any, ) -> None: self.colors = self.DEFAULT_COLORS.copy() @@ -167,7 +167,7 @@ def color_node(self, node_type: RequirementNode) -> Any: return self.default_color return self.colors.get(node_type.value, self.default_color) - def color_edges(self, edge_index: int) -> Union[str, Tuple[int, int, int]]: + def color_edges(self, edge_index: int) -> Union[str, tuple[int, int, int]]: """Returns the themed color of the given edge depending on his type.""" edge_color_index = edge_index % len(self.edges_colors) edge_color = self.edges_colors[edge_color_index] @@ -189,11 +189,11 @@ def __init__(self, world: "World"): def draw( self, - ax: Optional[Axes] = None, - theme: Optional[RequirementTheme] = None, + ax: Axes | None = None, + theme: RequirementTheme | None = None, layout: Union[str, "RequirementsGraphLayout"] = "level", engine: DrawEngine = DrawEngine.PLT, - save_path: Optional[Path] = None, + save_path: Path | None = None, **kwargs: Any, ) -> None: """Draw the requirements graph on the given Axes. @@ -295,7 +295,7 @@ def _add_transformation_edges( self, transfo: "Transformation", transfo_index: int, - zone: Optional["Zone"] = None, + zone: Zone | None = None, ) -> None: """Add edges induced by a HierarchyCraft recipe.""" zones = set() if zone is None else {zone} @@ -376,9 +376,9 @@ def _add_transformation_edges( def _add_crafts( self, - in_items: Set["Item"], - in_zone_items: Set["Item"], - zones: Set["Zone"], + in_items: set["Item"], + in_zone_items: set["Item"], + zones: set["Zone"], out_node: str, index: int, transfo: "Transformation", @@ -401,9 +401,9 @@ def _add_obj_edge( end_node: str, edge_type: RequirementEdge, index: int, - start_obj: Optional[Union["Zone", "Item"]] = None, - start_type: Optional[RequirementNode] = None, - edge_transformation: Optional["Transformation"] = None, + start_obj: Union["Zone", "Item"] | None = None, + start_type: RequirementNode | None = None, + edge_transformation: "Transformation | None" = None, ) -> None: start_name = req_node_name(start_obj, start_type) self._add_nodes([start_obj], start_type) @@ -439,7 +439,7 @@ def _add_start_edges(self, world: "World") -> None: def req_node_name( - obj: Optional[Union["Item", "Zone"]], node_type: Optional[RequirementNode] + obj: Union["Item", "Zone"] | None, node_type: RequirementNode | None ) -> str: """Get a unique node name for the requirements graph""" if node_type == RequirementNode.START or obj is None or node_type is None: @@ -450,7 +450,7 @@ def req_node_name( return node_type.value + "#" + name -def compute_levels(graph: Requirements) -> Dict[int, List[RequirementNode]]: +def compute_levels(graph: Requirements) -> dict[int, list[RequirementNode]]: """Compute the hierachical levels of a RequirementsGraph. Adds the attribute 'level' to each node in the given graph. @@ -476,7 +476,7 @@ def _compute_level_dependencies( if "level" in graph.nodes[node]: return True - pred_level_by_key: Dict[int, List[Optional[int]]] = {} + pred_level_by_key: dict[int, list[int | None]] = {} for pred, _node, key in graph.in_edges(node, keys=True): pred_level: int = graph.nodes[pred].get("level", None) if key not in pred_level_by_key: @@ -542,9 +542,9 @@ def collapse_as_digraph(multidigraph: nx.MultiDiGraph) -> nx.DiGraph: def _available_in_zones_stacks( - stacks: Optional[List["Stack"]], + stacks: list["Stack"] | None, zone: "Zone", - zones_stacks: Dict["Zone", List["Stack"]], + zones_stacks: dict["Zone", list["Stack"]], ) -> bool: """ Args: @@ -557,7 +557,7 @@ def _available_in_zones_stacks( """ if stacks is None: return True - is_available: Dict["Stack", bool] = {} + is_available: dict["Stack", bool] = {} for consumed_stack in stacks: start_stacks = zones_stacks.get(zone, []) for start_stack in start_stacks: @@ -585,8 +585,8 @@ def apply_color_theme(graph: nx.MultiDiGraph, theme: RequirementTheme) -> None: def compute_layout( - digraph: nx.DiGraph, layout: Union[str, RequirementsGraphLayout] = "level" -) -> Dict[str, Tuple[float, float]]: + digraph: nx.DiGraph, layout: str | RequirementsGraphLayout = "level" +) -> dict[str, tuple[float, float]]: layout = RequirementsGraphLayout(layout) if layout == RequirementsGraphLayout.LEVEL: pos = leveled_layout_energy(digraph) @@ -695,7 +695,7 @@ def _draw_on_plt_ax( # Add Hierarchies numbers if level_legend: - nodes_by_level: Dict[int, Any] = digraph.graph["nodes_by_level"] + nodes_by_level: dict[int, Any] = digraph.graph["nodes_by_level"] for level, level_nodes in nodes_by_level.items(): level_poses = np.array([pos[node] for node in level_nodes]) mean_x = np.mean(level_poses[:, 0]) @@ -706,10 +706,10 @@ def _draw_on_plt_ax( def _draw_html( - graph: Union[nx.DiGraph, nx.MultiDiGraph], + graph: nx.DiGraph | nx.MultiDiGraph, filepath: Path, resources_path: Path, - pos: Dict[str, Tuple[float, float]], + pos: dict[str, tuple[float, float]], depth: int, width: int, **kwargs: Any, @@ -767,9 +767,9 @@ def _serialize_pyvis( for node, node_data in graph.nodes(data=True): node_type = node_data.get("type") - node_obj: Optional[Union[Item, Zone]] = node_data.get("obj") + node_obj: Item | Zone | None = node_data.get("obj") - flat_node_data: Dict[str, Any] = {} + flat_node_data: dict[str, Any] = {} if node_type is RequirementNode.ITEM: title = f"{node_obj.name.capitalize()}" @@ -815,7 +815,7 @@ def _serialize_pyvis( serializable_graph.add_node(node, **flat_node_data) - done_edges: List[tuple] = [] + done_edges: list[tuple] = [] for start, end, key, edge_data in graph.edges(data=True, keys=True): transfo: "Transformation" = edge_data.get("obj") edge_type: RequirementEdge = edge_data.get("type") @@ -898,7 +898,7 @@ def _start_number_dict( edge_type: RequirementEdge, start_name: str, resources_path: Path, -) -> Optional[Dict[str, Union[bool, int, str]]]: +) -> dict[str, bool | int | str] | None: if edge_type is RequirementEdge.ITEM_REQUIRED: min_amount_of_start = [ stack.quantity @@ -928,7 +928,7 @@ def _end_number_dict( end_type: RequirementNode, end_name: str, resources_path: Path, -) -> Optional[dict]: +) -> dict | None: if end_type is RequirementNode.ITEM: amount_of_end = [ stack.quantity @@ -957,7 +957,7 @@ def _end_number_dict( } -def _arrows_data_for_image_uri(number_uri: str) -> Dict[str, Union[bool, int, str]]: +def _arrows_data_for_image_uri(number_uri: str) -> dict[str, bool | int | str]: return { "enabled": True, "src": number_uri, @@ -986,8 +986,8 @@ def _get_text_image_uri( def _create_text_image( text: str, - fill_color: Tuple[int, int, int] = (130, 130, 130), - font_path: Optional[Union[Path, str]] = "arial.ttf", + fill_color: tuple[int, int, int] = (130, 130, 130), + font_path: Path | str | None = "arial.ttf", ) -> "Image.Image": """Create a PIL image for an Item or Zone. diff --git a/src/hcraft/solving_behaviors.py b/src/hcraft/solving_behaviors.py index edfc9589..380855fd 100644 --- a/src/hcraft/solving_behaviors.py +++ b/src/hcraft/solving_behaviors.py @@ -41,7 +41,7 @@ """ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from hebg import Behavior @@ -59,9 +59,9 @@ from hcraft.env import HcraftEnv -def build_all_solving_behaviors(env: "HcraftEnv") -> Dict[str, "Behavior"]: +def build_all_solving_behaviors(env: "HcraftEnv") -> dict[str, "Behavior"]: """Return a dictionary of handcrafted behaviors to get each item, zone and property.""" - all_behaviors: Dict[str, Behavior] = {} + all_behaviors: dict[str, Behavior] = {} _reach_zones_behaviors(env, all_behaviors) _get_item_behaviors(env, all_behaviors) _drop_item_behaviors(env, all_behaviors) @@ -126,21 +126,21 @@ def task_to_behavior_name(task: Task) -> str: def _reach_zones_behaviors( - env: "HcraftEnv", all_behaviors: Dict[str, "Behavior"] + env: "HcraftEnv", all_behaviors: dict[str, "Behavior"] ) -> None: for zone in env.world.zones: behavior = ReachZone(zone, env, all_behaviors=all_behaviors) all_behaviors[behavior.name] = behavior -def _get_item_behaviors(env: "HcraftEnv", all_behaviors: Dict[str, "Behavior"]) -> None: +def _get_item_behaviors(env: "HcraftEnv", all_behaviors: dict[str, "Behavior"]) -> None: for item in env.world.items: behavior = GetItem(item, env, all_behaviors=all_behaviors) all_behaviors[behavior.name] = behavior def _drop_item_behaviors( - env: "HcraftEnv", all_behaviors: Dict[str, "Behavior"] + env: "HcraftEnv", all_behaviors: dict[str, "Behavior"] ) -> None: for item in env.world.items: behavior = DropItem(item, env, all_behaviors=all_behaviors) @@ -148,7 +148,7 @@ def _drop_item_behaviors( def _get_zone_item_behaviors( - env: "HcraftEnv", all_behaviors: Dict[str, "Behavior"] + env: "HcraftEnv", all_behaviors: dict[str, "Behavior"] ) -> None: for zone in [None] + env.world.zones: # Anywhere + in every specific zone for item in env.world.zones_items: @@ -157,7 +157,7 @@ def _get_zone_item_behaviors( def _do_transfo_behaviors( - env: "HcraftEnv", all_behaviors: Dict[str, "Behavior"] + env: "HcraftEnv", all_behaviors: dict[str, "Behavior"] ) -> None: for transfo in env.world.transformations: behavior = AbleAndPerformTransformation( diff --git a/src/hcraft/state.py b/src/hcraft/state.py index 87be543c..55b97b5c 100644 --- a/src/hcraft/state.py +++ b/src/hcraft/state.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict, Optional, Union +from typing import TYPE_CHECKING, Union import numpy as np @@ -68,7 +68,7 @@ def observation(self) -> np.ndarray: def amount_of( self, item: "Item", - owner: Optional[Union[str, "Zone", InventoryOwner]] = "player", + owner: Union[str, "Zone", InventoryOwner] | None = "player", ) -> int: """Current amount of the given item owned by owner. @@ -101,7 +101,7 @@ def has_discovered(self, zone: "Zone") -> bool: return bool(self.discovered_zones[zone_index]) @property - def current_zone(self) -> Optional["Zone"]: + def current_zone(self) -> Zone | None: """Current position of the player.""" if self.world.n_zones == 0: return None @@ -112,12 +112,12 @@ def _current_zone_slot(self) -> int: return int(self.position.nonzero()[0]) @property - def player_inventory_dict(self) -> Dict["Item", int]: + def player_inventory_dict(self) -> dict["Item", int]: """Current inventory of the player.""" return self._inv_as_dict(self.player_inventory, self.world.items) @property - def zones_inventories_dict(self) -> Dict["Zone", Dict["Item", int]]: + def zones_inventories_dict(self) -> dict["Zone", dict["Item", int]]: """Current inventories of the current zone and each zone containing item.""" zones_invs = {} for zone_slot, zone_inv in enumerate(self.zones_inventories): @@ -178,7 +178,7 @@ def reset(self) -> None: ) self._update_discoveries() - def _update_discoveries(self, action: Optional[int] = None) -> None: + def _update_discoveries(self, action: int | None = None) -> None: self.discovered_items = np.bitwise_or( self.discovered_items, self.player_inventory > 0 ) diff --git a/src/hcraft/task.py b/src/hcraft/task.py index 298147c9..d36210fd 100644 --- a/src/hcraft/task.py +++ b/src/hcraft/task.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING import numpy as np @@ -16,9 +16,9 @@ class Task: def __init__(self, name: str) -> None: self.name = name self.terminated = False - self._terminate_player_items: Optional[np.ndarray] = None - self._terminate_position: Optional[np.ndarray] = None - self._terminate_zones_items: Optional[np.ndarray] = None + self._terminate_player_items: np.ndarray | None = None + self._terminate_position: np.ndarray | None = None + self._terminate_zones_items: np.ndarray | None = None def build(self, world: "World") -> None: """Build the task operation arrays based on the given world.""" @@ -82,7 +82,7 @@ def reward(self, state: "HcraftState") -> float: class GetItemTask(AchievementTask): """Task of getting a given quantity of an item.""" - def __init__(self, item_stack: Union[Item, Stack], reward: float = 1.0): + def __init__(self, item_stack: Item | Stack, reward: float = 1.0): self.item_stack = _stack_item(item_stack) super().__init__(name=self.get_name(self.item_stack), reward=reward) @@ -137,8 +137,8 @@ class PlaceItemTask(AchievementTask): def __init__( self, - item_stack: Union[Item, Stack], - zone: Optional[Zone] = None, + item_stack: Item | Stack, + zone: Zone | None = None, reward: float = 1.0, ): item_stack = _stack_item(item_stack) @@ -173,14 +173,14 @@ def _is_terminal(self, state: "HcraftState") -> bool: return bool(_is_terminal) @staticmethod - def get_name(stack: Stack, zone: Optional[Zone]) -> str: + def get_name(stack: Stack, zone: Zone | None) -> str: """Name of the task for a given Stack and list of Zone""" quantity_str = _quantity_str(stack.quantity) zones_str = _zones_str(zone) return f"Place{quantity_str}{stack.item.name}{zones_str}" -def _stack_item(item_or_stack: Union[Item, Stack]) -> Stack: +def _stack_item(item_or_stack: Item | Stack) -> Stack: if not isinstance(item_or_stack, Stack): item_or_stack = Stack(item_or_stack) return item_or_stack @@ -190,7 +190,7 @@ def _quantity_str(quantity: int) -> str: return f" {quantity} " if quantity > 1 else " " -def _zones_str(zone: Optional[Zone]) -> str: +def _zones_str(zone: Zone | None) -> str: if zone is None: return " anywhere" return f" in {zone.name}" diff --git a/src/hcraft/transformation.py b/src/hcraft/transformation.py index f95e6344..50b96b60 100644 --- a/src/hcraft/transformation.py +++ b/src/hcraft/transformation.py @@ -139,7 +139,7 @@ """ -from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Set, Union, Any +from typing import TYPE_CHECKING, Sequence, Any from enum import Enum from dataclasses import dataclass @@ -176,13 +176,13 @@ class InventoryOwner(Enum): class Use: """Use the given item in the given inventory.""" - owner: Union[InventoryOwner, Zone] + owner: InventoryOwner | Zone """Owner of the inventory to change.""" item: Item """Item to use.""" consume: int = 0 """Amout of the item to remove from the inventory. Defaults to 0.""" - min: Optional[int] = None + min: int | None = None """Minimum amout of the item *before* the transformation to be valid. By default, min is 1 if consume is 0, else min=consume. @@ -202,7 +202,7 @@ def __post_init__(self): class Yield: """Yield the given item in the given inventory.""" - owner: Union[InventoryOwner, Zone] + owner: InventoryOwner | Zone """Owner of the inventory to change.""" item: Item """Item to yield.""" @@ -233,9 +233,9 @@ class InventoryOperation(Enum): """Effects of applying the transformation.""" -InventoryChange = Union[Use, Yield] -InventoryChanges = Dict[InventoryOperation, Union[List[Stack], Dict[Zone, List[Stack]]]] -InventoryOperations = Dict[InventoryOperation, np.ndarray] +InventoryChange = Use | Yield +InventoryChanges = dict[InventoryOperation, list[Stack] | dict[Zone | list[Stack]]] +InventoryOperations = dict[InventoryOperation, np.ndarray] class Transformation: @@ -292,10 +292,10 @@ class Transformation: def __init__( self, - name: Optional[str] = None, - destination: Optional[Zone] = None, - inventory_changes: Optional[Sequence[InventoryChange]] = None, - zone: Optional[Zone] = None, + name: str | None = None, + destination: Zone | None = None, + inventory_changes: Sequence[InventoryChange] | None = None, + zone: Zone | None = None, ) -> None: """The building blocks of every HierarchyCraft environment. @@ -310,14 +310,14 @@ def __init__( Defaults to None. """ self.destination = destination - self._destination: Optional[np.ndarray] = None + self._destination: np.ndarray | None = None self.zone = zone - self._zone: Optional[np.ndarray] = None + self._zone: np.ndarray | None = None self._changes_list = inventory_changes self.inventory_changes = _format_inventory_changes(inventory_changes) - self._inventory_operations: Dict[InventoryOwner, InventoryOperations] = {} + self._inventory_operations: dict[InventoryOwner, InventoryOperations] = {} self.name = name if name is not None else self.__repr__() @@ -361,10 +361,10 @@ def build(self, world: "World") -> None: def get_changes( self, - owner: Union[InventoryOwner, str], - operation: Union[InventoryOperation, str], + owner: InventoryOwner | str, + operation: InventoryOperation | str, default: Any = None, - ) -> Optional[Union[List[Stack], Dict[Zone, List[Stack]]]]: + ) -> list[Stack] | dict[Zone | list[Stack]] | None: """Get individual changes for a given owner and a given operation. Args: @@ -379,26 +379,26 @@ def get_changes( operations = self.inventory_changes.get(owner, {}) return operations.get(operation, default) - def production(self, owner: Union[InventoryOwner, str]) -> Set["Item"]: + def production(self, owner: InventoryOwner | str) -> set["Item"]: """Set of produced items for the given owner by this transformation.""" return self._relevant_items_changed(owner, InventoryOperation.ADD) - def consumption(self, owner: Union[InventoryOwner, str]) -> Set["Item"]: + def consumption(self, owner: InventoryOwner | str) -> set["Item"]: """Set of consumed items for the given owner by this transformation.""" return self._relevant_items_changed(owner, InventoryOperation.REMOVE) - def min_required(self, owner: Union[InventoryOwner, str]) -> Set["Item"]: + def min_required(self, owner: InventoryOwner | str) -> set["Item"]: """Set of items for which a minimum is required by this transformation for the given owner.""" return self._relevant_items_changed(owner, InventoryOperation.MIN) - def max_required(self, owner: Union[InventoryOwner, str]) -> Set["Item"]: + def max_required(self, owner: InventoryOwner | str) -> set["Item"]: """Set of items for which a maximum is required by this transformation for the given owner.""" return self._relevant_items_changed(owner, InventoryOperation.MAX) @property - def produced_zones_items(self) -> Set["Item"]: + def produced_zones_items(self) -> set["Item"]: """Set of produced zones items by this transformation.""" return ( self.production(CURRENT_ZONE) @@ -407,7 +407,7 @@ def produced_zones_items(self) -> Set["Item"]: ) @property - def consumed_zones_items(self) -> Set["Item"]: + def consumed_zones_items(self) -> set["Item"]: """Set of consumed zones items by this transformation.""" return ( self.consumption(CURRENT_ZONE) @@ -416,7 +416,7 @@ def consumed_zones_items(self) -> Set["Item"]: ) @property - def min_required_zones_items(self) -> Set["Item"]: + def min_required_zones_items(self) -> set["Item"]: """Set of zone items for which a minimum is required by this transformation.""" return ( self.min_required(CURRENT_ZONE) @@ -425,7 +425,7 @@ def min_required_zones_items(self) -> Set["Item"]: ) @property - def max_required_zones_items(self) -> Set["Item"]: + def max_required_zones_items(self) -> set["Item"]: """Set of zone items for which a maximum is required by this transformation.""" return ( self.max_required(CURRENT_ZONE) @@ -434,8 +434,8 @@ def max_required_zones_items(self) -> Set["Item"]: ) def _relevant_items_changed( - self, owner: Union[InventoryOwner, str], operation: InventoryOperation - ) -> Set["Item"]: + self, owner: InventoryOwner | str, operation: InventoryOperation + ) -> set["Item"]: added_stacks = self.get_changes(owner, operation) items = set() @@ -458,8 +458,8 @@ def _is_valid_position(self, position: np.ndarray) -> bool: def _is_valid_inventory( self, inventory: np.ndarray, - max_items: Optional[np.ndarray], - min_items: Optional[np.ndarray], + max_items: np.ndarray | None, + min_items: np.ndarray | None, ) -> bool: if max_items is not None and np.any(inventory > max_items): return False @@ -574,8 +574,8 @@ def _build_apply_operations(self): def _build_operation_array( self, - stacks: List[Stack], - world_items_list: List["Item"], + stacks: list[Stack], + world_items_list: list["Item"], default_value: int = 0, ) -> np.ndarray: operation = default_value * np.ones(len(world_items_list), dtype=np.int32) @@ -586,9 +586,9 @@ def _build_operation_array( def _build_zones_items_op( self, - stacks_per_zone: Dict[Zone, List["Stack"]], - zones: List[Zone], - zones_items: List["Item"], + stacks_per_zone: dict[Zone, list["Stack"]], + zones: list[Zone], + zones_items: list["Item"], default_value: float = 0.0, ) -> np.ndarray: operation = default_value * np.ones( @@ -635,7 +635,7 @@ def _preconditions_repr(self) -> str: preconditions_text += " " preconditions_text += owners_brackets[owner].replace(".", stacks_text) - zones_specific_ops: Dict[Zone, Dict[InventoryOperation, List[Stack]]] = {} + zones_specific_ops: dict[Zone, dict[InventoryOperation, list[Stack]]] = {} for op, zones_stacks in self.inventory_changes.get( InventoryOwner.ZONES, {} ).items(): @@ -701,7 +701,7 @@ def _effects_repr(self) -> str: effects_text += " " effects_text += owners_brackets[owner].replace(".", stacks_text) - zones_specific_ops: Dict[Zone, Dict[InventoryOperation, List[Stack]]] = {} + zones_specific_ops: dict[Zone, dict[InventoryOperation, list[Stack]]] = {} for op, zones_stacks in self.inventory_changes.get( InventoryOwner.ZONES, {} ).items(): @@ -742,7 +742,7 @@ def _update_inventory( player_inventory: np.ndarray, position: np.ndarray, zones_inventories: np.ndarray, - destination: Optional[np.ndarray], + destination: np.ndarray | None, operation_arr: np.ndarray, ) -> None: if owner is PLAYER: @@ -763,7 +763,7 @@ def _update_inventory( def _build_apply_operation_array( operations: InventoryOperations, -) -> Optional[np.ndarray]: +) -> np.ndarray | None: apply_operation = None if InventoryOperation.ADD in operations: add_op = operations[InventoryOperation.ADD] @@ -779,12 +779,12 @@ def _build_apply_operation_array( def _stacks_effects_str( - stacks: Optional[List["Stack"]], + stacks: list["Stack"] | None, prefix: str = "", suffix: str = "", stack_prefix: str = "", -) -> List[str]: - strings: List[str] = [] +) -> list[str]: + strings: list[str] = [] if not stacks: return strings strings.append(_unstacked_str(stacks, prefix, suffix, stack_prefix)) @@ -792,12 +792,12 @@ def _stacks_effects_str( def _stacks_precontions_str( - stacks: Optional[List["Stack"]], + stacks: list["Stack"] | None, prefix: str = "", suffix: str = "", symbol: str = "", -) -> List[str]: - strings: List[str] = [] +) -> list[str]: + strings: list[str] = [] if not stacks: return strings strings.append(_unstacked_condition_str(stacks, prefix, suffix, symbol)) @@ -805,7 +805,7 @@ def _stacks_precontions_str( def _unstacked_condition_str( - stacks: List["Stack"], prefix: str = "", suffix: str = "", symbol: str = "" + stacks: list["Stack"], prefix: str = "", suffix: str = "", symbol: str = "" ) -> str: items_text = ",".join( [f"{stack.item.name}{symbol}{stack.quantity}" for stack in stacks] @@ -814,16 +814,16 @@ def _unstacked_condition_str( def _unstacked_str( - stacks: List["Stack"], prefix: str = "", suffix: str = "", stack_prefix: str = "" + stacks: list["Stack"], prefix: str = "", suffix: str = "", stack_prefix: str = "" ) -> str: items_text = ",".join([f"{stack_prefix}{stack}" for stack in stacks]) return f"{prefix}{items_text}{suffix}" def _append_changes( - dict_of_changes: Dict[Union[InventoryOwner, Zone], InventoryChanges], + dict_of_changes: dict[InventoryOwner | Zone, InventoryChanges], change: InventoryChange, - zone: Optional[Zone] = None, + zone: Zone | None = None, ) -> None: owner = change.owner if zone is not None: @@ -871,9 +871,9 @@ def _append_stack(operation: InventoryOperation, stack: Stack) -> None: def _format_inventory_changes( - list_of_changes: Optional[List[InventoryChange]], -) -> Dict[InventoryOwner, InventoryChanges]: - dict_of_stacks: Dict[InventoryOwner, InventoryChanges] = {} + list_of_changes: list[InventoryChange] | None, +) -> dict[InventoryOwner, InventoryChanges]: + dict_of_stacks: dict[InventoryOwner, InventoryChanges] = {} if list_of_changes is None: return dict_of_stacks @@ -885,5 +885,5 @@ def _format_inventory_changes( return dict_of_stacks -def _items_from_stack_list(stacks: List["Stack"]) -> Set["Item"]: +def _items_from_stack_list(stacks: list["Stack"]) -> set["Item"]: return set(stack.item for stack in stacks) diff --git a/src/hcraft/world.py b/src/hcraft/world.py index 4b57d8f9..f592cbed 100644 --- a/src/hcraft/world.py +++ b/src/hcraft/world.py @@ -21,7 +21,7 @@ ```python from hcraft.elements import Item, Stack, Zone, world_from_transformations -transformations: List["Transformation"] = ... +transformations: list["Transformation"] = ... world = world_from_transformations( transformations=transformations, start_zone=Zone("Start zone"), @@ -37,7 +37,7 @@ from dataclasses import dataclass, field from functools import partial from pathlib import Path -from typing import Dict, List, Optional, Set, Tuple, Union +from typing import Set from hcraft.elements import Item, Stack, Zone from hcraft.requirements import RequirementNode, Requirements, req_node_name @@ -57,14 +57,14 @@ class World: Also contain optional start_zone, start_items and start_zones_items. """ - items: List[Item] - zones: List[Zone] - zones_items: List[Item] - transformations: List["Transformation"] = field(default_factory=list) + items: list[Item] + zones: list[Zone] + zones_items: list[Item] + transformations: list["Transformation"] = field(default_factory=list) - start_zone: Optional[Zone] = None - start_items: List[Stack] = field(default_factory=list) - start_zones_items: Dict[Zone, List[Stack]] = field(default_factory=dict) + start_zone: Zone | None = None + start_items: list[Stack] = field(default_factory=list) + start_zones_items: dict[Zone, list[Stack]] = field(default_factory=dict) resources_path: Path = field(default_factory=_default_resources_path) order_world: bool = False @@ -131,15 +131,15 @@ def slot_from_zoneitem(self, item: Item) -> int: def world_from_transformations( - transformations: List["Transformation"], - start_zone: Optional[Zone] = None, - start_items_or_stacks: Optional[List[Union[Stack, Item]]] = None, - start_zones_items_or_stack: Optional[Dict[Zone, List[Union[Stack, Item]]]] = None, + transformations: list["Transformation"], + start_zone: Zone | None = None, + start_items_or_stacks: list[Stack | Item] | None = None, + start_zones_items_or_stack: dict[Zone, list[Stack | Item]] | None = None, order_world: bool = True, ) -> World: """Reads the transformation to build the list of items, zones and zones_items composing the world.""" - start_stacks: List[Stack] = [] + start_stacks: list[Stack] = [] if start_items_or_stacks is None: start_items_or_stacks = [] for stack_or_item in start_items_or_stacks: @@ -147,7 +147,7 @@ def world_from_transformations( stack_or_item if isinstance(stack_or_item, Stack) else Stack(stack_or_item) ) - start_zones_stacks: Dict[Zone, List[Stack]] = {} + start_zones_stacks: dict[Zone, list[Stack]] = {} if start_zones_items_or_stack is None: start_zones_items_or_stack = {} for zone, zone_items_or_stacks in start_zones_items_or_stack.items(): @@ -181,10 +181,10 @@ def world_from_transformations( def _start_elements( - start_zone: Optional[Zone], - start_stacks: List[Stack], - start_zones_stacks: Dict[Zone, List[Stack]], -) -> Tuple[Set[Zone], Set[Item], Set[Item]]: + start_zone: Zone | None, + start_stacks: list[Stack], + start_zones_stacks: dict[Zone, list[Stack]], +) -> tuple[set[Zone], set[Item], Set[Item]]: zones = set() if start_zone is not None: zones.add(start_zone) @@ -199,10 +199,10 @@ def _start_elements( def _transformations_elements( transfo: "Transformation", - zones: Set[Zone], - items: Set[Item], - zones_items: Set[Item], -) -> Tuple[Set[Zone], Set[Item], Set[Item]]: + zones: set[Zone], + items: set[Item], + zones_items: set[Item], +) -> tuple[set[Zone], set[Item], Set[Item]]: if transfo.destination is not None: zones.add(transfo.destination) if transfo.zone is not None: @@ -228,22 +228,22 @@ def _transformations_elements( def _get_node_level( - requirements: Requirements, obj: Union[Item, Zone], node_type: RequirementNode -) -> Tuple[int, str]: + requirements: Requirements, obj: Item | Zone, node_type: RequirementNode +) -> tuple[int, str]: node_name = req_node_name(obj, node_type=node_type) return (requirements.graph.nodes[node_name].get("level", 1000), node_name) -def _add_items_to(stacks: Optional[List[Stack]], items_set: Set[Item]) -> None: +def _add_items_to(stacks: list[Stack] | None, items_set: set[Item]) -> None: if stacks is not None: for stack in stacks: items_set.add(stack.item) def _add_dict_items_to( - dict_of_stacks: Optional[Dict[Zone, List[Stack]]], - items_set: Set[Item], - zones_set: Set[Zone], + dict_of_stacks: dict[Zone, list[Stack]] | None, + items_set: set[Item], + zones_set: set[Zone], ) -> None: if dict_of_stacks is not None: for zone, stacks in dict_of_stacks.items(): diff --git a/tests/envs.py b/tests/envs.py index afb9e56d..c3a795c8 100644 --- a/tests/envs.py +++ b/tests/envs.py @@ -1,4 +1,3 @@ -from typing import List from hcraft.elements import Item, Zone from hcraft.env import HcraftEnv @@ -11,7 +10,7 @@ def classic_env(player=PLAYER, current_zone=CURRENT_ZONE, include_move=True): other_zone = Zone("other_zone") zones = [start_zone, other_zone] - transformations: List[Transformation] = [] + transformations: list[Transformation] = [] if include_move: move_to_other_zone = Transformation( "move_to_other_zone", diff --git a/tests/examples/minecraft/test_gym_make.py b/tests/examples/minecraft/test_gym_make.py index d529cfe2..451560d5 100644 --- a/tests/examples/minecraft/test_gym_make.py +++ b/tests/examples/minecraft/test_gym_make.py @@ -1,4 +1,4 @@ -from typing import List, Type, TypeVar +from typing import Type, TypeVar import pytest import pytest_check as check @@ -48,8 +48,8 @@ def test_all_items_gym_make(): TaskOfType = TypeVar("TaskOfType") def _task_names_of_type( - tasks: List[Task], task_type: Type[TaskOfType] - ) -> List[TaskOfType]: + tasks: list[Task], task_type: Type[TaskOfType] + ) -> list[TaskOfType]: return [task for task in tasks if isinstance(task, task_type)] check.assert_equal( diff --git a/tests/test_env.py b/tests/test_env.py index 7846c106..a7ee12ed 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import List import numpy as np import pytest @@ -27,7 +26,7 @@ def setup_method(self): self.zones, self.zones_items, ) = classic_env() - self.transformations: List[Transformation] = list( + self.transformations: list[Transformation] = list( self.named_transformations.values() ) self.env.reset() diff --git a/tests/test_purpose.py b/tests/test_purpose.py index f1d5b319..9b4a0637 100644 --- a/tests/test_purpose.py +++ b/tests/test_purpose.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, List, Tuple +from typing import Any import pytest import pytest_check as check @@ -357,7 +357,7 @@ def test_requires_achivements_shaping_go_to_zone(self): ) -def _check_get_item_tasks(items: List[Item], tasks: List[Task]): +def _check_get_item_tasks(items: list[Item], tasks: list[Task]): all_items_stacks = [Stack(item) for item in items] expected_task_names = [ GetItemTask.get_name(item_stack) for item_stack in all_items_stacks @@ -365,13 +365,13 @@ def _check_get_item_tasks(items: List[Item], tasks: List[Task]): _check_in_tasks_names(tasks, expected_task_names) -def _check_go_to_zone_tasks(zones: List[Zone], tasks: List[Task]): +def _check_go_to_zone_tasks(zones: list[Zone], tasks: list[Task]): expected_task_names = [GoToZoneTask.get_name(zone) for zone in zones] _check_in_tasks_names(tasks, expected_task_names) def _check_place_item_tasks( - items_and_zones: List[Tuple[Item, Zone]], tasks: List[Task] + items_and_zones: list[tuple[Item, Zone]], tasks: list[Task] ): stacks_and_zones = [(Stack(item), zones) for item, zones in items_and_zones] expected_task_names = [ @@ -380,7 +380,7 @@ def _check_place_item_tasks( _check_in_tasks_names(tasks, expected_task_names) -def _check_in_tasks_names(tasks: List[Task], expected_task_names: List[str]): +def _check_in_tasks_names(tasks: list[Task], expected_task_names: list[str]): task_names = [task.name for task in tasks] for task_name in expected_task_names: check.is_in(task_name, task_names)