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
from datetime import timedelta, datetime
from pathlib import Path
from random import Random
from typing import Literal, TypedDict, Callable
from typing import Literal, TypedDict, Callable, Set
import numpy as np
import numpy.typing as npt
......@@ -29,6 +29,7 @@ from cooperative_cuisine.counter_factory import (
)
from cooperative_cuisine.counters import (
PlateConfig,
Counter,
)
from cooperative_cuisine.effects import EffectManager
from cooperative_cuisine.hooks import (
......@@ -101,6 +102,18 @@ class Environment:
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__(
self,
env_config: Path | str,
......@@ -138,7 +151,7 @@ class Environment:
self.players: dict[str, Player] = {}
"""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."""
if self.as_files:
with open(env_config, "r") as file:
......@@ -152,22 +165,28 @@ class Environment:
env_config, Loader=yaml.Loader
)
"""The config of the environment. All environment specific attributes is configured here."""
self.player_view_restricted: bool = self.environment_config["player_config"][
"restricted_view"
]
self.environment_config["player_config"] = PlayerConfig(
**(
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 self.player_view_restricted:
self.player_view_angle = self.environment_config["player_config"][
"view_angle"
]
self.player_view_range = self.environment_config["player_config"][
"view_range"
]
self.player_view_angle: float = self.environment_config[
"player_config"
].view_angle
self.player_view_range: float = self.environment_config[
"player_config"
].view_range
self.extra_setup_functions()
self.layout_config = layout_config
self.layout_config: str = layout_config
"""The layout config for the environment"""
# self.counter_side_length = 1 # -> this changed! is 1 now
......@@ -176,7 +195,7 @@ class Environment:
self.hook(ITEM_INFO_LOADED, item_info=item_info)
if self.environment_config["orders"]["meals"]["all"]:
self.allowed_meal_names = set(
self.allowed_meal_names: Set[str] = set(
[
item
for item, info in self.item_info.items()
......@@ -184,20 +203,20 @@ class Environment:
]
)
else:
self.allowed_meal_names = set(
self.allowed_meal_names: Set[str] = set(
self.environment_config["orders"]["meals"]["list"]
)
"""The allowed meals depend on the `environment_config.yml` configured behaviour. Either all meals that
are possible or only a limited subset."""
self.order_manager = OrderManager(
self.order_manager: OrderManager = OrderManager(
order_config=self.environment_config["orders"],
hook=self.hook,
random=self.random,
)
"""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"],
item_info=self.item_info,
serving_window_additional_kwargs={
......@@ -233,7 +252,7 @@ class Environment:
) = self.counter_factory.parse_layout_file(self.layout_config)
self.hook(LAYOUT_FILE_PARSED)
self.movement = Movement(
self.movement: Movement = Movement(
counter_positions=np.array([c.pos for c in self.counters]),
player_config=self.environment_config["player_config"],
world_borders=np.array(
......@@ -244,7 +263,7 @@ class Environment:
)
"""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."""
self.effect_manager: dict[
......@@ -260,7 +279,7 @@ class Environment:
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]
if self.environment_config["orders"]["meals"]["all"]
else [
......@@ -280,7 +299,7 @@ class Environment:
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.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."""
self.env_time_end = self.env_time + timedelta(
seconds=self.environment_config["game"]["time_limit_seconds"]
......@@ -299,6 +318,11 @@ class Environment:
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):
"""Resets counters.
......@@ -340,12 +364,7 @@ class Environment:
for manager in self.effect_manager.values():
manager.set_counters(counters)
@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 get_env_time(self):
def get_env_time(self) -> datetime:
"""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."""
......@@ -405,19 +424,16 @@ class Environment:
Args:
player_name: The id/name of the player to reference actions and in the state.
pos: The optional init position of the player.
Raises:
ValueError: if the player_name already exists.
"""
if player_name in self.players:
raise ValueError(f"Player {player_name} already exists.")
log.debug(f"Add player {player_name} to the game")
player = Player(
player_name,
player_config=PlayerConfig(
**(
self.environment_config["player_config"]
if "player_config" in self.environment_config
else {}
)
),
player_config=self.environment_config["player_config"],
hook=self.hook,
pos=pos,
)
......@@ -464,7 +480,9 @@ class Environment:
effect_manager.progress(passed_time=passed_time, now=self.env_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.
Args:
......@@ -506,8 +524,7 @@ class Environment:
"info_msg": [
(msg["msg"], msg["level"])
for msg in self.info_msgs_per_player[player_id]
if msg["start_time"] < self.env_time
and msg["end_time"] > self.env_time
if msg["start_time"] < self.env_time < msg["end_time"]
],
**(additional_key_values if additional_key_values else {}),
}
......
......@@ -12,7 +12,7 @@ from scipy.spatial import distance_matrix
from cooperative_cuisine.counters import Counter
from cooperative_cuisine.hooks import Hooks, PLAYERS_COLLIDE
from cooperative_cuisine.player import Player
from cooperative_cuisine.player import Player, PlayerConfig
class Movement:
......@@ -23,7 +23,13 @@ class Movement:
world_borders_upper = None
"""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.
Args:
......@@ -34,13 +40,11 @@ class Movement:
"""
self.counter_positions: npt.NDArray[float] = counter_positions
"""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`."""
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."""
self.player_movement_speed: float | int = player_config[
"speed_units_per_seconds"
]
self.player_movement_speed: float | int = player_config.speed_units_per_seconds
"""How many grid cells a player can move in a second."""
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
......
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