Skip to content
Snippets Groups Projects
Commit 95500cc8 authored by Florian Schröder's avatar Florian Schröder
Browse files

Refactor 'player_config' usage and improve typing in 'cooperative_cuisine'

The 'player_config' dictionary is replaced with the PlayerConfig class to provide type-checking and improve readability. This change is applied across different files in the 'cooperative_cuisine' module. Additionally, this commit improves type hints for several variables and parameters to assist with code interpretation and potential error prevention.
parent 2bd77a93
No related branches found
No related tags found
No related merge requests found
Pipeline #48284 passed
...@@ -17,7 +17,7 @@ from collections import defaultdict ...@@ -17,7 +17,7 @@ from collections import defaultdict
from datetime import timedelta, datetime from datetime import timedelta, datetime
from pathlib import Path from pathlib import Path
from random import Random from random import Random
from typing import Literal, TypedDict, Callable from typing import Literal, TypedDict, Callable, Set
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
...@@ -29,6 +29,7 @@ from cooperative_cuisine.counter_factory import ( ...@@ -29,6 +29,7 @@ from cooperative_cuisine.counter_factory import (
) )
from cooperative_cuisine.counters import ( from cooperative_cuisine.counters import (
PlateConfig, PlateConfig,
Counter,
) )
from cooperative_cuisine.effects import EffectManager from cooperative_cuisine.effects import EffectManager
from cooperative_cuisine.hooks import ( from cooperative_cuisine.hooks import (
...@@ -101,6 +102,18 @@ class Environment: ...@@ -101,6 +102,18 @@ class Environment:
Handles player movement, collision-detection, counters, cooking processes, recipes, incoming orders, time. Handles player movement, collision-detection, counters, cooking processes, recipes, incoming orders, time.
""" """
# pdoc does not detect attributes in the tuple assignment in init
counters: list[Counter]
"""Counters of the environment."""
designated_player_positions: list[npt.NDArray[float]]
"""The positions new players can spawn based on the layout (`A` char)."""
free_positions: list[npt.NDArray[float]]
"""list of 2D points of free (no counters) positions in the environment."""
kitchen_height: int
"""Grid height of the kitchen."""
kitchen_width: int
"""Grid width of the kitchen."""
def __init__( def __init__(
self, self,
env_config: Path | str, env_config: Path | str,
...@@ -138,7 +151,7 @@ class Environment: ...@@ -138,7 +151,7 @@ class Environment:
self.players: dict[str, Player] = {} self.players: dict[str, Player] = {}
"""the player, keyed by their id/name.""" """the player, keyed by their id/name."""
self.as_files = as_files self.as_files: bool = as_files
"""Are the configs just the path to the files.""" """Are the configs just the path to the files."""
if self.as_files: if self.as_files:
with open(env_config, "r") as file: with open(env_config, "r") as file:
...@@ -152,22 +165,28 @@ class Environment: ...@@ -152,22 +165,28 @@ class Environment:
env_config, Loader=yaml.Loader env_config, Loader=yaml.Loader
) )
"""The config of the environment. All environment specific attributes is configured here.""" """The config of the environment. All environment specific attributes is configured here."""
self.environment_config["player_config"] = PlayerConfig(
self.player_view_restricted: bool = self.environment_config["player_config"][ **(
"restricted_view" self.environment_config["player_config"]
] if "player_config" in self.environment_config
else {}
)
)
self.player_view_restricted: bool = self.environment_config[
"player_config"
].restricted_view
"""If field-of-view of players is restricted in this environment.""" """If field-of-view of players is restricted in this environment."""
if self.player_view_restricted: if self.player_view_restricted:
self.player_view_angle = self.environment_config["player_config"][ self.player_view_angle: float = self.environment_config[
"view_angle" "player_config"
] ].view_angle
self.player_view_range = self.environment_config["player_config"][ self.player_view_range: float = self.environment_config[
"view_range" "player_config"
] ].view_range
self.extra_setup_functions() self.extra_setup_functions()
self.layout_config = layout_config self.layout_config: str = layout_config
"""The layout config for the environment""" """The layout config for the environment"""
# self.counter_side_length = 1 # -> this changed! is 1 now # self.counter_side_length = 1 # -> this changed! is 1 now
...@@ -176,7 +195,7 @@ class Environment: ...@@ -176,7 +195,7 @@ class Environment:
self.hook(ITEM_INFO_LOADED, item_info=item_info) self.hook(ITEM_INFO_LOADED, item_info=item_info)
if self.environment_config["orders"]["meals"]["all"]: if self.environment_config["orders"]["meals"]["all"]:
self.allowed_meal_names = set( self.allowed_meal_names: Set[str] = set(
[ [
item item
for item, info in self.item_info.items() for item, info in self.item_info.items()
...@@ -184,20 +203,20 @@ class Environment: ...@@ -184,20 +203,20 @@ class Environment:
] ]
) )
else: else:
self.allowed_meal_names = set( self.allowed_meal_names: Set[str] = set(
self.environment_config["orders"]["meals"]["list"] self.environment_config["orders"]["meals"]["list"]
) )
"""The allowed meals depend on the `environment_config.yml` configured behaviour. Either all meals that """The allowed meals depend on the `environment_config.yml` configured behaviour. Either all meals that
are possible or only a limited subset.""" are possible or only a limited subset."""
self.order_manager = OrderManager( self.order_manager: OrderManager = OrderManager(
order_config=self.environment_config["orders"], order_config=self.environment_config["orders"],
hook=self.hook, hook=self.hook,
random=self.random, random=self.random,
) )
"""The manager for the orders and score update.""" """The manager for the orders and score update."""
self.counter_factory = CounterFactory( self.counter_factory: CounterFactory = CounterFactory(
layout_chars_config=self.environment_config["layout_chars"], layout_chars_config=self.environment_config["layout_chars"],
item_info=self.item_info, item_info=self.item_info,
serving_window_additional_kwargs={ serving_window_additional_kwargs={
...@@ -233,7 +252,7 @@ class Environment: ...@@ -233,7 +252,7 @@ class Environment:
) = self.counter_factory.parse_layout_file(self.layout_config) ) = self.counter_factory.parse_layout_file(self.layout_config)
self.hook(LAYOUT_FILE_PARSED) self.hook(LAYOUT_FILE_PARSED)
self.movement = Movement( self.movement: Movement = Movement(
counter_positions=np.array([c.pos for c in self.counters]), counter_positions=np.array([c.pos for c in self.counters]),
player_config=self.environment_config["player_config"], player_config=self.environment_config["player_config"],
world_borders=np.array( world_borders=np.array(
...@@ -244,7 +263,7 @@ class Environment: ...@@ -244,7 +263,7 @@ class Environment:
) )
"""Does the movement of players in each step.""" """Does the movement of players in each step."""
self.progressing_counters = [] self.progressing_counters: list[Counter] = []
"""Counters that needs to be called in the step function via the `progress` method.""" """Counters that needs to be called in the step function via the `progress` method."""
self.effect_manager: dict[ self.effect_manager: dict[
...@@ -260,7 +279,7 @@ class Environment: ...@@ -260,7 +279,7 @@ class Environment:
else True else True
) )
self.recipe_validation = Validation( self.recipe_validation: Validation = Validation(
meals=[m for m in self.item_info.values() if m.type == ItemType.Meal] meals=[m for m in self.item_info.values() if m.type == ItemType.Meal]
if self.environment_config["orders"]["meals"]["all"] if self.environment_config["orders"]["meals"]["all"]
else [ else [
...@@ -280,7 +299,7 @@ class Environment: ...@@ -280,7 +299,7 @@ class Environment:
available_meals = {meal: self.item_info[meal] for meal in meals_to_be_ordered} available_meals = {meal: self.item_info[meal] for meal in meals_to_be_ordered}
self.order_manager.set_available_meals(available_meals) self.order_manager.set_available_meals(available_meals)
self.order_manager.create_init_orders(self.env_time) self.order_manager.create_init_orders(self.env_time)
self.start_time = self.env_time self.start_time: datetime = self.env_time
"""The relative env time when it started.""" """The relative env time when it started."""
self.env_time_end = self.env_time + timedelta( self.env_time_end = self.env_time + timedelta(
seconds=self.environment_config["game"]["time_limit_seconds"] seconds=self.environment_config["game"]["time_limit_seconds"]
...@@ -299,6 +318,11 @@ class Environment: ...@@ -299,6 +318,11 @@ class Environment:
env_start_time_worldtime=datetime.now(), env_start_time_worldtime=datetime.now(),
) )
@property
def game_ended(self) -> bool:
"""Whether the game is over or not based on the calculated `Environment.env_time_end`"""
return self.env_time >= self.env_time_end
def overwrite_counters(self, counters): def overwrite_counters(self, counters):
"""Resets counters. """Resets counters.
...@@ -340,12 +364,7 @@ class Environment: ...@@ -340,12 +364,7 @@ class Environment:
for manager in self.effect_manager.values(): for manager in self.effect_manager.values():
manager.set_counters(counters) manager.set_counters(counters)
@property def get_env_time(self) -> datetime:
def game_ended(self) -> bool:
"""Whether the game is over or not based on the calculated `Environment.env_time_end`"""
return self.env_time >= self.env_time_end
def get_env_time(self):
"""the internal time of the environment. An environment starts always with the time from `create_init_env_time`. """the internal time of the environment. An environment starts always with the time from `create_init_env_time`.
Utility method to pass a reference to the serving window.""" Utility method to pass a reference to the serving window."""
...@@ -405,19 +424,16 @@ class Environment: ...@@ -405,19 +424,16 @@ class Environment:
Args: Args:
player_name: The id/name of the player to reference actions and in the state. player_name: The id/name of the player to reference actions and in the state.
pos: The optional init position of the player. pos: The optional init position of the player.
Raises:
ValueError: if the player_name already exists.
""" """
if player_name in self.players: if player_name in self.players:
raise ValueError(f"Player {player_name} already exists.") raise ValueError(f"Player {player_name} already exists.")
log.debug(f"Add player {player_name} to the game") log.debug(f"Add player {player_name} to the game")
player = Player( player = Player(
player_name, player_name,
player_config=PlayerConfig( player_config=self.environment_config["player_config"],
**(
self.environment_config["player_config"]
if "player_config" in self.environment_config
else {}
)
),
hook=self.hook, hook=self.hook,
pos=pos, pos=pos,
) )
...@@ -464,7 +480,9 @@ class Environment: ...@@ -464,7 +480,9 @@ class Environment:
effect_manager.progress(passed_time=passed_time, now=self.env_time) effect_manager.progress(passed_time=passed_time, now=self.env_time)
self.hook(POST_STEP, passed_time=passed_time) self.hook(POST_STEP, passed_time=passed_time)
def get_state(self, player_id: str = None, additional_key_values: dict = None): def get_state(
self, player_id: str = None, additional_key_values: dict = None
) -> dict:
"""Get the current state of the game environment. The state here is accessible by the current python objects. """Get the current state of the game environment. The state here is accessible by the current python objects.
Args: Args:
...@@ -506,8 +524,7 @@ class Environment: ...@@ -506,8 +524,7 @@ class Environment:
"info_msg": [ "info_msg": [
(msg["msg"], msg["level"]) (msg["msg"], msg["level"])
for msg in self.info_msgs_per_player[player_id] for msg in self.info_msgs_per_player[player_id]
if msg["start_time"] < self.env_time if msg["start_time"] < self.env_time < msg["end_time"]
and msg["end_time"] > self.env_time
], ],
**(additional_key_values if additional_key_values else {}), **(additional_key_values if additional_key_values else {}),
} }
......
...@@ -12,7 +12,7 @@ from scipy.spatial import distance_matrix ...@@ -12,7 +12,7 @@ from scipy.spatial import distance_matrix
from cooperative_cuisine.counters import Counter from cooperative_cuisine.counters import Counter
from cooperative_cuisine.hooks import Hooks, PLAYERS_COLLIDE from cooperative_cuisine.hooks import Hooks, PLAYERS_COLLIDE
from cooperative_cuisine.player import Player from cooperative_cuisine.player import Player, PlayerConfig
class Movement: class Movement:
...@@ -23,7 +23,13 @@ class Movement: ...@@ -23,7 +23,13 @@ class Movement:
world_borders_upper = None world_borders_upper = None
"""World borders upper bounds.""" """World borders upper bounds."""
def __init__(self, counter_positions, player_config, world_borders, hook: Hooks): def __init__(
self,
counter_positions: npt.NDArray[float],
player_config: PlayerConfig,
world_borders: npt.NDArray[float],
hook: Hooks,
):
"""Constructor of Movement. """Constructor of Movement.
Args: Args:
...@@ -34,13 +40,11 @@ class Movement: ...@@ -34,13 +40,11 @@ class Movement:
""" """
self.counter_positions: npt.NDArray[float] = counter_positions self.counter_positions: npt.NDArray[float] = counter_positions
"""Positions of all counters in an environment. Needs to be updated if the counters position changes.""" """Positions of all counters in an environment. Needs to be updated if the counters position changes."""
self.player_radius: float = player_config["radius"] self.player_radius: float = player_config.radius
"""The radius of a player (indicating its size and collision sphere). Relative to one grid cell, e.g., `0.4`.""" """The radius of a player (indicating its size and collision sphere). Relative to one grid cell, e.g., `0.4`."""
self.player_interaction_range: float = player_config["interaction_range"] self.player_interaction_range: float = player_config.interaction_range
"""The range of how far a player can interact with the closest counter.""" """The range of how far a player can interact with the closest counter."""
self.player_movement_speed: float | int = player_config[ self.player_movement_speed: float | int = player_config.speed_units_per_seconds
"speed_units_per_seconds"
]
"""How many grid cells a player can move in a second.""" """How many grid cells a player can move in a second."""
self.world_borders: npt.NDArray[float] = world_borders self.world_borders: npt.NDArray[float] = world_borders
"""The world border arrays. For easier numpy comparison with player position. The outer dimension needs to be """The world border arrays. For easier numpy comparison with player position. The outer dimension needs to be
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment