From 4aecc23e57d5edfc412b93167c782543fe5e3f71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20Schr=C3=B6der?=
 <fschroeder@techfak.uni-bielefeld.de>
Date: Wed, 13 Mar 2024 14:37:08 +0100
Subject: [PATCH] Update methods and hooks in cooperative_cuisine

The main changes include enhancing methods in cooperative_cuisine/hooks.py, cooperative_cuisine/counters.py, and cooperative_cuisine/environment.py, and updating hooks. These changes greatly improve the handling of hooks and calls, provide explicit arguments for actions, and add more comprehensive documentation. This leads to better logging and debugging, higher code readability, and improved overall code structure in the cooperative_cuisine backend.
---
 .../configs/environment_config.yaml           |   1 +
 .../human_readable_print_templates.yaml       |   1 +
 cooperative_cuisine/counters.py               |  14 +-
 cooperative_cuisine/environment.py            |  10 +-
 cooperative_cuisine/hooks.py                  | 249 +++++++++++++++++-
 5 files changed, 255 insertions(+), 20 deletions(-)

diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml
index e9188198..b005d07a 100644
--- a/cooperative_cuisine/configs/environment_config.yaml
+++ b/cooperative_cuisine/configs/environment_config.yaml
@@ -179,6 +179,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 cb9c47e9..e2cf2e2e 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 bf97c26b..97dd3393 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 25eff9e6..f4c087fa 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 c541562b..edb4aa3c 100644
--- a/cooperative_cuisine/hooks.py
+++ b/cooperative_cuisine/hooks.py
@@ -21,69 +21,282 @@ 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"
 POST_SERVING = "post_serving"
@@ -99,6 +312,11 @@ DROP_ON_SINK_ADDON = "drop_on_sink_addon"
 PICK_UP_FROM_SINK_ADDON = "pick_up_from_sink_addon"
 PLATE_CLEANED = "plate_cleaned"
 
+ON_ITEM_TRANSITION = "on_item_transition"
+CONTENT_READY = "content_ready"
+
+# --- orders.py ---
+
 SERVE_NOT_ORDERED_MEAL = "serve_not_ordered_meal"
 SERVE_WITHOUT_PLATE = "serve_without_plate"
 
@@ -108,22 +326,27 @@ INIT_ORDERS = "init_orders"
 NEW_ORDERS = "new_orders"
 ORDER_EXPIRED = "order_expired"
 
+# --- environment.py --- but player related.
+
 ACTION_ON_NOT_REACHABLE_COUNTER = "action_on_not_reachable_counter"
 ACTION_PUT = "action_put"
 ACTION_INTERACT_START = "action_interact_start"
 
+# --- effects.py ---
+
 ITEM_BURNED = "item_burned"  # MISSING
 NEW_FIRE = "new_fire"
 FIRE_SPREADING = "fire_spreading"
 
+# --- movement.py ---
+
 PLAYERS_COLLIDE = "players_collide"
 
+# --- players.py ---
+
 PLAYER_START_INTERACT = "player_start_interaction"
 PLAYER_END_INTERACT = "player_end_interact"
 
-ON_ITEM_TRANSITION = "on_item_transition"
-CONTENT_READY = "content_ready"
-
 
 class Hooks:
     """Represents a collection of hooks and provides methods to register callbacks for hooks and invoke the callbacks when hooks are triggered.
-- 
GitLab