diff --git a/cooperative_cuisine/configs/dummy_environment_config.yaml b/cooperative_cuisine/configs/dummy_environment_config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1096832b82e8e43f191fab57177706264793bc79 --- /dev/null +++ b/cooperative_cuisine/configs/dummy_environment_config.yaml @@ -0,0 +1,103 @@ +plates: + clean_plates: 2 + dirty_plates: 1 + plate_delay: [ 5, 10 ] + # range of seconds until the dirty plate arrives. + +game: + time_limit_seconds: 300 + undo_dispenser_pickup: true + validate_recipes: false + + +layout_chars: + _: Free + hash: Counter # # + equal: EdgeCounter # = + A: Agent + pipe: Extinguisher + P: PlateDispenser + C: CuttingBoard + X: Trashcan + $: ServingWindow + S: Sink + +: SinkAddon + at: Plate # @ just a clean plate on a counter + U: Pot # with Stove + Q: Pan # with Stove + O: Peel # with Oven + F: Basket # with DeepFryer + T: Tomato + N: Onion # oNioN + L: Lettuce + K: Potato # Kartoffel + I: Fish # fIIIsh + D: Dough + E: Cheese # chEEEse + G: Sausage # sausaGe + B: Bun + M: Meat + question: Counter # ? mushroom + ↓: Counter + ^: Counter + right: Counter + left: Counter + wave: Free # ~ Water + minus: Free # - Ice + dquote: Counter # " wall/truck + p: Counter # second plate return ?? + + + +orders: + meals: + all: true + # if all: false -> only orders for these meals are generated + # TODO: what if this list is empty? + list: + # - TomatoSoup + # - OnionSoup + # - Salad + - FriedFish + order_gen_class: !!python/name:cooperative_cuisine.orders.RandomOrderGeneration '' + # the class to that receives the kwargs. Should be a child class of OrderGeneration in orders.py + order_gen_kwargs: + order_duration_random_func: + # how long should the orders be alive + # 'random' library call with getattr, kwargs are passed to the function + func: uniform + kwargs: + a: 40 + b: 60 + max_orders: 6 + # maximum number of active orders at the same time + num_start_meals: 2 + # number of orders generated at the start of the environment + sample_on_dur_random_func: + # 'random' library call with getattr, kwargs are passed to the function + func: uniform + kwargs: + a: 10 + b: 20 + sample_on_serving: false + # Sample the delay for the next order only after a meal was served. + serving_not_ordered_meals: true + # can meals that are not ordered be served / dropped on the serving window + +player_config: + radius: 0.4 + speed_units_per_seconds: 6 + interaction_range: 1.6 + restricted_view: False + view_angle: 70 + view_range: 4 # in grid units, can be "null" + +effect_manager: + FireManager: + class: !!python/name:cooperative_cuisine.effects.FireEffectManager '' + kwargs: + spreading_duration: [ 5, 10 ] + fire_burns_ingredients_and_meals: true + + +hook_callbacks: [ ] diff --git a/cooperative_cuisine/configs/environment_config_no_validation.yaml b/cooperative_cuisine/configs/environment_config_no_validation.yaml index 47fdde17761438f3afd11c81769cf3313bd5dd07..51cbca13a5b19842427432780561616c4210a6d9 100644 --- a/cooperative_cuisine/configs/environment_config_no_validation.yaml +++ b/cooperative_cuisine/configs/environment_config_no_validation.yaml @@ -13,6 +13,7 @@ game: layout_chars: _: Free hash: Counter # # + equal: EdgeCounter # = A: Agent pipe: Extinguisher P: PlateDispenser @@ -47,6 +48,7 @@ layout_chars: p: Counter # second plate return ?? + orders: meals: all: true diff --git a/cooperative_cuisine/configs/layouts/zztest_layouts/screenshot_tutorial.layout b/cooperative_cuisine/configs/layouts/zztest_layouts/screenshot_tutorial.layout index aeca0151c18ed089f5f8ad0ed583923516a7a612..068eb9bab45600e61c1bd7a1c3e81cad5397711e 100644 --- a/cooperative_cuisine/configs/layouts/zztest_layouts/screenshot_tutorial.layout +++ b/cooperative_cuisine/configs/layouts/zztest_layouts/screenshot_tutorial.layout @@ -1,11 +1,11 @@ PULTI##### __________ -___=___=_ +___=___#__ ___#___#__ ___#___#__ ___#___#__ ___=___=__ __________ -##C##$#S+ +##C##$#S+_ __________ __________ diff --git a/cooperative_cuisine/pygame_2d_vis/drawing.py b/cooperative_cuisine/pygame_2d_vis/drawing.py index 8cb8fcb9c9925f50666116fc8be1925d6dc3866a..5375ad8530bbaa729b68f60826d6b04d97ade2d4 100644 --- a/cooperative_cuisine/pygame_2d_vis/drawing.py +++ b/cooperative_cuisine/pygame_2d_vis/drawing.py @@ -113,9 +113,10 @@ class Visualizer: self.font = pygame.font.SysFont("Arial", 20) self.init_get_state_image = False - self.observation_screen = pygame.display.set_mode( - (100, 100), flags=pygame.HIDDEN - ) + # TODO FIX THIS + # self.observation_screen = pygame.display.set_mode( + # (100, 100), flags=pygame.HIDDEN + # ) self.grid_size = 48 diff --git a/cooperative_cuisine/pygame_2d_vis/grid.py b/cooperative_cuisine/pygame_2d_vis/grid.py new file mode 100644 index 0000000000000000000000000000000000000000..85b927e29d4f4c936fca5c3d81355694d619418d --- /dev/null +++ b/cooperative_cuisine/pygame_2d_vis/grid.py @@ -0,0 +1,97 @@ +import uuid +from pprint import pprint + +import matplotlib.pyplot as plt +import numpy as np +import yaml +from collections import defaultdict + +from cooperative_cuisine import ROOT_DIR +from cooperative_cuisine.counter_factory import convert_words_to_chars +from cooperative_cuisine.items import ItemInfo, ItemType +from cooperative_cuisine.pygame_2d_vis.drawing import Visualizer + +dummy_env_config_path = ROOT_DIR / "configs" / "dummy_environment_config.yaml" +with open(dummy_env_config_path) as c: + dummy_env_config = yaml.load(c, Loader=yaml.FullLoader) +character_map = dummy_env_config["layout_chars"] +character_map = convert_words_to_chars(character_map) + + +def layout_thumbnail(layout_path, vis, max_size, item_lookup): + with open(layout_path) as file: + layout = file.read() + + colors = defaultdict(lambda: 0) + + v = list(set(character_map.values())) + idx_free = v.index("Free") + zero = v[0] + v[0] = "Free" + v[idx_free] = zero + + colors.update({c: i for i, c in enumerate(v)}) + + grid = [] + counter = [] + player = [] + x = 0 + for line in layout.split("\n"): + row = [] + if line.startswith(";") or not line: + continue + for y, char in enumerate(line.strip()): + t = character_map[char] + row.append(colors[t]) + match t: + case "Agent": + player.append([float(y), float(x)]) + case "Free" | "Water" | "Ice" | "Lava": + pass + case _: + counter.append(([float(y), float(x)], t)) + grid.append(row) + x += 1 + + image = np.array(grid).T + + def map_t(t): + if t in {"Counter", "PlateDispenser", "CuttingBoard", "Trashcan", "ServingWindow", "Sink", "SinkAddon"}: + return t + if t in item_lookup: + i = item_lookup[t] + if i.type == ItemType.Equipment and i.equipment: + return i.equipment.name + if i.type == ItemType.Ingredient: + return t + "Dispenser" + return "Counter" + + def map_t_occupied(t): + if t in {"Counter", "PlateDispenser", "CuttingBoard", "Trashcan", "ServingWindow", "Sink", "SinkAddon"}: + return None + if t in item_lookup: + i = item_lookup[t] + if i.type == ItemType.Equipment and i.equipment: + return {"category": "ItemCookingEquipment", "type": t, "content_list": [], "content_ready": None, + "active_effects": []} + if i.type == ItemType.Ingredient: + return {"category": "Item", "type": t, "content_list": [], "content_ready": None, "active_effects": []} + return None + + state = {"players": [ + {"id": str(i), "pos": p, "facing_direction": [0, 1], "holding": None, "current_nearest_counter_pos": None, + "current_nearest_counter_id": None} for i, p in enumerate(player)], + "counters": [{"id": uuid.uuid4().hex, "pos": pos, "orientation": [0, 1], "type": map_t(t), + "occupied_by": map_t_occupied(t), "active_effects": []} for pos, t in counter], + "kitchen": {"width": image.shape[0], "height": image.shape[1]}, + } + + # im = viz.get_state_image(grid_size=32, state=state).transpose((1, 0, 2)) + + grid_size = max_size / max(state["kitchen"]["width"], state["kitchen"]["height"]) + vis.set_grid_size(grid_size) + surf = vis.draw_gamescreen(state, []) + return surf + + # plt.imshow(im) + # plt.show() diff --git a/cooperative_cuisine/pygame_2d_vis/gui.py b/cooperative_cuisine/pygame_2d_vis/gui.py index 0742e326a25fd4a005d21bddb7ed92199b9a47b8..aab8ff64bdeb9d7c3f6722b57fb06f522c942628 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui.py +++ b/cooperative_cuisine/pygame_2d_vis/gui.py @@ -17,17 +17,20 @@ import pygame import pygame_gui import requests import yaml +from minerl.env.test_fake_env import color from pygame import mixer from websockets.sync.client import connect from cooperative_cuisine import ROOT_DIR from cooperative_cuisine.action import ActionType, InterActionData, Action from cooperative_cuisine.argument_parser import create_gui_parser +from cooperative_cuisine.environment import Environment from cooperative_cuisine.game_server import ( CreateEnvironmentConfig, WebsocketMessage, PlayerRequestType, ) +from cooperative_cuisine.items import ItemInfo from cooperative_cuisine.pygame_2d_vis.drawing import Visualizer, CacheFlags from cooperative_cuisine.pygame_2d_vis.game_colors import colors from cooperative_cuisine.server_results import PlayerInfo @@ -35,6 +38,7 @@ from cooperative_cuisine.state_representation import StateRepresentation from cooperative_cuisine.utils import ( setup_logging, load_config_files, ) +from cooperative_cuisine.pygame_2d_vis.grid import layout_thumbnail class MenuStates(Enum): @@ -219,6 +223,8 @@ class PyGameGUI: self.layout_file_paths = sorted(list(self.layout_file_paths_dict.keys())) self.current_layout_idx = 0 + self.selected_layout_path = self.layout_file_paths_dict["basic.layout"] + self.last_state: StateRepresentation self.player_info = {"0": {"name": "0"}} @@ -710,6 +716,29 @@ class PyGameGUI: }, ) + self.scroll_space_layouts = pygame_gui.elements.UIScrollingContainer( + relative_rect=pygame.Rect((0, 0), (self.buttons_width * 1.2, self.window_height)), + manager=self.manager, + anchors={"top": "top", "left": "left"}, + ) + self.setup_layout_selection() + + rect = pygame.Rect( + (0, 0), + ( + self.window_width * 0.5, + self.buttons_height, + ), + ) + self.selected_layout_label = pygame_gui.elements.UILabel( + text="translations.selected_layout", + text_kwargs={"layout": "basic.layout"}, + relative_rect=rect, + manager=self.manager, + object_id="#selected_layout", + anchors={"centerx": "centerx", "bottom_target": self.start_button}, + ) + ######################################################################## # Tutorial screen ######################################################################## @@ -722,6 +751,7 @@ class PyGameGUI: manager=self.manager, anchors={"centerx": "centerx", "bottom": "bottom"}, ) + # TODO update on pregame button_rect = pygame.Rect((0, 0), (220, 80)) button_rect.bottom = -self.elements_margin @@ -919,6 +949,16 @@ class PyGameGUI: anchors={"centery": "centery", "right": "right"}, ) + rect = pygame.Rect((0, 0), (self.buttons_width, self.buttons_height)) + rect.bottomright = (0, 0) + self.return_to_main_button = pygame_gui.elements.UIButton( + relative_rect=rect, + text="translations.return_to_main_menu", + manager=self.manager, + object_id="#main_menu_button", + anchors={"right": "right", "bottom": "bottom"}, + ) + ######################################################################## # PostGame screen ######################################################################## @@ -1055,14 +1095,10 @@ class PyGameGUI: self.press_a_image, self.quit_button, self.fullscreen_button, - self.continue_button_two + self.continue_button_two, + self.scroll_space_layouts ] - if not self.CONNECT_WITH_STUDY_SERVER: - self.start_screen_elements.append(self.layout_selection) - else: - self.other_elements.append(self.layout_selection) - self.tutorial_screen_elements = [ self.tutorial_image, self.continue_button, @@ -1085,6 +1121,15 @@ class PyGameGUI: self.wait_players_label, ] + if not self.CONNECT_WITH_STUDY_SERVER: + self.start_screen_elements.append(self.scroll_space_layouts) + self.game_screen_elements.append(self.return_to_main_button) + self.other_elements.append(self.layout_selection) + else: + self.other_elements.append(self.scroll_space_layouts) + self.other_elements.append(self.return_to_main_button) + self.other_elements.append(self.layout_selection) + self.postgame_screen_elements = [ self.score_conclusion, self.scroll_space_completed_meals, @@ -1122,6 +1167,105 @@ class PyGameGUI: if element: element.show() + def setup_layout_selection(self): + """Sets up the layout selection screen with the layout selection dropdown.""" + + added_layouts = [] + layout_display_width = self.buttons_width + layout_display_height = self.buttons_height * 3 + overall_scroll_height = layout_display_height * len(self.layout_file_paths_dict) + self.layouts_main_container = pygame_gui.elements.UIPanel( + relative_rect=pygame.Rect( + (0, 0), + ( + self.buttons_width, + overall_scroll_height, + ), + ), + object_id="#graph_container", + manager=self.manager, + container=self.scroll_space_layouts, + # anchors={"centerx": "centerx", "top": "top"}, + ) + + self.scroll_space_layouts.set_scrollable_area_dimensions( + (self.buttons_width, overall_scroll_height) + ) + + with open(ROOT_DIR / "pygame_2d_vis" / "visualization.yaml", "r") as file: + visualization_config = yaml.safe_load(file) + + vis = Visualizer(visualization_config) + vis.cache_flags = CacheFlags.NONE + vis.create_player_colors(10) + + with open(ROOT_DIR / "configs" / "item_info.yaml") as file: + item_info = file.read() + item_lookup = yaml.safe_load(item_info) + for item_name in item_lookup: + item_lookup[item_name] = ItemInfo(name=item_name, **item_lookup[item_name]) + for item_name, item_info in item_lookup.items(): + if item_info.equipment: + item_info.equipment = item_lookup[item_info.equipment] + + self.layout_buttons = {} + + for idx, layout_name in enumerate( + sorted(self.layout_file_paths_dict.keys(), key=lambda x: "000000" if x == "basic.layout" else x)): + if idx == 0: + anchors = {"centerx": "centerx", "top": "top"} + else: + anchors = { + "centerx": "centerx", + "top_target": added_layouts[idx - 1], + } + + container = pygame_gui.elements.UIPanel( + relative_rect=pygame.Rect( + (0, 0), + ( + layout_display_width, + layout_display_height, + ), + ), + object_id="#graph_container", + manager=self.manager, + container=self.layouts_main_container, + anchors=anchors, + ) + + layout_path = self.layout_file_paths_dict[layout_name] + + layout_surface = layout_thumbnail(layout_path, vis, layout_display_height * 0.9, item_lookup) + layout_image = pygame_gui.elements.UIImage( + relative_rect=layout_surface.get_rect(), + image_surface=layout_surface, + manager=self.manager, + container=container, + anchors={"center": "center"}, + ) + + w, h = layout_image.get_abs_rect().size + rect = pygame.Rect( + (0, 0), + (w + 2, h + 2), + ) + button = pygame_gui.elements.UIButton( + text=layout_name.replace(".layout", ""), + relative_rect=rect, + manager=self.manager, + container=container, + object_id="#layout_button", + anchors={"centerx": "centerx", "centery": "centery"}, + ) + button.update_theming( + '{"colours": {"normal_bg": "rgba(0, 0, 0, 0)"}}' + ) + + self.layout_buttons[button] = layout_name + + added_layouts.append(container) + def setup_tutorial_screen(self): """Updates the tutorial screen with the current tutorial image and the continue button.""" self.show_screen_elements(self.tutorial_screen_elements) @@ -1504,13 +1648,12 @@ class PyGameGUI: environment_config_path = ROOT_DIR / "configs" / "tutorial_env_config.yaml" else: environment_config_path = ROOT_DIR / "configs" / "environment_config.yaml" - layout_path = self.layout_file_paths_dict[ - self.layout_selection.selected_option - ] + layout_path = self.selected_layout_path # layout_path = self.layout_file_paths[self.current_layout_idx] item_info_path = ROOT_DIR / "configs" / "item_info.yaml" - item_info, layout, environment_config, _ = load_config_files(item_info_path, layout_path, environment_config_path) + item_info, layout, environment_config, _ = load_config_files(item_info_path, layout_path, + environment_config_path) num_players = 1 if tutorial else self.number_players seed = int(random.random() * 100000) @@ -1849,6 +1992,7 @@ class PyGameGUI: self.init_ui_elements() self.set_game_size() self.update_screen_elements() + self.setup_layout_selection() def reset_gui_values(self): """Reset the values of the GUI elements to their default values. Default values are defined here.""" @@ -2086,6 +2230,14 @@ class PyGameGUI: self.split_players = False case self.split_players_button: self.split_players = not self.split_players + case other: + if button in self.layout_buttons: + selected_layout = self.layout_buttons[button] + self.selected_layout_path = self.layout_file_paths_dict[selected_layout] + print("SELECTED LAYOUT", self.selected_layout_path) + self.selected_layout_label.set_text( + "translations.selected_layout", text_kwargs={"layout": selected_layout} + ) ############################################ @@ -2106,6 +2258,15 @@ class PyGameGUI: ############################################ + case MenuStates.Game: + if button in [self.return_to_main_button]: + if not self.CONNECT_WITH_STUDY_SERVER: + self.menu_state = MenuStates.Start + self.stop_game_on_server("Return to main button") + self.disconnect_websockets() + + ############################################ + case MenuStates.PostGame: if button in [ self.finish_study_button, @@ -2190,6 +2351,7 @@ class PyGameGUI: self.continue_button_two, self.finish_study_button, self.next_game_button, + self.return_to_main_button ]: self.update_screen_elements() elif self.menu_state == MenuStates.Start: diff --git a/cooperative_cuisine/pygame_2d_vis/gui_theme.json b/cooperative_cuisine/pygame_2d_vis/gui_theme.json index ae44b16f07da3c54c36b65f20804eb8e2611508e..989d929004a6eaa498f95cde892a2e55b216cd74 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui_theme.json +++ b/cooperative_cuisine/pygame_2d_vis/gui_theme.json @@ -231,5 +231,12 @@ "misc": { "text_horiz_alignment": "left" } + }, + "#layout_label": { + "font": { + "size": 20, + "bold": 1, + "colour": "#000000" + } } } diff --git a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json index 43fdaa6662c95786559d0e500e071002184b8c5f..ece17718f16907b024446819aeec94aaa669289c 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.de.json @@ -37,6 +37,8 @@ "Salad": "Salat:", "Fried Fish": "Backfisch:", "Burger With Chips": "Burger mit Pommes:", - "recipe_score": "Punkte: %{score}" + "recipe_score": "Punkte: %{score}", + "return_to_main_menu": "Hauptmenü", + "selected_layout": "Layout: %{layout}" } } diff --git a/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json b/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json index cd705584605ea2e884f455d978f60b520016405f..4c72cb91ebbc691f84fc03bbaea0b294bcbb974a 100644 --- a/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json +++ b/cooperative_cuisine/pygame_2d_vis/locales/translations.en.json @@ -36,6 +36,8 @@ "Salad": "Salad:", "Fried Fish": "Fried Fish:", "Burger With Chips": "Burger with Chips:", - "recipe_score": "Points: %{score}" + "recipe_score": "Points: %{score}", + "return_to_main_menu": "Main Menu", + "selected_layout": "Layout: %{layout}" } } \ No newline at end of file