From 889364ac96dc0840a061529b73c6c7a33a67afcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Schr=C3=B6der?= <fschroeder@techfak.uni-bielefeld.de> Date: Sat, 3 Feb 2024 01:15:19 +0100 Subject: [PATCH] Add waste progress attribute and burnt item visualization A `waste_progress` attribute has been added to track the progress of burnt items. Changes also include the visualization of burnt items in greyscale and additional definitions for various types of burnt meals in Overcooked Simulator. This update enhances both the gameplay and its realism, indicating when an item is overcooked or burnt. --- .../game_content/item_info.yaml | 38 +++++++++ .../game_content/item_info_debug.yaml | 39 +++++++++ overcooked_simulator/game_items.py | 37 ++++++--- overcooked_simulator/gui_2d_vis/drawing.py | 80 ++++++++++++++++--- overcooked_simulator/state_representation.py | 1 + 5 files changed, 169 insertions(+), 26 deletions(-) diff --git a/overcooked_simulator/game_content/item_info.yaml b/overcooked_simulator/game_content/item_info.yaml index a6458c63..639f8551 100644 --- a/overcooked_simulator/game_content/item_info.yaml +++ b/overcooked_simulator/game_content/item_info.yaml @@ -176,3 +176,41 @@ Pizza: needs: [ PizzaBase, ChoppedTomato, GratedCheese, ChoppedSausage ] seconds: 7.0 equipment: Peel + +# -------------------------------------------------------------------------------- + +BurntCookedPatty: + type: Waste + seconds: 5.0 + needs: [ CookedPatty ] + equipment: Pan + +BurntChips: + type: Waste + seconds: 5.0 + needs: [ Chips ] + equipment: Basket + +BurntFriedFish: + type: Waste + seconds: 5.0 + needs: [ FriedFish ] + equipment: Basket + +BurntTomatoSoup: + type: Waste + needs: [ TomatoSoup ] + seconds: 6.0 + equipment: Pot + +BurntOnionSoup: + type: Waste + needs: [ OnionSoup ] + seconds: 6.0 + equipment: Pot + +BurntPizza: + type: Waste + needs: [ Pizza ] + seconds: 7.0 + equipment: Peel \ No newline at end of file diff --git a/overcooked_simulator/game_content/item_info_debug.yaml b/overcooked_simulator/game_content/item_info_debug.yaml index c2282253..6340f564 100644 --- a/overcooked_simulator/game_content/item_info_debug.yaml +++ b/overcooked_simulator/game_content/item_info_debug.yaml @@ -177,3 +177,42 @@ Pizza: needs: [ PizzaBase, ChoppedTomato, GratedCheese, ChoppedSausage ] seconds: 0.1 equipment: Peel + +# -------------------------------------------------------------------------------- + +BurntCookedPatty: + type: Waste + seconds: 5.0 + needs: [ CookedPatty ] + equipment: Pan + +BurntChips: + type: Waste + seconds: 5.0 + needs: [ Chips ] + equipment: Basket + +BurntFriedFish: + type: Waste + seconds: 5.0 + needs: [ FriedFish ] + equipment: Basket + +BurntTomatoSoup: + type: Waste + needs: [ TomatoSoup ] + seconds: 6.0 + equipment: Pot + +BurntOnionSoup: + type: Waste + needs: [ OnionSoup ] + seconds: 6.0 + equipment: Pot + +BurntPizza: + type: Waste + needs: [ Pizza ] + seconds: 7.0 + equipment: Peel + diff --git a/overcooked_simulator/game_items.py b/overcooked_simulator/game_items.py index 9a406367..163a2d13 100644 --- a/overcooked_simulator/game_items.py +++ b/overcooked_simulator/game_items.py @@ -45,6 +45,8 @@ class ItemType(Enum): """All combined ingredients that can be served.""" Equipment = "Equipment" """All counters and cooking equipments.""" + Waste = "Waste" + """Burnt ingredients and meals.""" @dataclasses.dataclass @@ -125,6 +127,8 @@ class Item: """The equipment with which the item was last progressed.""" self.progress_percentage: float = 0.0 """The current progress percentage of the item if it is progress-able.""" + self.waste_progress: bool = False + """Whether the progress will produce waste.""" self.uuid: str = uuid.uuid4().hex if uid is None else uid """A unique identifier for the item. Useful for GUIs that handles specific asset instances.""" @@ -167,6 +171,7 @@ class Item: """Reset the progress.""" self.progress_equipment = None self.progress_percentage = 0.0 + self.waste_progress = False def to_dict(self) -> dict: """For the state representation. Only the relevant attributes are put into the dict.""" @@ -175,6 +180,7 @@ class Item: "category": self.item_category, "type": self.name, "progress_percentage": self.progress_percentage, + "waste_progress": self.waste_progress, } @@ -231,19 +237,7 @@ class CookingEquipment(Item): else: self.content_list.append(other) - ingredients = collections.Counter(item.name for item in self.content_list) - for result, transition in self.transitions.items(): - if ingredients == transition.recipe: - if transition.seconds == 0: - self.content_ready = Item(name=result, item_info=transition) - else: - self.active_transition = { - "seconds": transition.seconds, - "result": Item(name=result, item_info=transition), - } - break - else: - self.content_ready = None + self.check_active_transition() return return_value def can_progress(self) -> bool: @@ -256,9 +250,26 @@ class CookingEquipment(Item): if self.progress_percentage == 1.0: self.content_list = [self.active_transition["result"]] self.reset() + self.check_active_transition() # todo set active transition for fire/burnt? + def check_active_transition(self): + ingredients = collections.Counter(item.name for item in self.content_list) + for result, transition in self.transitions.items(): + if ingredients == transition.recipe: + if transition.seconds == 0: + self.content_ready = Item(name=result, item_info=transition) + else: + self.active_transition = { + "seconds": transition.seconds, + "result": Item(name=result, item_info=transition), + } + self.waste_progress = transition.type == ItemType.Waste + break + else: + self.content_ready = None + def reset_content(self): """Reset the content attributes after the content was picked up from the equipment.""" self.content_list = [] diff --git a/overcooked_simulator/gui_2d_vis/drawing.py b/overcooked_simulator/gui_2d_vis/drawing.py index 0690e22d..00d4e32f 100644 --- a/overcooked_simulator/gui_2d_vis/drawing.py +++ b/overcooked_simulator/gui_2d_vis/drawing.py @@ -24,6 +24,21 @@ SHOW_INTERACTION_RANGE = False SHOW_COUNTER_CENTERS = False +def grayscale(img): + arr = pygame.surfarray.pixels3d(img) + mean_arr = np.dot(arr[:, :, :], [0.216, 0.587, 0.144]) + mean_arr3d = mean_arr[..., np.newaxis] + new_arr = np.repeat(mean_arr3d[:, :, :], 3, axis=2) + new_arr = new_arr.astype(np.int8) + surface = pygame.Surface(new_arr.shape[0:2], pygame.SRCALPHA, 32) + + # Copy the rgb part of array to the new surface. + pygame.pixelcopy.array_to_surface(surface, new_arr) + surface_alpha = np.array(surface.get_view("A"), copy=False) + surface_alpha[:, :] = pygame.surfarray.pixels_alpha(img) + return surface + + def create_polygon(n, length): if n == 1: return np.array([0, 0]) @@ -112,16 +127,27 @@ class Visualizer: size: float, pos: npt.NDArray, rot_angle=0, + burnt: bool = False, ): cache_entry = f"{img_path}" - if cache_entry in self.image_cache_dict.keys(): - image = self.image_cache_dict[cache_entry] + if cache_entry + ("-burnt" if burnt else "") in self.image_cache_dict: + image = self.image_cache_dict[cache_entry + ("-burnt" if burnt else "")] else: - image = pygame.image.load( - ROOT_DIR / "gui_2d_vis" / img_path - ).convert_alpha() - self.image_cache_dict[cache_entry] = image - + if burnt: + if cache_entry in self.image_cache_dict: + normal_image = self.image_cache_dict[cache_entry] + else: + normal_image = pygame.image.load( + ROOT_DIR / "gui_2d_vis" / img_path + ).convert_alpha() + self.image_cache_dict[cache_entry] = normal_image + image = grayscale(normal_image) + self.image_cache_dict[cache_entry + "-burnt"] = image + else: + image = pygame.image.load( + ROOT_DIR / "gui_2d_vis" / img_path + ).convert_alpha() + self.image_cache_dict[cache_entry] = image image = pygame.transform.scale(image, (size, size)) if rot_angle != 0: image = pygame.transform.rotate(image, rot_angle) @@ -230,6 +256,7 @@ class Visualizer: grid_size: float, parts: list[dict[str]], scale: float = 1.0, + burnt: bool = False, ): """Draws an item, based on its visual parts specified in the visualization config. @@ -254,6 +281,7 @@ class Visualizer: part["path"], part["size"] * scale * grid_size, draw_pos, + burnt=burnt, ) case "rect": height = part["height"] * grid_size @@ -293,34 +321,58 @@ class Visualizer: plate: item is on a plate (soup are is different on a plate and pot) """ - if not isinstance(item, list): # can we remove this check? - if item["type"] in self.config: + if not isinstance(item, list): # can we remove this check?w + if item["type"] in self.config or ( + item["type"].startswith("Burnt") + and item["type"].replace("Burnt", "") in self.config + ): item_key = item["type"] if "Soup" in item_key and plate: item_key += "Plate" + if item_key.startswith("Burnt"): + item_key = item_key.replace("Burnt", "") self.draw_thing( pos=pos, parts=self.config[item_key]["parts"], scale=scale, screen=screen, grid_size=grid_size, + burnt=item["type"].startswith("Burnt"), ) # if "progress_percentage" in item and item["progress_percentage"] > 0.0: + if item["waste_progress"]: + percentage = 1 - item["progress_percentage"] + else: + percentage = item["progress_percentage"] self.draw_progress_bar( - screen, pos, item["progress_percentage"], grid_size=grid_size + screen, + pos, + percentage, + grid_size=grid_size, + attention=item["waste_progress"], ) if ( "content_ready" in item and item["content_ready"] - and item["content_ready"]["type"] in self.config + and ( + item["content_ready"]["type"] in self.config + or ( + item["content_ready"]["type"].startswith("Burnt") + and item["content_ready"]["type"].replace("Burnt", "") + in self.config + ) + ) ): self.draw_thing( pos=pos, - parts=self.config[item["content_ready"]["type"]]["parts"], + parts=self.config[item["content_ready"]["type"].replace("Burnt", "")][ + "parts" + ], screen=screen, grid_size=grid_size, + burnt=item["type"].startswith("Burnt"), ) elif "content_list" in item and item["content_list"]: triangle_offsets = create_polygon(len(item["content_list"]), length=10) @@ -341,6 +393,7 @@ class Visualizer: pos: npt.NDArray[float], percent: float, grid_size: float, + attention: bool = False, ): """Visualize progress of progressing item as a green bar under the item.""" bar_pos = pos - (grid_size / 2) @@ -353,7 +406,7 @@ class Visualizer: progress_width, bar_height, ) - pygame.draw.rect(screen, colors["green1"], progress_bar) + pygame.draw.rect(screen, colors["red" if attention else "green1"], progress_bar) def draw_counter( self, screen: pygame.Surface, counter_dict: dict, grid_size: float @@ -491,6 +544,7 @@ class Visualizer: percent=percentage, screen=order_screen, grid_size=grid_size, + attention=percentage < 0.25, ) orders_rect = order_screen.get_rect() diff --git a/overcooked_simulator/state_representation.py b/overcooked_simulator/state_representation.py index 47ddf287..1949e624 100644 --- a/overcooked_simulator/state_representation.py +++ b/overcooked_simulator/state_representation.py @@ -20,6 +20,7 @@ class ItemState(TypedDict): category: Literal["Item"] | Literal["ItemCookingEquipment"] type: str progress_percentage: float | int + waste_progress: bool # add ItemType Meal ? -- GitLab