diff --git a/CHANGELOG.md b/CHANGELOG.md index 737375f8c607867def81e3fd4ac2b99853338499..be5fec5110d6163960ab3084db4c75139aea53bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,13 @@ - Send full websocket url in player_info. - ">90"% code coverage in tests - i18n for the gui -- Controller hotplugging +- Controller hot-plugging - Hook when returning items to dispensers - Displaying image of served meals on game conclusion screen - Pathfinding in random agent - Level layouts from 2d-grid-overcooked-literature - Caching of graph recipe layouts +- Score label changes color when score changes - Control screenshot, replay, single game/study server, gui via cli (sub commands) ### Changed @@ -45,6 +46,7 @@ - Better drawing of orders, now in a pygame_gui UIImage - Buttons for setting player controls in the GUI disappear depending on number of players - Icon for serving window, now a star +- Additional state content is stored in own variable. Is no longer passed via a kwarg to the step function. ### Deprecated diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml index d6a51ea95225f77ae00cb56e49a2d4eda40c95e2..25514c3bd6627f35df634e5836e9e617abc47f34 100644 --- a/cooperative_cuisine/configs/environment_config.yaml +++ b/cooperative_cuisine/configs/environment_config.yaml @@ -13,6 +13,7 @@ game: layout_chars: _: Free hash: Counter # # + equal: EdgeCounter # = A: Agent pipe: Extinguisher P: PlateDispenser @@ -186,6 +187,8 @@ hook_callbacks: - progress_finished - content_ready - dispenser_item_returned + - additional_state_update + - game_ended_step callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder '' callback_class_kwargs: diff --git a/cooperative_cuisine/configs/environment_config_no_validation.yaml b/cooperative_cuisine/configs/environment_config_no_validation.yaml index 1802c28a539cd92d7879ea14643f545bcebd3c99..47fdde17761438f3afd11c81769cf3313bd5dd07 100644 --- a/cooperative_cuisine/configs/environment_config_no_validation.yaml +++ b/cooperative_cuisine/configs/environment_config_no_validation.yaml @@ -184,6 +184,8 @@ hook_callbacks: - progress_finished - content_ready - dispenser_item_returned + - additional_state_update# + - game_ended_step callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder '' callback_class_kwargs: diff --git a/cooperative_cuisine/configs/item_info.yaml b/cooperative_cuisine/configs/item_info.yaml index 96ddf78c9ddb9800f1948f9e159cc2215784648c..f8a14b7f57adbe184f318892a2d236338c66b43e 100644 --- a/cooperative_cuisine/configs/item_info.yaml +++ b/cooperative_cuisine/configs/item_info.yaml @@ -172,6 +172,11 @@ FishAndChips: needs: [ FriedFish, Chips ] equipment: ~ +BurgerWithChips: + type: Meal + needs: [ Burger, Chips ] + equipment: ~ + Pizza: type: Meal needs: [ PizzaBase, ChoppedTomato, GratedCheese, ChoppedSausage ] diff --git a/cooperative_cuisine/configs/layouts/overcooked-1/1-1-far-apart.layout b/cooperative_cuisine/configs/layouts/overcooked-1/1-1-far-apart.layout index 1b0b160ca73b18a62526ab9d020b9a4394fd2c63..5cf16b3f7bfa3ca45e8afd87d3255a73d3e64da8 100644 --- a/cooperative_cuisine/configs/layouts/overcooked-1/1-1-far-apart.layout +++ b/cooperative_cuisine/configs/layouts/overcooked-1/1-1-far-apart.layout @@ -1,6 +1,6 @@ ###N#T##U#### #___________| -L___A___A___# +#___A___A___# #___________S ##########__+ P___________# diff --git a/cooperative_cuisine/configs/layouts/study_layouts/1-1-far-apart.layout b/cooperative_cuisine/configs/layouts/study_layouts/1-1-far-apart.layout new file mode 100644 index 0000000000000000000000000000000000000000..651db57d04b706517e12ee8dbabd509fe9dbe480 --- /dev/null +++ b/cooperative_cuisine/configs/layouts/study_layouts/1-1-far-apart.layout @@ -0,0 +1,14 @@ +=#N#T###U###= +#___________| +#___A_______# +#___________S +=#########__+ +P___________# +$_______A___# +$___________X +=##C#C###@@#= + +; seconds=150 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-1_(Overcooked!) \ No newline at end of file diff --git a/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout b/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout new file mode 100644 index 0000000000000000000000000000000000000000..60eb7af88bc47cc657b5d23e51556cad9a204fc0 --- /dev/null +++ b/cooperative_cuisine/configs/layouts/study_layouts/1-4-bottleneck.layout @@ -0,0 +1,14 @@ +=#S+#====C#C#= +T____#==#____| +M_A__####__A_# +B____________# +L____####____$ +#____#==#____$ +#____#==#____P +X____#==#____# +=QQ#U====#@@@= + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-4_(Overcooked!) \ No newline at end of file diff --git a/cooperative_cuisine/configs/layouts/study_layouts/1-5-circle.layout b/cooperative_cuisine/configs/layouts/study_layouts/1-5-circle.layout new file mode 100644 index 0000000000000000000000000000000000000000..995c7634b47696c65e5330cb94c9c7c326aa09c3 --- /dev/null +++ b/cooperative_cuisine/configs/layouts/study_layouts/1-5-circle.layout @@ -0,0 +1,14 @@ +====P$$#==== +=T#N____##|= +#_A________X +#_###S+###_# +#_#======#_# +#_#======#_# +#_####@@##_# +#________A_# +=#C#C###U#U= + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-5_(Overcooked!) \ No newline at end of file diff --git a/cooperative_cuisine/configs/layouts/study_layouts/forced-cooperation.layout b/cooperative_cuisine/configs/layouts/study_layouts/forced-cooperation.layout new file mode 100644 index 0000000000000000000000000000000000000000..39fdd1a4a754bd7bc7a076f438a4c2de9553145a --- /dev/null +++ b/cooperative_cuisine/configs/layouts/study_layouts/forced-cooperation.layout @@ -0,0 +1,9 @@ +=#C#C#####$$= +#___________# +#_A_________P +#_____##@#@#= +X_____#_____X +=###@##_____| +F_________A_# +Q___________K +=#+S###LT#MB= \ No newline at end of file diff --git a/cooperative_cuisine/configs/study/study_config.yaml b/cooperative_cuisine/configs/study/study_config.yaml index d09fb9929d1a256a4399cb7b459d454cb5abf807..e21045a79df9db46299d3da217f95e09b29adeca 100644 --- a/cooperative_cuisine/configs/study/study_config.yaml +++ b/cooperative_cuisine/configs/study/study_config.yaml @@ -1,54 +1,134 @@ levels: - config_path: CONFIGS_DIR/environment_config.yaml - layout_path: LAYOUTS_DIR/overcooked-1/1-1-far-apart.layout + layout_path: LAYOUTS_DIR/study_layouts/1-1-far-apart.layout item_info_path: CONFIGS_DIR/item_info.yaml name: "Level 1" + seed: 12345 config_overwrite: + player_config: + speed_units_per_seconds: 5 game: time_limit_seconds: 300 + validate_recipes: false plates: clean_plates: 0 dirty_plates: 0 - orders: - order_gen_kwargs: - order_duration_random_func: - kwargs: - a: 60 - b: 70 + return_dirty: true + orders: + meals: + all: false + list: + - TomatoSoup + - OnionSoup + order_gen_kwargs: + order_duration_random_func: + kwargs: + a: 60 + b: 70 + sample_on_dur_random_func: + func: uniform + kwargs: + a: 45 + b: 35 - config_path: CONFIGS_DIR/environment_config.yaml - layout_path: LAYOUTS_DIR/overcooked-1/1-4-bottleneck.layout + layout_path: LAYOUTS_DIR/study_layouts/1-4-bottleneck.layout item_info_path: CONFIGS_DIR/item_info.yaml name: "Level 2" + seed: 12345 config_overwrite: + player_config: + speed_units_per_seconds: 5 game: time_limit_seconds: 300 + plates: + clean_plates: 1 + dirty_plates: 0 + return_dirty: true + orders: + meals: + all: false + list: + - Burger + - Salad + - TomatoSoup + order_gen_kwargs: + order_duration_random_func: + kwargs: + a: 60 + b: 70 + sample_on_dur_random_func: + func: uniform + kwargs: + a: 45 + b: 35 - config_path: CONFIGS_DIR/environment_config.yaml - layout_path: LAYOUTS_DIR/overcooked-1/1-5-circle.layout + layout_path: LAYOUTS_DIR/study_layouts/1-5-circle.layout item_info_path: CONFIGS_DIR/item_info.yaml name: "Level 3" + seed: 12345 config_overwrite: + player_config: + speed_units_per_seconds: 5 game: time_limit_seconds: 300 + validate_recipes: true plates: clean_plates: 1 dirty_plates: 0 - return_dirty: false + return_dirty: true + orders: + meals: + all: false + list: + - TomatoSoup + - OnionSoup + order_gen_kwargs: + order_duration_random_func: + kwargs: + a: 60 + b: 70 + sample_on_dur_random_func: + func: uniform + kwargs: + a: 45 + b: 35 + - config_path: CONFIGS_DIR/environment_config.yaml - layout_path: LAYOUTS_DIR/overcooked-1/4-1-moving-counters.layout + layout_path: LAYOUTS_DIR/study_layouts/forced-cooperation.layout item_info_path: CONFIGS_DIR/item_info.yaml name: "Level 4" + seed: 12345 config_overwrite: + player_config: + speed_units_per_seconds: 300 game: - time_limit_seconds: 300 + time_limit_seconds: 10 plates: clean_plates: 0 dirty_plates: 0 - - + return_dirty: true + orders: + meals: + all: false + list: + - Burger + - Salad + - Chips + - BurgerWithChips + order_gen_kwargs: + order_duration_random_func: + kwargs: + a: 60 + b: 70 + sample_on_dur_random_func: + func: uniform + kwargs: + a: 40 + b: 50 num_players: 1 num_bots: 0 diff --git a/cooperative_cuisine/counters.py b/cooperative_cuisine/counters.py index 97dd339359c6887ffad601782084813cae5eaefe..eb39a753b95e938c077def8ebd36697bcef37eac 100644 --- a/cooperative_cuisine/counters.py +++ b/cooperative_cuisine/counters.py @@ -333,6 +333,10 @@ class Counter: } +class EdgeCounter(Counter): + ... + + class CuttingBoard(Counter): """Cutting ingredients on. The requirement in a new object could look like. diff --git a/cooperative_cuisine/environment.py b/cooperative_cuisine/environment.py index f4c087fa1a55b21efc2c558b3bd9c4bddacce0f2..dfe89d708e058d8232641cf37c1180dc802da1d4 100644 --- a/cooperative_cuisine/environment.py +++ b/cooperative_cuisine/environment.py @@ -52,6 +52,7 @@ from cooperative_cuisine.hooks import ( ITEM_INFO_CONFIG, POST_STEP, hooks_via_callback_class, + ADDITIONAL_STATE_UPDATE, ) from cooperative_cuisine.items import ( ItemInfo, @@ -313,6 +314,10 @@ class Environment: self.info_msgs_per_player: dict[str, list[InfoMsg]] = defaultdict(list) """Cache of info messages per player which should be showed in the visualization of each player.""" + self.additional_state_content = {} + """The environment will extend the content of each state with this dictionary. Adapt it with the setter + function.""" + self.hook( ENV_INITIALIZED, environment_config=env_config, @@ -483,14 +488,11 @@ 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 - ) -> dict: + def get_state(self, player_id: str = None) -> dict: """Get the current state of the game environment. The state here is accessible by the current python objects. Args: player_id: The player for which to get the state. - additional_key_values: Additional dict that is added to the state Returns: The state of the game as a dict. @@ -529,26 +531,23 @@ class Environment: for msg in self.info_msgs_per_player[player_id] if msg["start_time"] < self.env_time < msg["end_time"] ], - **(additional_key_values if additional_key_values else {}), + **self.additional_state_content, } self.hook(STATE_DICT, state=state, player_id=player_id) return state raise ValueError(f"No valid {player_id=}") - def get_json_state( - self, player_id: str = None, additional_key_values: dict = None - ) -> str: + def get_json_state(self, player_id: str = None) -> str: """Return the current state of the game formatted in json dict. Args: player_id: The player for which to get the state. - additional_key_values: Additional dict that is added to the state Returns: The state of the game formatted as a json-string """ - state = self.get_state(player_id, additional_key_values) + state = self.get_state(player_id) json_data = json.dumps(state) self.hook(JSON_STATE, json_data=json_data, player_id=player_id) # assert StateRepresentation.model_validate_json(json_data=json_data) @@ -586,3 +585,7 @@ class Environment: """Add a value to the current score and log it.""" self.score += score log.debug(f"Score: {self.score} ({score}) - {info}") + + def update_additional_state_content(self, **kwargs): + self.hook(ADDITIONAL_STATE_UPDATE, update=kwargs) + self.additional_state_content.update(kwargs) diff --git a/cooperative_cuisine/game_server.py b/cooperative_cuisine/game_server.py index 624a6f5f3d97f552e32a0ba11b65f330301b159d..e565cbe91a1652af87d51cbc3b7de066e8903700 100644 --- a/cooperative_cuisine/game_server.py +++ b/cooperative_cuisine/game_server.py @@ -113,8 +113,6 @@ class EnvironmentData: """Time of when the environment was started.""" last_step_time: int | None = None """Time of the last performed step of the environment.""" - all_players_ready: bool = False - """Did all players send 'ready'.""" # add manager_id? @@ -190,6 +188,8 @@ class EnvironmentHandler: graphs = env.recipe_validation.get_recipe_graphs() kitchen_size = (env.kitchen_width, env.kitchen_height) + env.update_additional_state_content(all_players_ready=False) + res = CreateEnvResult( env_id=env_id, player_info=player_info, @@ -278,7 +278,9 @@ class EnvironmentHandler: self.envs[env_id].start_time = start_time self.envs[env_id].last_step_time = time.time_ns() self.envs[env_id].environment.reset_env_time() - self.envs[env_id].all_players_ready = True + self.envs[env_id].environment.update_additional_state_content( + all_players_ready=True + ) def get_state( self, player_hash: str @@ -299,7 +301,6 @@ class EnvironmentHandler: env_data = self.envs[self.player_data[player_hash].env_id] state = env_data.environment.get_json_state( self.player_data[player_hash].player_id, - additional_key_values={"all_players_ready": env_data.all_players_ready}, ) return state if player_hash not in self.player_data: diff --git a/cooperative_cuisine/hooks.py b/cooperative_cuisine/hooks.py index 3f7c037696a6bb8443b42f213bf8e1378f5af473..d83daf7ff82a02b0c436f0bb79f9e92aec34eb4a 100644 --- a/cooperative_cuisine/hooks.py +++ b/cooperative_cuisine/hooks.py @@ -511,6 +511,14 @@ Args: counter (Counter): the last interacted counter. """ +# -- extra -- +ADDITIONAL_STATE_UPDATE = "additional_state_update" +"""Update of the additional content of the state. + +Args: + update (dict[str, Any]): update of the additional state content. +""" + class Hooks: """Represents a collection of hooks and provides methods to register callbacks for hooks and invoke the callbacks when hooks are triggered. diff --git a/cooperative_cuisine/orders.py b/cooperative_cuisine/orders.py index 95b994c558ecc6e71a5f11f926f57326669fd393..0b0529e0a39287b5dcf25bc5b494dba7e7fe7626 100644 --- a/cooperative_cuisine/orders.py +++ b/cooperative_cuisine/orders.py @@ -402,7 +402,7 @@ class RandomOrderGeneration(OrderGeneration): """For efficient checking to update order removable.""" self.number_cur_orders: int = 0 """How many orders are currently open.""" - self.needed_orders: int = 0 + self.num_needed_orders: int = 0 """For the sample on dur but when it was restricted due to max order number.""" def init_orders(self, now) -> list[Order]: @@ -435,26 +435,25 @@ class RandomOrderGeneration(OrderGeneration): if new_finished_orders: self.create_random_next_time_delta(now) return [] - # print( - # " - -", - # self.needed_orders, - # self.number_cur_orders, - # self.next_order_time, - # now, - # ) - - if self.needed_orders: - self.needed_orders -= len(new_finished_orders) - self.needed_orders = max(self.needed_orders, 0) - self.number_cur_orders += len(new_finished_orders) + + # print(self.number_cur_orders, self.num_needed_orders) + + if self.num_needed_orders: + # self.num_needed_orders -= len(new_finished_orders) + # self.num_needed_orders = max(self.num_needed_orders, 0) + # self.number_cur_orders += len(new_finished_orders) + return self.create_orders_for_meals( - self.random.choices(self.available_meals, k=len(new_finished_orders)), + self.random.choices( + self.available_meals, + k=len(new_finished_orders) + len(expired_orders), + ), now, ) if self.next_order_time <= now: if self.number_cur_orders >= self.kwargs.max_orders: - self.needed_orders += 1 + self.num_needed_orders += 1 else: if not self.kwargs.sample_on_serving: self.create_random_next_time_delta(now) diff --git a/cooperative_cuisine/pygame_2d_vis/drawing.py b/cooperative_cuisine/pygame_2d_vis/drawing.py index ff41b9c416b62ed76851229aa0d9ebcd302220a3..6896a7bee8ec9332e444f68a95de22f39176b874 100644 --- a/cooperative_cuisine/pygame_2d_vis/drawing.py +++ b/cooperative_cuisine/pygame_2d_vis/drawing.py @@ -426,7 +426,7 @@ class Visualizer: grid_size, grid_size, ), - width=2, + width=3, ) def draw_thing( @@ -924,7 +924,22 @@ class Visualizer: self, screen: pygame.Surface, graph_dict, width, height, grid_size ) -> None: # screen.fill(self.config["GameWindow"]["background_color"]) + positions_dict = graph_dict["layout"] + + positions = np.array(list(positions_dict.values())) + unique_x_vals = np.unique(positions[:, 0]) + new_positions_unique = np.linspace( + start=0, + stop=np.max(positions[:, 0]), + num=len(unique_x_vals), + ) + replace_map = { + unique_x_vals[i]: new_positions_unique[i] for i in range(len(unique_x_vals)) + } + for k, v in graph_dict["layout"].items(): + graph_dict["layout"][k] = (replace_map[v[0]], v[1]) + positions = np.array(list(positions_dict.values())) positions = positions - positions.min(axis=0) positions[positions == 0] = 0.000001 diff --git a/cooperative_cuisine/pygame_2d_vis/gui.py b/cooperative_cuisine/pygame_2d_vis/gui.py index e7b8816909f81455c80950307a7f8a14af217fdf..7d6fca9f7418cb593acbbc2e3af210c62a81709a 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui.py +++ b/cooperative_cuisine/pygame_2d_vis/gui.py @@ -172,7 +172,6 @@ class PyGameGUI: self.FPS = self.visualization_config["GameWindow"]["FPS"] - self.screen_margin = self.visualization_config["GameWindow"]["screen_margin"] self.min_width = self.visualization_config["GameWindow"]["min_width"] self.min_height = self.visualization_config["GameWindow"]["min_height"] self.buttons_width = self.visualization_config["GameWindow"]["buttons_width"] @@ -202,6 +201,10 @@ class PyGameGUI: self.images_path = ROOT_DIR / "pygame_gui" / "images" self.vis = Visualizer(self.visualization_config) + self.last_score: float = 0 + self.switch_score_color: bool = False + self.count_frames_score_label: int = 0 + self.fullscreen = do_study self.menu_state = MenuStates.Start @@ -423,6 +426,9 @@ class PyGameGUI: self.window_width = self.window_width_windowed self.window_height = self.window_height_windowed + self.screen_margin = self.visualization_config["GameWindow"][ + "screen_margin_proportion" + ] * min(self.window_width, self.window_height) self.main_window = pygame.display.set_mode( ( self.window_width, @@ -530,24 +536,30 @@ class PyGameGUI: object_id="#start_button", ) - img = pygame.image.load( - ROOT_DIR / "pygame_2d_vis" / "gui_images" / f"continue_{self.language}.png" - ).convert_alpha() - - image_rect = img.get_rect() - img_width = self.buttons_width * 1.5 - img_height = img_width * (image_rect.height / image_rect.width) - new_dims = (img_width, img_height) - img = pygame.transform.smoothscale(img, new_dims) - image_rect = img.get_rect() - - image_rect.centery += 80 - self.press_a_image = pygame_gui.elements.UIImage( - image_rect, - img, - manager=self.manager, - anchors={"centerx": "centerx", "centery": "centery"}, - ) + if self.visualization_config["Gui"]["press_button_to_continue"]: + img = pygame.image.load( + ROOT_DIR + / "pygame_2d_vis" + / "gui_images" + / f"continue_{self.language}.png" + ).convert_alpha() + + image_rect = img.get_rect() + img_width = self.buttons_width * 1.5 + img_height = img_width * (image_rect.height / image_rect.width) + new_dims = (img_width, img_height) + img = pygame.transform.smoothscale(img, new_dims) + image_rect = img.get_rect() + + image_rect.centery += 80 + self.press_a_image = pygame_gui.elements.UIImage( + image_rect, + img, + manager=self.manager, + anchors={"centerx": "centerx", "centery": "centery"}, + ) + else: + self.press_a_image = None # self.press_a_image.set_dimensions(new_dims) if not self.CONNECT_WITH_STUDY_SERVER: @@ -826,13 +838,12 @@ class PyGameGUI: anchors={"centerx": "centerx", "top_target": self.level_name_label}, ) - scroll_height = ( + self.scroll_height = ( self.continue_button.get_abs_rect().top - self.text_recipes_label.get_abs_rect().bottom ) - self.scroll_width = self.window_width self.scroll_space_recipes = pygame_gui.elements.UIScrollingContainer( - relative_rect=pygame.Rect((0, 0), (self.scroll_width, scroll_height)), + relative_rect=pygame.Rect((0, 0), (self.window_width, self.scroll_height)), manager=self.manager, anchors={"centerx": "centerx", "top_target": self.text_recipes_label}, ) @@ -946,7 +957,7 @@ class PyGameGUI: ) self.scroll_width_completed_meals = self.window_width self.scroll_space_completed_meals = pygame_gui.elements.UIScrollingContainer( - relative_rect=pygame.Rect((0, 0), (self.scroll_width, scroll_height)), + relative_rect=pygame.Rect((0, 0), (self.window_width, scroll_height)), manager=self.manager, anchors={ "centerx": "centerx", @@ -1088,9 +1099,11 @@ class PyGameGUI: + self.other_elements ) for element in all_elements: - element.hide() + if element: + element.hide() for element in elements + self.on_all_screens: - element.show() + if element: + element.show() def setup_tutorial_screen(self): """Updates the tutorial screen with the current tutorial image and the continue button.""" @@ -1326,7 +1339,7 @@ class PyGameGUI: self.vis.draw_orders( screen=self.orders_image.image, state=state, - grid_size=self.buttons_height, + grid_size=self.screen_margin * 0.68, width=self.orders_container_width, height=self.screen_margin, config=self.visualization_config, @@ -1424,6 +1437,33 @@ class PyGameGUI: "translations.score", text_kwargs={"score": str(score)} ) + if self.switch_score_color: + self.count_frames_score_label += 1 + + duration_color_change = 90 + if score > self.last_score: + self.score_label.update_theming( + '{"colours": {"normal_text": "#03b706"}, "font": { "size": 20, "bold": 1}}' + ) + + self.count_frames_score_label = 0 + self.switch_score_color = True + elif score < self.last_score: + self.score_label.update_theming( + '{"colours": {"normal_text": "#e22312"}, "font": { "size": 20, "bold": 1}}' + ) + self.count_frames_score_label = 0 + self.switch_score_color = True + elif self.switch_score_color: + if self.count_frames_score_label >= duration_color_change: + self.score_label.update_theming( + '{"colours": {"normal_text": "#000000"}, "font": { "size": 20, "bold": 1}}' + ) + self.count_frames_score_label = 0 + self.switch_score_color = False + + self.last_score = score + def update_remaining_time(self, remaining_time: float): """Updates the remaining time label. @@ -1513,12 +1553,12 @@ class PyGameGUI: "translations.level_name", text_kwargs={"level": self.level_info["name"]} ) - graph_width = self.window_width * 0.55 + 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.scroll_width * 0.9 + container_width = self.window_width * 0.9 container_height = rows * row_height icon_size = row_height * 0.9 @@ -1542,8 +1582,11 @@ class PyGameGUI: meal = re.sub(r"(?<!^)(?=[A-Z])", " ", meal) positions = np.array(list(rg["layout"].values())) - unique_vals = np.unique(positions[:, 1]) - height = row_height * len(unique_vals) + + unique_x_vals = np.unique(positions[:, 0]) + + + height = row_height * len(np.unique(positions[:, 1])) graph_height = height * 0.9 graph_surface = pygame.Surface( @@ -1599,11 +1642,10 @@ class PyGameGUI: container=container, anchors={"centery": "centery", "right": "right"}, ) - last_recipes_labels.append(container) self.scroll_space_recipes.set_scrollable_area_dimensions( - (self.scroll_width * 0.95, container_height) + (self.window_width * 0.95, container_height) ) def setup_tutorial(self): @@ -2096,8 +2138,7 @@ class PyGameGUI: if event.type == pygame.JOYDEVICEREMOVED: self.remove_joystick(event) - # Press enter key or controller start button instead of mouse button press - if ( + if self.visualization_config["Gui"]["press_button_to_continue"] and ( event.type == pygame.JOYBUTTONDOWN and any([joy.get_button(7) for joy in self.joysticks.values()]) or (event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN) diff --git a/cooperative_cuisine/pygame_2d_vis/images/plate_dispenser.png b/cooperative_cuisine/pygame_2d_vis/images/plate_dispenser.png new file mode 100644 index 0000000000000000000000000000000000000000..acb313ac3c570111d0b38ee00351a8facf166f11 Binary files /dev/null and b/cooperative_cuisine/pygame_2d_vis/images/plate_dispenser.png differ diff --git a/cooperative_cuisine/pygame_2d_vis/images/wall.png b/cooperative_cuisine/pygame_2d_vis/images/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..9a7173aebc63094bae6d1618f48d6d188974dff8 Binary files /dev/null and b/cooperative_cuisine/pygame_2d_vis/images/wall.png differ diff --git a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json index a772563275311776808d4eaf1b377bdf4c072406..47579ce8519b642e48e78611225d37e7e037fdfd 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json @@ -13,7 +13,7 @@ "salad_recipe": "Rezept für Salat:", "recipes_in_this_level": "Rezepte in diesem Level:", "level_name": "%{level}", - "was_served": " wurde serviert", + "was_served": " wurde serviert.", "waiting_for_players": "WARTE AUF ANDERE SPIELER", "orders": "Bestellungen:", "score": "Punktestand: %{score}", @@ -25,7 +25,7 @@ "completed_level": "Level beendet: %{level}", "next_game": "Nächstes Level", "finish_study": "Studie Beenden", - "thank_you": "Vielen Dank an der Teilnahme!", + "thank_you": "Vielen Dank für der Teilnahme an der Studie!", "signal_supervisor": "Bitte gib dem Studienteilnehmer Bescheid, dass die Studie vorbei ist.", "Tomato Soup": "Tomatensuppe:", "Onion Soup": "Zwiebelsuppe:", @@ -34,6 +34,7 @@ "Fish And Chips": "Fish and Chips:", "Chips": "Pommes", "Salad": "Salat:", - "Fried Fish": "Backfisch:" + "Fried Fish": "Backfisch:", + "Burger With Chips": "Burger mit Pommes:" } } \ 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 3e1b3f392fb7cc071e60695cf5f89798eb793516..9f9c558bf762d3db59f73aacd127e71d19491853 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json @@ -13,7 +13,7 @@ "salad_recipe": "Salad recipe:", "recipes_in_this_level": "Recipes in this level:", "level_name": "%{level}", - "was_served": " was served", + "was_served": " was served.", "waiting_for_players": "WAITING FOR OTHER PLAYERS", "orders": "Orders:", "score": "Score: %{score}", @@ -34,6 +34,7 @@ "Fish And Chips": "Fish and Chips:", "Chips": "Chips", "Salad": "Salad:", - "Fried Fish": "Fried Fish:" + "Fried Fish": "Fried Fish:", + "Burger With Chips": "Burger with Chips:" } } \ No newline at end of file diff --git a/cooperative_cuisine/pygame_2d_vis/visualization.yaml b/cooperative_cuisine/pygame_2d_vis/visualization.yaml index 6f1fbe54331287555d4c9e25f4e2fd1bdf4fdb41..9b2dcffe094b750fa66085196ec715fd9d1a5530 100644 --- a/cooperative_cuisine/pygame_2d_vis/visualization.yaml +++ b/cooperative_cuisine/pygame_2d_vis/visualization.yaml @@ -5,9 +5,10 @@ Gui: use_player_cook_sprites: True show_interaction_range: False show_counter_centers: False + press_button_to_continue: False GameWindow: - screen_margin: 100 + screen_margin_proportion: 0.15 min_width: 900 min_height: 600 buttons_width: 180 @@ -35,6 +36,16 @@ Counter: path: images/counter5.png size: 1 +EdgeCounter: + parts: + # - type: rect + # height: 1 + # width: 1 + # color: whitesmoke + - type: image + path: images/wall.png + size: 1 + CuttingBoard: parts: - type: image @@ -44,11 +55,10 @@ CuttingBoard: PlateDispenser: - parts: [ ] -# - type: rect -# height: 0.95 -# width: 0.95 -# color: cadetblue1 + parts: + - type: image + path: images/plate_dispenser.png + size: 1 Trashcan: parts: @@ -113,11 +123,11 @@ ServingWindow: size: 0.8 center_offset: [ 0, -0.02 ] rotate_image: False - - type: image - path: images/bell_gold.png - size: 0.5 - center_offset: [ -0.2, -0.05 ] - rotate_image: False +# - type: image +# path: images/bell_gold.png +# size: 0.5 +# center_offset: [ -0.2, -0.05 ] +# rotate_image: False Stove: parts: @@ -376,6 +386,18 @@ FishAndChips: size: 0.8 center_offset: [ +0.2, 0 ] +BurgerWithChips: + parts: + - type: image + path: images/fries2.png + size: 0.8 + center_offset: [ -0.1, 0 ] + - type: image + path: images/burger.png + size: 0.8 + center_offset: [ +0.2, 0 ] + + Dough: parts: - type: image diff --git a/cooperative_cuisine/study_server.py b/cooperative_cuisine/study_server.py index 5f6a0cfc7e01ddb5255b0260cac8a03a69638f1b..657c4c00aa6a3624cd963553a4a226e068b3314f 100644 --- a/cooperative_cuisine/study_server.py +++ b/cooperative_cuisine/study_server.py @@ -15,7 +15,6 @@ import asyncio import json import logging import os -import random import signal import subprocess import uuid @@ -68,6 +67,8 @@ class LevelConfig(BaseModel): """Path to the item info file.""" config_overwrite: dict[str, Any] | None = None """Overwrite parts of the `environment_config`""" + seed: int + """Seed for the level.""" class LevelInfo(BaseModel): @@ -106,13 +107,13 @@ class Study: game_port: The port number of the game server. """ with open(study_config_path, "r") as file: - env_config_f = file.read() + study_config_f = file.read() self.study_id = uuid.uuid4().hex[:UUID_CUTOFF] """Unique ID of the study.""" self.study_config: StudyConfig = yaml.load( - str(env_config_f), Loader=yaml.Loader + str(study_config_f), Loader=yaml.Loader ) """Configuration for the study which layouts, env_configs and item infos are used for the study levels.""" self.levels: list[dict] = self.study_config["levels"] @@ -201,7 +202,7 @@ class Study: deep_update(self.current_config, level.config_overwrite) environment_config = yaml.dump(self.current_config) - seed = int(random.random() * 1000000) + seed = level.seed creation_json = CreateEnvironmentConfig( manager_id=study_manager.server_manager_id, number_players=self.study_config["num_players"] diff --git a/cooperative_cuisine/validation.py b/cooperative_cuisine/validation.py index e6b615cb68126dcf457f5a7f5d6d534427bef75c..cea549750ef51f2cbfc6b323cd8a3c6d14fb3eca 100644 --- a/cooperative_cuisine/validation.py +++ b/cooperative_cuisine/validation.py @@ -2,6 +2,7 @@ import hashlib import json import os +import uuid import warnings from typing import TypedDict, Tuple, Iterator, Set @@ -144,18 +145,19 @@ class Validation: graph.add_edge(need, current) elif len(current_info.needs) > 1: - for idx, item_name in enumerate(current_info.needs): - add_queue.append(f"{item_name}_{idx}") + for item_name in current_info.needs: + unique_id = uuid.uuid4().hex + add_queue.append(f"{item_name}_{unique_id}") if current_info.equipment and current_info.equipment.equipment: equip_id = f"{current_info.equipment.name}_{current_index}" equip_equip_id = f"{current_info.equipment.equipment.name}_{current_index}" graph.add_edge(equip_equip_id, current) graph.add_edge(equip_id, equip_equip_id) - graph.add_edge(f"{item_name}_{idx}", equip_id) + graph.add_edge(f"{item_name}_{unique_id}", equip_id) else: graph.add_edge( - f"{item_name}_{idx}", + f"{item_name}_{unique_id}", current, )