diff --git a/CHANGELOG.md b/CHANGELOG.md index be5fec5110d6163960ab3084db4c75139aea53bf..ec679566d9e8b19dafd0afbada2f84c0a4999689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ - Level layouts from 2d-grid-overcooked-literature - Caching of graph recipe layouts - Score label changes color when score changes +- Scoring optionally dependent on remaining times of orders +- Score of order is shown in game +- Possible max score is shown next to recipe graphs - Control screenshot, replay, single game/study server, gui via cli (sub commands) ### Changed diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml index 25514c3bd6627f35df634e5836e9e617abc47f34..e11bb2dfc6726d4d4b2a87654f1cc88b4a9c4f1d 100644 --- a/cooperative_cuisine/configs/environment_config.yaml +++ b/cooperative_cuisine/configs/environment_config.yaml @@ -86,7 +86,7 @@ orders: player_config: radius: 0.4 - speed_units_per_seconds: 6 + speed_units_per_seconds: 5 interaction_range: 1.25 restricted_view: False view_angle: 70 @@ -106,13 +106,12 @@ hook_callbacks: hooks: [ completed_order ] callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' callback_class_kwargs: - static_score: 20 + time_dependence_func: !!python/name:cooperative_cuisine.scores.constant_score '' + time_dependence_kwargs: { } + static_score: 100 score_on_specific_kwarg: meal_name - score_map: - Burger: 15 - OnionSoup: 10 - Salad: 5 - TomatoSoup: 10 + score_map: { } + not_ordered_meals: hooks: [ serve_not_ordered_meal ] callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' diff --git a/cooperative_cuisine/configs/item_info.yaml b/cooperative_cuisine/configs/item_info.yaml index f8a14b7f57adbe184f318892a2d236338c66b43e..5bfe9904a9f4461060a5803730645cdbc77bd98c 100644 --- a/cooperative_cuisine/configs/item_info.yaml +++ b/cooperative_cuisine/configs/item_info.yaml @@ -174,7 +174,7 @@ FishAndChips: BurgerWithChips: type: Meal - needs: [ Burger, Chips ] + needs: [ Bun, ChoppedLettuce, ChoppedTomato, CookedPatty, Chips ] equipment: ~ Pizza: diff --git a/cooperative_cuisine/configs/item_info_debug.yaml b/cooperative_cuisine/configs/item_info_debug.yaml index f7783818b44059e9a004f627c60b433b6ac2e01a..ff43f34667f48a9cc981abb30eea923a789b1fcf 100644 --- a/cooperative_cuisine/configs/item_info_debug.yaml +++ b/cooperative_cuisine/configs/item_info_debug.yaml @@ -150,6 +150,11 @@ Burger: needs: [ Bun, ChoppedLettuce, ChoppedTomato, CookedPatty ] equipment: ~ +BurgerWithChips: + type: Meal + needs: [ Bun, ChoppedLettuce, ChoppedTomato, CookedPatty, Chips ] + equipment: ~ + Salad: type: Meal needs: [ ChoppedLettuce, ChoppedTomato ] diff --git a/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout b/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout index 60eb7af88bc47cc657b5d23e51556cad9a204fc0..a66163dadb89139302f67d35e1cea1fc1c3a53a9 100644 --- a/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout +++ b/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout @@ -1,12 +1,12 @@ -=#S+#====C#C#= -T____#==#____| -M_A__####__A_# -B____________# -L____####____$ -#____#==#____$ -#____#==#____P -X____#==#____# -=QQ#U====#@@@= +=#S+#===C#C#= +T____#=#____| +M_A__###__A_# +B___________# +L____###____$ +#____#=#____$ +#____#=#____P +X____#=#____# +=QQ#U===#@@@= ; seconds=240 ; plates={c:0, d:0} diff --git a/cooperative_cuisine/configs/study/study_config.yaml b/cooperative_cuisine/configs/study/study_config.yaml index e21045a79df9db46299d3da217f95e09b29adeca..d491af8801982c1acb218c46c6b8483aaad2610e 100644 --- a/cooperative_cuisine/configs/study/study_config.yaml +++ b/cooperative_cuisine/configs/study/study_config.yaml @@ -6,16 +6,15 @@ levels: name: "Level 1" seed: 12345 config_overwrite: - player_config: - speed_units_per_seconds: 5 game: time_limit_seconds: 300 - validate_recipes: false + validate_recipes: true plates: clean_plates: 0 dirty_plates: 0 return_dirty: true orders: + serving_not_ordered_meals: false meals: all: false list: @@ -29,8 +28,22 @@ levels: sample_on_dur_random_func: func: uniform kwargs: - a: 45 - b: 35 + a: 20 + b: 30 + + hook_callbacks: + orders: + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.stepped_score '' + time_dependence_kwargs: + score_ratios: [ 0.5, 1, 1 ] + steps: [ 0, 0.25, 0.66 ] + round_decimals: 0 + static_score: 20 + score_on_specific_kwarg: meal_name + score_map: [ ] - config_path: CONFIGS_DIR/environment_config.yaml layout_path: LAYOUTS_DIR/study_layouts/1-4-bottleneck.layout @@ -38,8 +51,6 @@ levels: name: "Level 2" seed: 12345 config_overwrite: - player_config: - speed_units_per_seconds: 5 game: time_limit_seconds: 300 plates: @@ -47,6 +58,7 @@ levels: dirty_plates: 0 return_dirty: true orders: + serving_not_ordered_meals: false meals: all: false list: @@ -61,8 +73,24 @@ levels: sample_on_dur_random_func: func: uniform kwargs: - a: 45 - b: 35 + a: 20 + b: 30 + hook_callbacks: + orders: + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.stepped_score '' + time_dependence_kwargs: + score_ratios: [ 0.5, 1, 1 ] + steps: [ 0, 0.25, 0.66 ] + round_decimals: 0 + static_score: 20 + score_on_specific_kwarg: meal_name + score_map: + Burger: 40 + Salad: 20 + TomatoSoup: 35 - config_path: CONFIGS_DIR/environment_config.yaml layout_path: LAYOUTS_DIR/study_layouts/1-5-circle.layout @@ -70,8 +98,6 @@ levels: name: "Level 3" seed: 12345 config_overwrite: - player_config: - speed_units_per_seconds: 5 game: time_limit_seconds: 300 validate_recipes: true @@ -80,6 +106,7 @@ levels: dirty_plates: 0 return_dirty: true orders: + serving_not_ordered_meals: false meals: all: false list: @@ -93,9 +120,21 @@ levels: sample_on_dur_random_func: func: uniform kwargs: - a: 45 - b: 35 - + a: 20 + b: 30 + hook_callbacks: + orders: + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.stepped_score '' + time_dependence_kwargs: + score_ratios: [ 0.5, 1, 1 ] + steps: [ 0, 0.25, 0.66 ] + round_decimals: 0 + static_score: 20 + score_on_specific_kwarg: meal_name + score_map: [ ] - config_path: CONFIGS_DIR/environment_config.yaml layout_path: LAYOUTS_DIR/study_layouts/forced-cooperation.layout @@ -103,15 +142,14 @@ levels: name: "Level 4" seed: 12345 config_overwrite: - player_config: - speed_units_per_seconds: 300 game: - time_limit_seconds: 10 + time_limit_seconds: 300 plates: clean_plates: 0 dirty_plates: 0 return_dirty: true orders: + serving_not_ordered_meals: false meals: all: false list: @@ -127,8 +165,25 @@ levels: sample_on_dur_random_func: func: uniform kwargs: - a: 40 - b: 50 + a: 20 + b: 30 + hook_callbacks: + orders: + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.stepped_score '' + time_dependence_kwargs: + score_ratios: [ 0.5, 1, 1 ] + steps: [ 0, 0.25, 0.66 ] + round_decimals: 0 + static_score: 20 + score_on_specific_kwarg: meal_name + score_map: + Burger: 40 + Salad: 20 + Chips: 10 + BurgerWithChips: 60 num_players: 1 num_bots: 0 diff --git a/cooperative_cuisine/environment.py b/cooperative_cuisine/environment.py index dfe89d708e058d8232641cf37c1180dc802da1d4..90be31686eb64c7cd15a3a034cb5c2e2fc4edf34 100644 --- a/cooperative_cuisine/environment.py +++ b/cooperative_cuisine/environment.py @@ -504,7 +504,7 @@ class Environment: "counters": [c.to_dict() for c in self.counters], "kitchen": {"width": self.kitchen_width, "height": self.kitchen_height}, "score": self.score, - "orders": self.order_manager.order_state(), + "orders": self.order_manager.order_state(self.env_time), "ended": self.game_ended, "env_time": self.env_time.isoformat(), "remaining_time": max( diff --git a/cooperative_cuisine/hooks.py b/cooperative_cuisine/hooks.py index d83daf7ff82a02b0c436f0bb79f9e92aec34eb4a..ea6d6c59d46637d3ed2795793af63e4a3308c0bd 100644 --- a/cooperative_cuisine/hooks.py +++ b/cooperative_cuisine/hooks.py @@ -423,6 +423,7 @@ 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. + remaining_time_ratio (float): the ratio of the remaining time of the order relative to order duration. meal_name (str): name of the meal. """ INIT_ORDERS = "init_orders" diff --git a/cooperative_cuisine/orders.py b/cooperative_cuisine/orders.py index 0b0529e0a39287b5dcf25bc5b494dba7e7fe7626..9a98314a352324fb787b32e1a8121d6aeb11d193 100644 --- a/cooperative_cuisine/orders.py +++ b/cooperative_cuisine/orders.py @@ -54,6 +54,7 @@ from cooperative_cuisine.hooks import ( ORDER_EXPIRED, ) from cooperative_cuisine.items import Item, Plate, ItemInfo +from cooperative_cuisine.scores import ScoreViaHooks from cooperative_cuisine.state_representation import OrderState log = logging.getLogger(__name__) @@ -185,6 +186,9 @@ class OrderManager: self.hook: Hooks = hook """Reference to the hook manager.""" + self.score_callbacks = [] + """List of score callbacks.""" + self.find_score_hook_callbacks() def set_available_meals(self, available_meals): """Set the available meals from which orders can be generated. @@ -204,6 +208,12 @@ class OrderManager: ) self.next_relevant_time = next_relevant_time + def find_score_hook_callbacks(self): + """Find the score hook callback class.""" + for hook in self.hook.hooks[COMPLETED_ORDER]: + if isinstance(hook, ScoreViaHooks): + self.score_callbacks.append(hook) + def serve_meal(self, item: Item, env_time: datetime, player: str) -> bool: """Is called by the ServingWindow to serve a meal. Returns True if the meal can be served and should be "deleted" from the hands of the player.""" @@ -236,6 +246,10 @@ class OrderManager: order=order, meal=meal, relative_order_time=env_time - order.start_time, + remaining_time_ratio=( + order.start_time + order.max_duration - env_time + ).total_seconds() + / order.max_duration.total_seconds(), meal_name=meal.name, ) return True @@ -286,13 +300,21 @@ class OrderManager: def find_order_for_meal(self, meal) -> Tuple[Order, int] | None: """Get the order that will be fulfilled for a meal. At the moment the oldest order in the list that has the - same meal (name).""" + same meal (name). + + Args: + meal: The meal to find the order for. + """ for index, order in enumerate(self.open_orders): if order.meal.name == meal.name: return order, index - def order_state(self) -> list[OrderState]: - """Similar to the `to_dict` in `Item` and `Counter`. Relevant for the state of the environment""" + def order_state(self, env_time: datetime) -> list[OrderState]: + """Similar to the `to_dict` in `Item` and `Counter`. Relevant for the state of the environment + + Args: + env_time: The current time of the environment. + """ return [ { "id": order.uuid, @@ -300,6 +322,24 @@ class OrderManager: "meal": order.meal.name, "start_time": order.start_time.isoformat(), "max_duration": order.max_duration.total_seconds(), + "score": sum( + [ + sc.get_score( + COMPLETED_ORDER, + **{ + "meal": order.meal, + "order": order, + "relative_order_time": env_time - order.start_time, + "remaining_time_ratio": ( + order.start_time + order.max_duration - env_time + ).total_seconds() + / order.max_duration.total_seconds(), + "meal_name": order.meal.name, + }, + )[0] + for sc in self.score_callbacks + ] + ), } for order in self.open_orders ] @@ -436,7 +476,7 @@ class RandomOrderGeneration(OrderGeneration): self.create_random_next_time_delta(now) return [] - # print(self.number_cur_orders, self.num_needed_orders) + # (self.number_cur_orders, self.num_needed_orders) if self.num_needed_orders: # self.num_needed_orders -= len(new_finished_orders) diff --git a/cooperative_cuisine/pygame_2d_vis/drawing.py b/cooperative_cuisine/pygame_2d_vis/drawing.py index 6896a7bee8ec9332e444f68a95de22f39176b874..d69b12eebbadf21f1ee144d00521a61cf01c2229 100644 --- a/cooperative_cuisine/pygame_2d_vis/drawing.py +++ b/cooperative_cuisine/pygame_2d_vis/drawing.py @@ -97,6 +97,8 @@ class Visualizer: if "Gui" in config and "show_counter_centers" in config["Gui"] else False ) + pygame.font.init() + self.font = pygame.font.SysFont("Arial", 20) def create_player_colors(self, n) -> None: """Create different colors for the players. The color hues are sampled uniformly in HSV-Space, @@ -822,11 +824,7 @@ class Visualizer: config: Visualization configuration (loaded from yaml file) given as a dict. """ - orders_width = width - 100 orders_height = height - # order_screen = pygame.Surface( - # (orders_width, orders_height), - # ) bg_color = colors[config["GameWindow"]["background_color"]] pygame.draw.rect(screen, bg_color, screen.get_rect()) @@ -879,6 +877,11 @@ class Visualizer: attention=percentage < 0.25, ) + self.font.set_bold(True) + + text_surface = self.font.render(str(order["score"]), True, (0, 0, 0)) + screen.blit(text_surface, center) + def save_state_image( self, grid_size: int, state: dict, filename: str | Path ) -> None: diff --git a/cooperative_cuisine/pygame_2d_vis/gui.py b/cooperative_cuisine/pygame_2d_vis/gui.py index 7d6fca9f7418cb593acbbc2e3af210c62a81709a..4ad01fc36073e8402dc4c0fe45842dd045e8a048 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui.py +++ b/cooperative_cuisine/pygame_2d_vis/gui.py @@ -1440,7 +1440,7 @@ class PyGameGUI: if self.switch_score_color: self.count_frames_score_label += 1 - duration_color_change = 90 + duration_color_change = 60 if score > self.last_score: self.score_label.update_theming( '{"colours": {"normal_text": "#03b706"}, "font": { "size": 20, "bold": 1}}' @@ -1553,15 +1553,16 @@ class PyGameGUI: "translations.level_name", text_kwargs={"level": self.level_info["name"]} ) - graph_width = self.window_width * 0.6 rows = 0 for rg in self.level_info["recipe_graphs"]: rows += len(np.unique(np.array(list(rg["layout"].values()))[:, 1])) row_height = self.window_height / 14 - container_width = self.window_width * 0.9 + container_width = self.window_width * 0.98 container_height = rows * row_height icon_size = row_height * 0.9 + graph_width = container_width * 0.55 + main_container = pygame_gui.elements.UIPanel( relative_rect=pygame.Rect( (0, 0), @@ -1583,9 +1584,6 @@ class PyGameGUI: positions = np.array(list(rg["layout"].values())) - unique_x_vals = np.unique(positions[:, 0]) - - height = row_height * len(np.unique(positions[:, 1])) graph_height = height * 0.9 @@ -1621,7 +1619,7 @@ class PyGameGUI: rect = pygame.Rect( (0, 0), - (self.window_width / 5, height), + (self.window_width / 6, height), ) label = pygame_gui.elements.UILabel( text=f"translations.{meal}", @@ -1633,17 +1631,33 @@ class PyGameGUI: ) rect = graph_surface.get_rect() - rect.right = 0 graph_image = pygame_gui.elements.UIImage( relative_rect=rect, image_surface=graph_surface, manager=self.manager, object_id="#recipe_graph", container=container, - anchors={"centery": "centery", "right": "right"}, + anchors={"center": "center"}, ) last_recipes_labels.append(container) + rect = pygame.Rect( + (0, 0), + (self.window_width / 6, height), + ) + rect.right = 0 + recipe_score_label = pygame_gui.elements.UILabel( + text="", + relative_rect=rect, + manager=self.manager, + container=container, + object_id="#recipe_name", + anchors={"centery": "centery", "right": "right"}, + ) + recipe_score_label.set_text( + "translations.recipe_score", text_kwargs={"score": str(rg["score"])} + ) + self.scroll_space_recipes.set_scrollable_area_dimensions( (self.window_width * 0.95, container_height) ) diff --git a/cooperative_cuisine/pygame_2d_vis/gui_theme.json b/cooperative_cuisine/pygame_2d_vis/gui_theme.json index 5ceaf4397e562c1e82e53566b5f09e91598e41f9..153f1ad88332218811b1f0e164c0564512429593 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui_theme.json +++ b/cooperative_cuisine/pygame_2d_vis/gui_theme.json @@ -64,7 +64,7 @@ "normal_text": "#000000" }, "font": { - "size": 20, + "size": 24, "bold": 1 } }, diff --git a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json index 47579ce8519b642e48e78611225d37e7e037fdfd..4b417c16b808c02e81bcdf6ee3f9fa9b6180080f 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json @@ -32,9 +32,10 @@ "Burger": "Burger:", "Pizza": "Pizza:", "Fish And Chips": "Fish and Chips:", - "Chips": "Pommes", + "Chips": "Pommes:", "Salad": "Salat:", "Fried Fish": "Backfisch:", - "Burger With Chips": "Burger mit Pommes:" + "Burger With Chips": "Burger mit Pommes:", + "recipe_score": "Punkte: %{score}" } } \ No newline at end of file diff --git a/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json b/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json index 9f9c558bf762d3db59f73aacd127e71d19491853..d2290d1fd2747ccd98ef973045f2255d72d2dc6c 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json @@ -35,6 +35,7 @@ "Chips": "Chips", "Salad": "Salad:", "Fried Fish": "Fried Fish:", - "Burger With Chips": "Burger with Chips:" + "Burger With Chips": "Burger with Chips:", + "recipe_score": "Points: %{score}" } } \ No newline at end of file diff --git a/cooperative_cuisine/scores.py b/cooperative_cuisine/scores.py index 3bab87e92f29caec0903c3865c747757f3b0c188..eb8529381456cbd4b5d438964d6425d713ef2c35 100644 --- a/cooperative_cuisine/scores.py +++ b/cooperative_cuisine/scores.py @@ -39,18 +39,123 @@ hook_callbacks: callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' callback_class_kwargs: static_score: -10 + +The score can be modified based on the time remaining for the order to be completed. +hook_callbacks: + orders: + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.linear_score '' + time_dependence_kwargs: + min_score_ratio: 0.3 + round_decimals: 2 + static_score: 100 + score_on_specific_kwarg: meal_name + score_map: [] + + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.stepped_score '' + time_dependence_kwargs: + steps: [0.3, 0.6, 1.0] + ratios: [0.3, 0.6, 1.0] + round_decimals: 2 + static_score: 100 + score_on_specific_kwarg: meal_name + score_map: [] + + hooks: [ completed_order ] + callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks '' + callback_class_kwargs: + time_dependence_func: !!python/name:cooperative_cuisine.scores.constant_score '' + time_dependence_kwargs: {} + static_score: 100 + score_on_specific_kwarg: meal_name + score_map: [] ``` # Code Documentation """ +from __future__ import annotations + +from typing import Any, TYPE_CHECKING -from typing import Any +import numpy as np -from cooperative_cuisine.environment import Environment +if TYPE_CHECKING: + from cooperative_cuisine.environment import Environment from cooperative_cuisine.hooks import HookCallbackClass +def constant_score(max_score: float, time_percentage: float = 1.0): + """Returns the constant score. No time dependence. + + Args: + max_score: The maximum score to be returned. + time_percentage: The time percentage. Default is 1.0. + """ + return max_score + + +def linear_score( + max_score: float, + min_score_ratio: float = 0.0, + time_percentage: float = 1.0, + round_decimals: int = 0, +): + """ + Returns the linear score based on the time percentage. + + Args: + max_score: Maximum possible score. + min_score_ratio: Minimum score ratio. Default is 0.0. + time_percentage: Time percentage of the order. Default is 1.0. + round_decimals: Number of decimals to round the score to. Default is 0. + + Returns: Modified score based on the time percentage. + """ + modified_score = float( + np.round( + max(max_score * time_percentage, max_score * min_score_ratio), + decimals=round_decimals, + ) + ) + return modified_score + + +def stepped_score( + max_score: float, + steps: list[float], + score_ratios: list[float], + round_decimals: int = 0, + time_percentage: float = 1.0, +): + """ + Modifies the score based on a step function based on the time percentage. + + Args: + max_score: Maximum possible score. + steps: Steps for the score ratios. + score_ratios: Ratios for the score based on the steps. + round_decimals: Number of decimals to round the score to. Default is 0. + time_percentage: Time percentage of the order. Default is 1.0. + + Returns: Modified score based on the time percentage. + + """ + if len(steps) != len(score_ratios): + raise ValueError("steps and vals must have the same length") + + for threshold, ratio in zip(reversed(steps), reversed(score_ratios)): + if max(time_percentage, 0) >= threshold: + return float(np.round(max_score * ratio, decimals=round_decimals)) + + assert False, "This should not happen." + + class ScoreViaHooks(HookCallbackClass): """ Defines a class ScoreViaHooks that extends the HookCallbackClass. @@ -71,6 +176,8 @@ class ScoreViaHooks(HookCallbackClass): static_score: float = 0, score_map: dict[str, float] = None, score_on_specific_kwarg: str = None, + time_dependence_func: callable = constant_score, + time_dependence_kwargs: dict[str, Any] = None, kwarg_filter: dict[str, Any] = None, **kwargs, ): @@ -94,24 +201,47 @@ class ScoreViaHooks(HookCallbackClass): """Filtering condition for keyword arguments.""" self.score_on_specific_kwarg: str = score_on_specific_kwarg """The specific keyword argument to score on.""" + self.time_dependence_func: callable = time_dependence_func + """The function to calculate the score based on time.""" + self.time_dependence_kwargs: dict[str, Any] = ( + time_dependence_kwargs if time_dependence_kwargs else {} + ) + """The keyword arguments to be passed to the time_dependence_func.""" - def __call__(self, hook_ref: str, env: Environment, **kwargs): + def get_score(self, hook_ref: str, **kwargs): if self.score_on_specific_kwarg: if kwargs[self.score_on_specific_kwarg] in self.score_map: - self.env.increment_score( - self.score_map[kwargs[self.score_on_specific_kwarg]], - info=f"{hook_ref} - {kwargs[self.score_on_specific_kwarg]}", - ) + score = self.score_map[kwargs[self.score_on_specific_kwarg]] + info = f"{hook_ref} - {kwargs[self.score_on_specific_kwarg]}" else: - self.env.increment_score(self.static_score, info=hook_ref) + score = self.static_score + info = hook_ref elif self.score_map and hook_ref in self.score_map: if self.kwarg_filter: if self.kwarg_filter.items() <= kwargs.items(): - self.env.increment_score( - self.score_map[hook_ref], - info=f"{hook_ref} - {self.kwarg_filter}", - ) + score = self.score_map[hook_ref] + info = f"{hook_ref} - {self.kwarg_filter}" + else: + score = 0 + info = "NO INFO?" else: - self.env.increment_score(self.score_map[hook_ref], info=hook_ref) + score = self.score_map[hook_ref] + info = hook_ref else: - self.env.increment_score(self.static_score, info=hook_ref) + score = self.static_score + info = hook_ref + + if score: + if hook_ref == "completed_order": + ratio = kwargs["remaining_time_ratio"] + else: + ratio = 1.0 + modified_score = self.time_dependence_func( + score, time_percentage=ratio, **self.time_dependence_kwargs + ) + return modified_score, info + return 0, "NO INFO?" + + def __call__(self, hook_ref: str, env: Environment, **kwargs): + score, info = self.get_score(hook_ref, **kwargs) + self.env.increment_score(score, info=info) diff --git a/cooperative_cuisine/state_representation.py b/cooperative_cuisine/state_representation.py index 209bf7d98ad3157f093071b23a479fd89563a637..68191b67fe3c3ee9b21b0b1e79c186e3a971c327 100644 --- a/cooperative_cuisine/state_representation.py +++ b/cooperative_cuisine/state_representation.py @@ -27,6 +27,8 @@ class OrderState(TypedDict): """Time of the creation of the order.""" max_duration: float """Maximum duration of the order until it should be served.""" + score: float | int + """Score of the order.""" class EffectState(TypedDict): diff --git a/cooperative_cuisine/validation.py b/cooperative_cuisine/validation.py index cea549750ef51f2cbfc6b323cd8a3c6d14fb3eca..111edaf73d760b87e97e053b6b55b83420ae578c 100644 --- a/cooperative_cuisine/validation.py +++ b/cooperative_cuisine/validation.py @@ -4,6 +4,7 @@ import json import os import uuid import warnings +from datetime import datetime, timedelta from typing import TypedDict, Tuple, Iterator, Set import networkx as nx @@ -17,8 +18,9 @@ from cooperative_cuisine.counters import ( PlateDispenser, Counter, ) +from cooperative_cuisine.hooks import COMPLETED_ORDER from cooperative_cuisine.items import ItemInfo, ItemType, Item -from cooperative_cuisine.orders import OrderManager +from cooperative_cuisine.orders import OrderManager, Order class MealGraphDict(TypedDict): @@ -30,6 +32,8 @@ class MealGraphDict(TypedDict): """A list of tuples representing the edges between cooking steps.""" layout: dict[str, Tuple[float, float]] """A dictionary mapping cooking step names to their layout coordinates.""" + score: float + """The max possible score of the meal.""" class Validation: @@ -161,7 +165,26 @@ class Validation: current, ) - before_hash = "".join(sorted(str(sorted([sorted(i) for i in graph.edges])))) + dummy_order = Order(meal, datetime.now(), timedelta(seconds=1)) + score = sum( + [ + sc.get_score( + COMPLETED_ORDER, + **{ + "meal": dummy_order.meal, + "order": dummy_order, + "relative_order_time": timedelta(seconds=0), + "remaining_time_ratio": 1.0, + "meal_name": dummy_order.meal.name, + }, + )[0] + for sc in self.order_manager.score_callbacks + ] + ) + + before_hash = "".join( + sorted(str(sorted([sorted(i) for i in graph.edges]))) + ) + str(score) h = hashlib.new("sha1") h.update(before_hash.encode()) graph_hash = h.hexdigest() @@ -186,6 +209,7 @@ class Validation: "meal": meal.name, "edges": list(graph.edges), "layout": layout, + "score": score, } with open(generated_graph_layouts_path, "w") as f: self.recipe_graph_dicts[graph_hash] = graph_dict