diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml index 2d56c9e99729ff1b41c3ecb99960cdc05a9664e1..d6a51ea95225f77ae00cb56e49a2d4eda40c95e2 100644 --- a/cooperative_cuisine/configs/environment_config.yaml +++ b/cooperative_cuisine/configs/environment_config.yaml @@ -180,6 +180,7 @@ hook_callbacks: - players_collide - post_plate_dispenser_pick_up - post_plate_dispenser_drop_off + - drop_off_on_cooking_equipment_plate_dispenser - on_item_transition - progress_started - progress_finished diff --git a/cooperative_cuisine/configs/human_readable_print_templates.yaml b/cooperative_cuisine/configs/human_readable_print_templates.yaml index cb9c47e9d634a2a320ec0456858e58a444e021fb..e2cf2e2e1813ad48dc220789363a3716b48dbb15 100644 --- a/cooperative_cuisine/configs/human_readable_print_templates.yaml +++ b/cooperative_cuisine/configs/human_readable_print_templates.yaml @@ -21,6 +21,7 @@ fire_spreading: "A fire spread to target." drop_off_on_cooking_equipment: "Player $player put $item in/on $equipment at $counter." post_plate_dispenser_pick_up: "Player $player picked up $returned_item from $counter." post_plate_dispenser_drop_off: "Player $player dropped $item on $counter." +drop_off_on_cooking_equipment_plate_dispenser: "Player $player dropped $item on $counter." on_item_transition: "$item became $result." progress_started: "Item $item started progressing." progress_finished: "Item $item finished its progress." diff --git a/cooperative_cuisine/counters.py b/cooperative_cuisine/counters.py index bf97c26b5886f738d122c282e8aa57cbcfe3d113..97dd339359c6887ffad601782084813cae5eaefe 100644 --- a/cooperative_cuisine/counters.py +++ b/cooperative_cuisine/counters.py @@ -69,6 +69,7 @@ from cooperative_cuisine.hooks import ( PRE_PLATE_DISPENSER_PICK_UP, POST_PLATE_DISPENSER_PICK_UP, DISPENSER_ITEM_RETURNED, + DROP_OFF_ON_COOKING_EQUIPMENT_PLATE_DISPENSER, ) from cooperative_cuisine.state_representation import CounterState @@ -235,14 +236,16 @@ class Counter: item=item, ) elif self.occupied_by.can_combine(item): + return_this = self.occupied_by.combine(item) self.hook( DROP_OFF_ON_COOKING_EQUIPMENT, item=item, equipment=self.occupied_by, counter=self, player=player, + return_this=return_this, ) - return self.occupied_by.combine(item) + return return_this return None @@ -606,6 +609,7 @@ class PlateDispenser(Counter): self.hook( PRE_PLATE_DISPENSER_PICK_UP, counter=self, + on_hands=on_hands, player=player, ) if self.occupied_by: @@ -614,6 +618,7 @@ class PlateDispenser(Counter): POST_PLATE_DISPENSER_PICK_UP, counter=self, player=player, + on_hands=on_hands, returned_item=returned_item, ) return returned_item @@ -628,6 +633,7 @@ class PlateDispenser(Counter): player=player, item=item, ) + # unnecessary hooks? if not self.occupied_by: self.occupied_by.append(item) self.hook( @@ -639,10 +645,12 @@ class PlateDispenser(Counter): elif self.occupied_by[-1].can_combine(item): returned_item = self.occupied_by[-1].combine(item) self.hook( - POST_PLATE_DISPENSER_DROP_OFF, + DROP_OFF_ON_COOKING_EQUIPMENT_PLATE_DISPENSER, counter=self, player=player, - item=returned_item, + item=item, + equipment=self.occupied_by[-1], + return_this=returned_item, ) return returned_item return None diff --git a/cooperative_cuisine/environment.py b/cooperative_cuisine/environment.py index 25eff9e6734879d45dfad4ad6359c2b75fc1826f..f4c087fa1a55b21efc2c558b3bd9c4bddacce0f2 100644 --- a/cooperative_cuisine/environment.py +++ b/cooperative_cuisine/environment.py @@ -193,7 +193,9 @@ class Environment: self.item_info: dict[str, ItemInfo] = self.load_item_info(item_info) """The loaded item info dict. Keys are the item names.""" - self.hook(ITEM_INFO_LOADED, item_info=item_info) + self.hook( + ITEM_INFO_LOADED, item_info=item_info, parsed_item_info=self.item_info + ) if self.environment_config["orders"]["meals"]["all"]: self.allowed_meal_names: Set[str] = set( @@ -371,7 +373,7 @@ class Environment: Utility method to pass a reference to the serving window.""" return self.env_time - def load_item_info(self, item_info) -> dict[str, ItemInfo]: + def load_item_info(self, item_info: str) -> dict[str, ItemInfo]: """Load `item_info.yml`, create ItemInfo classes and replace equipment strings with item infos.""" self.hook(ITEM_INFO_CONFIG, item_info_config=item_info) item_lookup = yaml.safe_load(item_info) @@ -554,9 +556,9 @@ class Environment: def reset_env_time(self): """Reset the env time to the initial time, defined by `create_init_env_time`.""" - self.hook(PRE_RESET_ENV_TIME) + self.hook(PRE_RESET_ENV_TIME, env_time=self.env_time) self.env_time = create_init_env_time() - self.hook(POST_RESET_ENV_TIME) + self.hook(POST_RESET_ENV_TIME, env_time=self.env_time) log.debug(f"Reset env time to {self.env_time}") def register_callback_for_hook(self, hook_ref: str | list[str], callback: Callable): diff --git a/cooperative_cuisine/hooks.py b/cooperative_cuisine/hooks.py index c541562b25e7018c8442e3a0b562af5b8ae40b99..3f7c037696a6bb8443b42f213bf8e1378f5af473 100644 --- a/cooperative_cuisine/hooks.py +++ b/cooperative_cuisine/hooks.py @@ -21,108 +21,495 @@ from typing import Callable, Any, TYPE_CHECKING, Type if TYPE_CHECKING: from cooperative_cuisine.environment import Environment +# --- environment.py --- ITEM_INFO_LOADED = "item_info_load" -"""Called after the item info is loaded and stored in the env attribute `item_info`. The kwargs are the passed config -(`item_info`) to the environment from which it was loaded and if it is a file path or the config string (`as_files`)""" +"""Called after the item info is loaded and stored in the env attribute `item_info`. + +Args: + item_info (str): the string content of the item info config file. + parsed_item_info (dict[str, ItemInfo]): the parsed item info. +""" LAYOUT_FILE_PARSED = "layout_file_parsed" """After the layout file was parsed. No additional kwargs. Everything is stored in the env.""" ITEM_INFO_CONFIG = "item_info_config" +"""Called before the item info is parsed. + +Args: + item_info_config (str): the string content of the item info config file. +""" ENV_INITIALIZED = "env_initialized" -"""At the end of the __init__ method. No additional kwargs. Everything is stored in the env.""" +"""At the end of the __init__ method. + +Args: + environment_config (str): the str content of the environment config file. + layout_config (str): the str content of the layout config file. + seed (int): the random seed. + env_start_time_worldtime (datetime): the "real" world time when the hook was called. +""" PRE_PERFORM_ACTION = "pre_perform_action" -"""Before an action is performed / entered into the environment. `action` kwarg with the entered action.""" +"""Before an action is performed / entered into the environment. `action` kwarg with the entered action. + +Args: + action (Action): the action that will be performed. +""" POST_PERFORM_ACTION = "post_perform_action" -"""After an action is performed / entered into the environment. `action` kwarg with the entered action.""" +"""After an action is performed / entered into the environment. `action` kwarg with the entered action. + +Args: + action (Action): the action that was performed. +""" # TODO Pre and Post Perform Movement PLAYER_ADDED = "player_added" -"""Called after a player has been added. Kwargs: `player` and `pos`.""" +"""Called after a player has been added. Kwargs: `player` and `pos`. + +Args: + player (Player): the new player object that was created. + pos (npt.NDArray[float]): the 2d position of the player. +""" GAME_ENDED_STEP = "game_ended_step" +"""When the step function is called but the game ended already. No kwargs.""" PRE_STATE = "pre_state" +"""Called before the state is calculated for a player. + +Args: + player_id (str): The id of the player for which the state is calculated. +""" PRE_STEP = "pre_step" +"""When the step function is called before the execution happens. Currently not active (Optimization). + +Args: + passed_time (timedelta): the time elapsed since the last step call. +""" + + POST_STEP = "post_step" +"""When the step function is called after the execution. + +Args: + passed_time (timedelta): the time elapsed since the last step call. +""" STATE_DICT = "state_dict" +"""When the dictionary (containing the state) is generated. + + Args: + state (dict): the current state for a player. Should follow the `StateRepresentation`. + player_id (str): the id of the player. + + """ + JSON_STATE = "json_state" +"""When the json string was generated for a state. + +Args: + json_data (str): the json string containing the state. + player_id (str): the id of the player. +""" PRE_RESET_ENV_TIME = "pre_reset_env_time" +"""Before the time of the environment is reset. + +Args: + env_time (datetime): the env time before the reset. +""" POST_RESET_ENV_TIME = "post_reset_env_time" +"""After the time of the environment is reset. + +Args: + env_time (datetime): the env time after the reset. +""" -# ----- +# --- counters.py --- PRE_COUNTER_PICK_UP = "pre_counter_pick_up" +"""Before the pick up on a counter is executed. + +Args: + counter (Counter): the counter from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + player (str): player id. +""" + POST_COUNTER_PICK_UP = "post_counter_pick_up" -PRE_COUNTER_DROP_OFF = "pre_counter_drop_off" -POST_COUNTER_DROP_OFF = "post_counter_drop_off" +"""After the pick up on a counter is executed. + +Args: + counter (Counter): the counter from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + return_this (Item | None): what will be put on the player (holding) + player (str): player id. +""" PRE_DISPENSER_PICK_UP = "pre_dispenser_pick_up" +"""Before the pick up on a dispenser happens. `PRE_COUNTER_PICK_UP` is not called. + +Args: + counter (Counter): the counter(dispenser) from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + player (str): player id. +""" + POST_DISPENSER_PICK_UP = "post_dispenser_pick_up" +"""After the new item on a dispenser is generated when a pick up happens. -DROP_OFF_ON_COOKING_EQUIPMENT = "drop_off_on_cooking_equipment" +Args: + counter (Counter): the counter/dispenser from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + return_this (Item | None): what will be put on the player (holding) - the new item / the item that was on the counter. + player (str): player id. +""" PRE_PLATE_DISPENSER_PICK_UP = "pre_plate_dispenser_pick_up" +"""Before the pick up on a plate dispenser happens. `PRE_COUNTER_PICK_UP` and `PRE_DISPENSER_PICK_UP` is not called. + +Args: + counter (Counter): the plate dispenser from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + player (str): player id. +""" + + POST_PLATE_DISPENSER_PICK_UP = "post_plate_dispenser_pick_up" +"""After the pick up from a plate dispenser is executed. + +Args: + counter (Counter): the counter from which to pick up. + on_hands (bool): if the picked up item is put on the free player hands (True) or on a cooking equipment (False). + return_this (Item | None): what will be put on the player (holding) + player (str): player id. +""" + +PRE_COUNTER_DROP_OFF = "pre_counter_drop_off" +"""Before the drop off on a counter is executed. + + +Args: + item (Item): the item to drop on the counter. + equipment (Item | None | deque[Item]): what is currently on the counter. + counter (Counter): the counter from which to drop off. + player (str): player id. +""" + +POST_COUNTER_DROP_OFF = "post_counter_drop_off" +"""Drop off on a free counter (nothing on it). + +Args: + counter (Counter): the counter from which to drop off. + player (str): player id. + item (Item): the item to drop on the counter. +""" + +DROP_OFF_ON_COOKING_EQUIPMENT = "drop_off_on_cooking_equipment" +"""When drop off on a counter with a cooking equipment is on it. Before the combining happens. + +Args: + item (Item): the item to drop on the counter. + equipment (Item | None | deque[Item]): what is currently on the counter. + counter (Counter): the counter from which to drop off. + player (str): player id. + return_this (Item | None): what will be put on the player (holding) +""" + + PRE_PLATE_DISPENSER_DROP_OFF = "pre_plate_dispenser_drop_off" +"""Before something is dropped on a plate dispenser. + +Args: + counter (Counter): the plate dispenser from which to drop off. + player (str): player id. + item (Item): the item to drop on the counter. +""" + POST_PLATE_DISPENSER_DROP_OFF = "post_plate_dispenser_drop_off" +"""Something is dropped on a free plate dispenser. + +Args: + counter (Counter): the counter from which to drop off. + player (str): player id. + item (Item): the item to drop on the counter. +""" + + +DROP_OFF_ON_COOKING_EQUIPMENT_PLATE_DISPENSER = ( + "drop_off_on_cooking_equipment_plate_dispenser" +) +"""After something is dropped on a plate dispenser and is combined with the top plate. + +Args: + counter (Counter): the counter from which to drop off. + player (str): player id. + item (Item): the item to drop on the counter. + return_this (Item | None): what will be put on the player (holding) + equipment (Item | None | deque[Item]): the cooking equipment that combined the items. +""" DISPENSER_ITEM_RETURNED = "dispenser_item_returned" +"""Undo the pickup on a dispenser. (Drop off action.) + +Args: + counter (Counter): the dispenser. + player (str): player id. + item (Item): the item that is returned. +""" CUTTING_BOARD_PROGRESS = "cutting_board_progress" +"""Valid cutting board interaction step. + +Args: + counter (Counter): the cutting board which is used to chop the ingredient. + player (str): player id. + percent (float): how much percent is added in the progress call. + passed_time (timedelta): passed time since the last step call in the environment- +""" + CUTTING_BOARD_100 = "cutting_board_100" +"""Chopping finished on a cutting board. +Args: + counter (Counter): the cutting board. + player (str): player id. +""" + +# --- items.py --- PROGRESS_STARTED = "progress_started" +"""Progress on a cooking equipment is started. + +Args: + item (CookingEquipment): the cooking equipment that does the progress. +""" PROGRESS_FINISHED = "progress_finished" +"""Progress on a cooking equipment is finished. (Does not include fire.) + +Args: + item (CookingEquipment): the cooking equipment that did the progress. +""" + PRE_SERVING = "pre_serving" +"""Called if player wants to drop off on `ServingWindow`. + +Args: + counter (ServingWindow): the serving window the player wants to serve on. + item (Item): the item to serve (on the player's hand). + env_time (datetime): current env time. + player (str): the player id. +""" POST_SERVING = "post_serving" +"""Serving was successful. + +Args: + counter (ServingWindow): the serving window on which the item was served on. + item (Item): the item that was served. (Plate) + env_time (datetime): current env time. + player (str): the player id. +""" NO_SERVING = "no_serving" +"""Serving was not successful. (Rejected from the order manager) + +Args: + counter (ServingWindow): the serving window the player wanted to serve on. + item (Item): the item that cannot be serves (now still on the player's hand). + env_time (datetime): current env time. + player (str): the player id. +""" PLATE_OUT_OF_KITCHEN_TIME = "plate_out_of_kitchen_time" +"""A plate is out of the kitchen (after successful serving). + +Args: + time_plate_to_add (datetime): the time the plate will arrive back in the kitchen +""" DIRTY_PLATE_ARRIVES = "dirty_plate_arrives" +"""A plate arrived at the kitchen (some time after serving) + +Args: + counter (Counter): +""" TRASHCAN_USAGE = "trashcan_usage" +"""Successful usage of the trashcan. + +Args: + counter (Trashcan): the trashcan used. + item (Item): the item the player holding. (If it is a `CookingEquipmeent` only content_list is removed). + player (str): the player id. +""" ADDED_PLATE_TO_SINK = "added_plate_to_sink" +"""Dirty Plate dropped on the sink. + +Args: + counter (Sink): the sink, target of the drop off. + item (Plate): the dirty plate. + player (str): the player id. +""" DROP_ON_SINK_ADDON = "drop_on_sink_addon" +"""Something is dropped on the sink addon and combined with the top plate. + +Args: + counter (SinkAddon): the target counter of the drop off. + item (Item): the item which is combined with the top plate. + player (str): the player id. +""" PICK_UP_FROM_SINK_ADDON = "pick_up_from_sink_addon" +"""Player picks up the top plate. + +Args: + player (str): the player id. + occupied_by (Plate): the top plate that is picked up. + counter (SinkAddon): the counter class from it picked up. +""" PLATE_CLEANED = "plate_cleaned" +"""The player finished cleaning a plate. + +Plate is transferred to the SinkAddon afterwards. + +Args: + counter (Sink): The sink on which the plate was cleaned. + player (str): the player id. +""" + +# --- items.py --- + +ON_ITEM_TRANSITION = "on_item_transition" +"""A new Effect appears (Fire starts). + +Args: + item (CookingEquipment): the pot, pan, ... on which the fire starts. + result (Effect): the fire effect that is now active on the equipment. + seconds (float/int): how long it took to create the fire. +""" +CONTENT_READY = "content_ready" +"""A meal is ready on a cooking equipment. + +Args: + result (str): Name of the meal. + before (CookingEquipment): the cooking equipment. +""" + +# --- orders.py --- SERVE_NOT_ORDERED_MEAL = "serve_not_ordered_meal" +"""If a meal does not match to an order but `serve_not_ordered_meals`is active. + +Args: + meal (Item): the meal that is served. + meal_name (str): equivalent to meal.name. The name of the meal. +""" SERVE_WITHOUT_PLATE = "serve_without_plate" +"""Somehow tried to serve without a plate. Will not be served. + +Args: + item (Item): The item that the player has in his hands. +""" -ORDER_DURATION_SAMPLE = "order_duration_sample" COMPLETED_ORDER = "completed_order" +"""A meal was served that completed an order. + +Args: + order (Order): the order that was fulfilled. + meal (Item): The meal that was served. + relative_order_time (timedelta): the time that the player needed to fulfill the order. + meal_name (str): name of the meal. +""" INIT_ORDERS = "init_orders" +"""The initial orders were generated. + +Args: + init_orders (list[Order]): the init orders. +""" NEW_ORDERS = "new_orders" +"""New orders (not init orders) were generated. + +Args: + new_orders (list[Order]): the new orders. +""" ORDER_EXPIRED = "order_expired" +"""An order expired (took too long). + +Args: + order (Order): the order that expired. +""" + +# --- environment.py --- but player related. ACTION_ON_NOT_REACHABLE_COUNTER = "action_on_not_reachable_counter" +"""Player wants to interact or pick/drop but no counter is in reach. + +Args: + action (Action): the action with the player id. + counter (Counter): closest but not reachable counter. +""" ACTION_PUT = "action_put" +"""Player does the put (pick or drop) action + +Args: + action (Action): the action with the player id. + counter (Counter): on which the put action is performed. +""" ACTION_INTERACT_START = "action_interact_start" +"""Player starts interacting on a counter. + +Args: + action (Action): the action with the player id + counter (Counter): on which the player starts the interaction. +""" + +# --- effects.py --- ITEM_BURNED = "item_burned" # MISSING +"""NOT IMPLEMENTED""" NEW_FIRE = "new_fire" +"""New fire from cooking equipment (Does not include spreading). + +Args: + target (Counter|Item): on which the fire was created. +""" FIRE_SPREADING = "fire_spreading" +"""Fire spreads on other counter/items after some time. + +Args: + target (Counter|Item): on which the fire is spreading. +""" + +# --- movement.py --- PLAYERS_COLLIDE = "players_collide" +"""Every frame player collides. + +Args: + collisions (npt.NDArray[bool]): the players which collide. +""" + +# --- players.py --- PLAYER_START_INTERACT = "player_start_interaction" +"""Same as `ACTION_INTERACT_START` but in the player.py + +Args: + player (str): the player id. + counter (Counter): the last interacted counter. +""" PLAYER_END_INTERACT = "player_end_interact" +"""The interaction with a counter stopped. Either stopped by the player through button press or move away. -ON_ITEM_TRANSITION = "on_item_transition" -CONTENT_READY = "content_ready" +Args: + player (str): the player id. + counter (Counter): the last interacted counter. +""" class Hooks: diff --git a/cooperative_cuisine/orders.py b/cooperative_cuisine/orders.py index 33c841f71c12a69fec1c8c656bed01c3ce7a30cf..95b994c558ecc6e71a5f11f926f57326669fd393 100644 --- a/cooperative_cuisine/orders.py +++ b/cooperative_cuisine/orders.py @@ -51,7 +51,6 @@ from cooperative_cuisine.hooks import ( COMPLETED_ORDER, INIT_ORDERS, NEW_ORDERS, - ORDER_DURATION_SAMPLE, ORDER_EXPIRED, ) from cooperative_cuisine.items import Item, Plate, ItemInfo @@ -248,7 +247,7 @@ class OrderManager: def create_init_orders(self, env_time): """Create the initial orders in an environment.""" init_orders = self.order_gen.init_orders(env_time) - self.hook(INIT_ORDERS) + self.hook(INIT_ORDERS, init_orders=init_orders) self.open_orders.extend(init_orders) # self.update_next_relevant_time() @@ -495,10 +494,6 @@ class RandomOrderGeneration(OrderGeneration): **self.kwargs.order_duration_random_func["kwargs"] ) duration = timedelta(seconds=seconds) - self.hook( - ORDER_DURATION_SAMPLE, - duration=duration, - ) log.info(f"Create order for meal {meal} with duration {duration}") orders.append( Order( diff --git a/cooperative_cuisine/pygame_2d_vis/drawing.py b/cooperative_cuisine/pygame_2d_vis/drawing.py index 899f0559d2e4da9cdd53fdc5ba6725f6f19ee36a..2cc1f6f48309fc2e8ec0db5206d667d1d4e737ac 100644 --- a/cooperative_cuisine/pygame_2d_vis/drawing.py +++ b/cooperative_cuisine/pygame_2d_vis/drawing.py @@ -1021,12 +1021,11 @@ def generate_recipe_images(config: dict, folder_path: str | Path): def main(args): - """ - - Runs the Cooperative Cuisine Image Generation process. + """Runs the Cooperative Cuisine Image Generation process. - This method takes command line arguments to specify the state file, visualization configuration file, and output file for the generated image. It then reads the visualization configuration - * file and state file, and calls the 'save_screenshot' and 'generate_recipe_images' methods to generate the image. + This method takes command line arguments to specify the state file, visualization configuration file, and output + file for the generated image. It then reads the visualization configuration file and state file, and calls the + 'save_screenshot' and 'generate_recipe_images' methods to generate the image. Args: -s, --state: A command line argument of type `argparse.FileType("r", encoding="UTF-8")`. Specifies the state file to use for image generation. If not provided, the default value is 'ROOT_DIR / "pygame_2d_vis" / "sample_state.json"'. diff --git a/cooperative_cuisine/reinforcement_learning/pearl_test.py b/cooperative_cuisine/reinforcement_learning/tryout_pearl.py similarity index 100% rename from cooperative_cuisine/reinforcement_learning/pearl_test.py rename to cooperative_cuisine/reinforcement_learning/tryout_pearl.py diff --git a/tests/test_game_server.py b/tests/test_game_server.py index 84271ad77196e85aef512ce9994e0d8926130a0e..527cf31bc1a3cda61e3c7e6d2c1e223595ab5636 100644 --- a/tests/test_game_server.py +++ b/tests/test_game_server.py @@ -39,6 +39,7 @@ def create_env_config(): layout_config=layout, environment_config=environment_config, seed=123, + env_name="test_env", )