From d6cd453aa6fa89b11bf8abba5969ee546ed211be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Schr=C3=B6der?= <fschroeder@techfak.uni-bielefeld.de> Date: Wed, 17 Jan 2024 14:04:19 +0100 Subject: [PATCH] fixes creation of meals. still not can serve it on the serving window. And TODO: fix GUI --- overcooked_simulator/counters.py | 75 ++++-- .../game_content/item_info_new.yaml | 13 +- overcooked_simulator/game_items.py | 233 ++++++++++-------- .../gui_2d_vis/overcooked_gui.py | 28 ++- .../overcooked_environment.py | 40 ++- tests/test_start.py | 16 +- 6 files changed, 255 insertions(+), 150 deletions(-) diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index fdc94f2b..39d8b8cc 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -18,8 +18,8 @@ import numpy.typing as npt from overcooked_simulator.game_items import ( Item, CookingEquipment, - Meal, Plate, + ItemInfo, ) @@ -182,16 +182,16 @@ class ServingWindow(Counter): class Dispenser(Counter): - def __init__(self, pos, dispensing): + def __init__(self, pos, dispensing: ItemInfo): self.dispensing = dispensing super().__init__( pos, - self.dispensing.create_item(), + self.create_item(), ) def pick_up(self, on_hands: bool = True): return_this = self.occupied_by - self.occupied_by = self.dispensing.create_item() + self.occupied_by = self.create_item() return return_this def drop_off(self, item: Item) -> Item | None: @@ -204,17 +204,27 @@ class Dispenser(Counter): def __repr__(self): return f"{self.dispensing.name}Dispenser" + def create_item(self): + kwargs = { + "name": self.dispensing.name, + "item_info": self.dispensing, + } + return Item(**kwargs) + class PlateDispenser(Counter): - def __init__(self, pos, dispensing, plate_config): + def __init__( + self, pos, dispensing, plate_config, plate_transitions, **kwargs + ) -> None: + super().__init__(pos, **kwargs) self.dispensing = dispensing - super().__init__(pos) self.occupied_by = deque() self.out_of_kitchen_timer = [] self.plate_config = {"plate_delay": [5, 10]} self.plate_config.update(plate_config) self.next_plate_time = datetime.max self.env_time = create_init_env_time() # is overwritten in progress anyway + self.plate_transitions = plate_transitions self.setup_plates() def pick_up(self, on_hands: bool = True): @@ -260,15 +270,12 @@ class PlateDispenser(Counter): """Create plates based on the config. Clean and dirty ones.""" if "dirty_plates" in self.plate_config: self.occupied_by.extend( - [ - self.dispensing.create_item() - for _ in range(self.plate_config["dirty_plates"]) - ] + [self.create_item() for _ in range(self.plate_config["dirty_plates"])] ) if "clean_plates" in self.plate_config: self.occupied_by.extend( [ - self.dispensing.create_item(clean_plate=True) + self.create_item(clean=True) for _ in range(self.plate_config["clean_plates"]) ] ) @@ -294,6 +301,14 @@ class PlateDispenser(Counter): def __repr__(self): return "PlateReturn" + def create_item(self, clean: bool = False): + kwargs = { + "clean": clean, + "transitions": self.plate_transitions, + "item_info": self.dispensing, + } + return Plate(**kwargs) + class Trash(Counter): def pick_up(self, on_hands: bool = True): @@ -327,23 +342,39 @@ class Stove(Counter): class Sink(Counter): - def __init__(self, pos, sink_addon=None): + def __init__(self, pos, transitions, sink_addon=None): super().__init__(pos) self.progressing = False self.sink_addon: SinkAddon = sink_addon self.occupied_by = deque() + self.transitions = transitions + + @property + def occupied(self): + return len(self.occupied_by) != 0 def progress(self, passed_time: timedelta, now: datetime): """Called by environment step function for time progression""" - if self.progressing: - if self.occupied_by: - self.occupied_by[-1].progress() - if self.occupied_by[-1].finished: - plate = self.occupied_by.pop() - if not self.occupied_by: - self.pause_progress() - plate.finished_call() - self.sink_addon.add_clean_plate(plate) + if ( + self.occupied + and self.progressing + and self.occupied_by[-1].name in self.transitions + ): + percent = ( + passed_time.total_seconds() + / self.transitions[self.occupied_by[-1].name]["seconds"] + ) + self.occupied_by[-1].progress( + equipment=self.__class__.__name__, percent=percent + ) + print(self.occupied_by[-1].progress_percentage) + if self.occupied_by[-1].progress_percentage == 1.0: + self.occupied_by[-1].reset() + self.occupied_by[-1].name = self.transitions[self.occupied_by[-1].name][ + "result" + ] + plate = self.occupied_by.pop() + self.sink_addon.add_clean_plate(plate) def start_progress(self): """Starts the cutting process.""" @@ -378,7 +409,7 @@ class Sink(Counter): class SinkAddon(Counter): def __init__(self, pos, occupied_by=None): super().__init__(pos) - self.occupied_by = deque(occupied_by) if occupied_by else deque() + self.occupied_by = deque([occupied_by]) if occupied_by else deque() def can_drop_off(self, item: Item) -> bool: return self.occupied_by and self.occupied_by[-1].can_combine(item) diff --git a/overcooked_simulator/game_content/item_info_new.yaml b/overcooked_simulator/game_content/item_info_new.yaml index 440e8d46..f87d6617 100644 --- a/overcooked_simulator/game_content/item_info_new.yaml +++ b/overcooked_simulator/game_content/item_info_new.yaml @@ -4,11 +4,16 @@ CuttingBoard: Sink: type: Equipment +Stove: + type: Equipment + Pot: type: Equipment + equipment: Stove Pan: type: Equipment + equipment: Stove DirtyPlate: type: Equipment @@ -39,19 +44,19 @@ Bun: ChoppedTomato: type: Ingredient needs: [ Tomato ] - seconds: 2.0 + seconds: 0.1 equipment: CuttingBoard ChoppedLettuce: type: Ingredient needs: [ Lettuce ] - seconds: 4.0 + seconds: 0.1 equipment: CuttingBoard ChoppedOnion: type: Ingredient needs: [ Onion ] - seconds: 2.0 + seconds: 0.1 equipment: CuttingBoard ChoppedMeat: @@ -62,7 +67,7 @@ ChoppedMeat: CookedPatty: type: Ingredient - seconds: 10.0 + seconds: 2.0 needs: [ ChoppedMeat ] equipment: Pan diff --git a/overcooked_simulator/game_items.py b/overcooked_simulator/game_items.py index d9ebae0e..06df4e12 100644 --- a/overcooked_simulator/game_items.py +++ b/overcooked_simulator/game_items.py @@ -31,36 +31,17 @@ class ItemInfo: def __post_init__(self): self.type = ItemType(self.type) - def create_item(self, clean_plate=False, parts=None) -> Item: - kwargs = { - "name": self.name, - "item_info": self, - "clean": clean_plate, - "parts": parts, - } - match self.type: - case ItemType.Ingredient: - return Item(**kwargs) - case ItemType.Equipment: - if "Plate" in self.name: - return Plate(**kwargs) - else: - return CookingEquipment(**kwargs) - case ItemType.Meal: - return Meal(**kwargs) - def add_start_meal_to_equipment(self, start_item: ItemInfo): self._start_meals.append(start_item) def sort_start_meals(self): self._start_meals.sort(key=lambda item_info: len(item_info.needs)) - def can_start_meal(self, items: list[Item]): - # TODO check specific order / only specific start items - return items and self._return_start_meal(items) is not None + # def can_start_meal(self, items: list[Item]): + # return items and self._return_start_meal(items) is not None - def start_meal(self, items: list[Item]) -> Item: - return self._return_start_meal(items).create_item(parts=items) + # def start_meal(self, items: list[Item]) -> Item: + # return self._return_start_meal(items).create_item(parts=items) def _return_start_meal(self, items: list[Item]) -> ItemInfo | None: for meal in self._start_meals: @@ -125,24 +106,41 @@ class CookingEquipment(Item): super().__init__(*args, **kwargs) self.transitions = transitions self.active_transition: Optional[dict] = None - self.content: Item | list[Item] = [] # list if cooking, meal item when done + # self.content: Item | list[Item] = [] # list if cooking, meal item when done + + self.content_ready: Item | None = None + self.content_list: list[Item] = [] + log.debug(f"Initialize {self.name}: {self.transitions}") + for transition in self.transitions.values(): + transition["recipe"] = collections.Counter(transition["needs"]) + def can_combine(self, other): # already cooking or nothing to combine - if self.active_transition is not None or other is None: + if other is None: return False + if isinstance(other, CookingEquipment): + other = other.content_list + else: + other = [other] + # other extends ingredients for meal - ingredients = collections.Counter(item.name for item in self.content + [other]) + ingredients = collections.Counter( + item.name for item in self.content_list + other + ) return any( - ingredients <= collections.Counter(transition["needs"]) - for transition in self.transitions.values() + ingredients <= recipe["recipe"] for recipe in self.transitions.values() ) """ def can_combine(self, other): - if other is None: + if other is Non + # TODO remove when content ready and content list is implemented + if isinstance(self.content, Item): + return False +e: return False if self.content is None: if isinstance(other, CookingEquipment): @@ -162,20 +160,32 @@ class CookingEquipment(Item): """ def combine(self, other): + return_value = None if isinstance(other, CookingEquipment): - ... + self.content_list.extend(other.content_list) + return_value = other + other.reset_content() + elif isinstance(other, list): + self.content_list.extend(other) else: - self.content.append(other) + self.content_list.append(other) - ingredients = collections.Counter(item.name for item in self.content) + ingredients = collections.Counter(item.name for item in self.content_list) for result, transition in self.transitions.items(): - recipe = collections.Counter(transition["needs"]) + recipe = transition["recipe"] if ingredients == recipe: - self.active_transition = { - "seconds": transition["seconds"], - "result": Item(name=result, item_info=transition["info"]) - } + if transition["seconds"] == 0: + self.content_ready = Item(name=result, item_info=transition["info"]) + else: + self.active_transition = { + "seconds": transition["seconds"], + "result": Item(name=result, item_info=transition["info"]), + } + print(f"{self.name} {self.active_transition}, {self.content_list}") break + else: + self.content_ready = None + return return_value """ def combine(self, other): @@ -221,91 +231,120 @@ class CookingEquipment(Item): percent = passed_time.total_seconds() / self.active_transition["seconds"] super().progress(equipment=self.name, percent=percent) if self.progress_percentage == 1.0: - self.content = self.active_transition["result"] + self.content_list = [self.active_transition["result"]] self.reset() - def can_release_content(self) -> bool: - return ( - self.content - and isinstance(self.content, ProgressibleItem) - and self.content.finished - ) + # todo set active transition for fire/burnt? + + # def can_release_content(self) -> bool: + # return ( + # self.content + # and isinstance(self.content, ProgressibleItem) + # and self.content.finished + # ) + def reset_content(self): + self.content_list = [] + self.content_ready = None def release(self): - content = self.content - self.content = None + content = self.content_list + self.content_list = None + self.content_ready = None return content @property def extra_repr(self): - return self.content + return f"{self.content_list}, {self.content_ready}" def reset(self): super().reset() self.active_transition = None -class Meal(Item): - def __init__(self, parts=None, *args, **kwargs): - super().__init__(*args, **kwargs) - self.parts = [] if parts is None else parts - # self.rules ... - - def can_combine(self, other) -> bool: - if other and not self.finished: - satisfied = [False for _ in range(len(self.parts))] - for n in self.item_info.needs: - for i, p in enumerate(self.parts): - if not satisfied[i] and p.name == n: - satisfied[i] = True - break - else: - if n == other.name: - return True - return False - - def combine(self, other): - self.parts.append(other) - - def can_progress(self) -> bool: - return self.item_info.steps_needed and len(self.item_info.needs) == len( - self.parts - ) - - def finished_call(self): - super().finished_call() - - @property - def extra_repr(self): - return self.parts +# class Meal(Item): +# def __init__(self, parts=None, *args, **kwargs): +# super().__init__(*args, **kwargs) +# self.parts = [] if parts is None else parts +# # self.rules ... +# +# def can_combine(self, other) -> bool: +# if other and not self.finished: +# satisfied = [False for _ in range(len(self.parts))] +# for n in self.item_info.needs: +# for i, p in enumerate(self.parts): +# if not satisfied[i] and p.name == n: +# satisfied[i] = True +# break +# else: +# if n == other.name: +# return True +# return False +# +# def combine(self, other): +# self.parts.append(other) +# +# def can_progress(self) -> bool: +# return self.item_info.steps_needed and len(self.item_info.needs) == len( +# self.parts +# ) +# +# def finished_call(self): +# super().finished_call() +# +# @property +# def extra_repr(self): +# return self.parts class Plate(CookingEquipment): - def __init__(self, clean, content: Meal = None, *args, **kwargs): - super().__init__(content, *args, **kwargs) + def __init__(self, transitions, clean, content=None, *args, **kwargs): self.clean = clean - self.name = self.create_name() + super().__init__( + name=self.create_name(), transitions=transitions, *args, **kwargs + ) def finished_call(self): self.clean = True self.name = self.create_name() - def progress(self): - """Progresses the item process as long as it is not finished.""" - if self.progressed_steps >= self.steps_needed: - self.finished = True - self.finished_call() - if not self.finished: - self.progressed_steps += 1 - - def can_progress(self, counter_type="Sink") -> bool: - return not self.clean + def progress(self, equipment: str, percent: float): + Item.progress(self, equipment, percent) - def can_combine(self, other): - return self.clean and super().can_combine(other) + # def progress(self, equipment: str, percent: float): + # """Progresses the item process as long as it is not finished.""" + # if self.progressed_steps >= self.steps_needed: + # self.finished = True + # self.finished_call() + # if not self.finished: + # self.progressed_steps += 1 - def combine(self, other): - return super().combine(other) + # def can_progress(self, counter_type="Sink") -> bool: + # return not self.clean def create_name(self): return "CleanPlate" if self.clean else "DirtyPlate" + + def can_combine(self, other): + if not super().can_combine(other): + if isinstance(other, CookingEquipment) and len(other.content_list) == 1: + return other.content_list[0].name in self.transitions + return False + else: + return True + + +""" +Dominik +TODOs für yaml issue: + + +Fix cooking equipment drawing with list as content +Pot / Pan cooking +Meal combination +Plate cleaning +Environment factory for counter and cooking equipment? +Yaml check +Plot recipe graph +Clean unused code & imports + +""" diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index c2728c76..89afcb4f 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -17,7 +17,6 @@ from overcooked_simulator.counters import Counter from overcooked_simulator.game_items import ( Item, CookingEquipment, - Meal, Plate, ) from overcooked_simulator.gui_2d_vis.game_colors import BLUE @@ -401,7 +400,7 @@ class PyGameGUI: scale: Rescale the item by this factor. """ - if not isinstance(item, Meal): + if not isinstance(item, list): if item.name in self.visualization_config: self.draw_thing( pos, self.visualization_config[item.name]["parts"], scale=scale @@ -410,17 +409,22 @@ class PyGameGUI: if isinstance(item, (Item, Plate)) and item.progress_percentage > 0.0: self.draw_progress_bar(pos, item.progress_percentage) - if isinstance(item, CookingEquipment) and item.content: - self.draw_item(pos, item.content) - - if isinstance(item, Meal): - if item.finished: - if item.name in self.visualization_config: - self.draw_thing(pos, self.visualization_config[item.name]["parts"]) - else: - for idx, o in enumerate(item.parts): - triangle_offsets = create_polygon(len(item.parts), length=10) + if isinstance(item, CookingEquipment) and item.content_list: + if isinstance(item.content_list, list): + for idx, o in enumerate(item.content_list): + triangle_offsets = create_polygon(len(item.content_list), length=10) self.draw_item(pos + triangle_offsets[idx], o, scale=0.6) + elif item.name in self.visualization_config: + self.draw_thing(pos, self.visualization_config[item.name]["parts"]) + + # if isinstance(item, Meal): + # if item.finished: + # if item.name in self.visualization_config: + # self.draw_thing(pos, self.visualization_config[item.name]["parts"]) + # else: + # for idx, o in enumerate(item.parts): + # triangle_offsets = create_polygon(len(item.parts), length=10) + # self.draw_item(pos + triangle_offsets[idx], o, scale=0.6) def draw_progress_bar(self, pos, percent): """Visualize progress of progressing item as a green bar under the item.""" diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 367e59bf..b5b849c2 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -89,8 +89,16 @@ class Environment: self.item_info_path: Path = item_info_path self.item_info = self.load_item_info() self.validate_item_info() - self.game_score = GameScore() + plate_transitions = { + item: { + "seconds": info.seconds, + "needs": info.needs, + "info": info, + } + for item, info in self.item_info.items() + if info.type == ItemType.Meal + } self.SYMBOL_TO_CHARACTER_MAP = { "#": Counter, @@ -108,9 +116,12 @@ class Environment: "T": lambda pos: Dispenser(pos, self.item_info["Tomato"]), "L": lambda pos: Dispenser(pos, self.item_info["Lettuce"]), "P": lambda pos: PlateDispenser( - pos, - self.item_info["Plate"], - environment_config["plates"] if "plates" in environment_config else {}, + plate_transitions=plate_transitions, + pos=pos, + dispensing=self.item_info["Plate"], + plate_config=environment_config["plates"] + if "plates" in environment_config + else {}, ), "N": lambda pos: Dispenser(pos, self.item_info["Onion"]), # N for oNioN "_": "Free", @@ -121,7 +132,11 @@ class Environment: name="Pot", item_info=self.item_info["Pot"], transitions={ - item: {"seconds": info.seconds, "needs": info.needs, "info": info} + item: { + "seconds": info.seconds, + "needs": info.needs, + "info": info, + } for item, info in self.item_info.items() if info.equipment is not None and info.equipment.name == "Pot" }, @@ -133,7 +148,11 @@ class Environment: name="Pan", item_info=self.item_info["Pan"], transitions={ - item: {"seconds": info.seconds, "needs": info.needs, "info": info} + item: { + "seconds": info.seconds, + "needs": info.needs, + "info": info, + } for item, info in self.item_info.items() if info.equipment is not None and info.equipment.name == "Pan" }, @@ -141,7 +160,14 @@ class Environment: ), # Stove with pan: Q because it looks like a pan "B": lambda pos: Dispenser(pos, self.item_info["Bun"]), "M": lambda pos: Dispenser(pos, self.item_info["Meat"]), - "S": lambda pos: Sink(pos), + "S": lambda pos: Sink( + pos, + transitions={ + info.needs[0]: {"seconds": info.seconds, "result": item} + for item, info in self.item_info.items() + if info.equipment is not None and info.equipment.name == "Sink" + }, + ), "+": SinkAddon, } diff --git a/tests/test_start.py b/tests/test_start.py index 64fce11d..4902f861 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -6,7 +6,7 @@ import pytest from overcooked_simulator import ROOT_DIR from overcooked_simulator.counters import Counter, CuttingBoard -from overcooked_simulator.game_items import CuttableItem +from overcooked_simulator.game_items import Item from overcooked_simulator.overcooked_environment import Action, Environment from overcooked_simulator.simulation_runner import Simulator from overcooked_simulator.utils import create_init_env_time @@ -169,7 +169,7 @@ def test_pickup(): counter_pos = np.array([2, 2]) counter = Counter(counter_pos) - counter.occupied_by = CuttableItem(name="Tomato", item_info=None) + counter.occupied_by = Item(name="Tomato", item_info=None) sim.env.counters = [counter] sim.register_player("p1", np.array([2, 3])) @@ -219,10 +219,10 @@ def test_processing(): sim.start() counter_pos = np.array([2, 2]) - counter = CuttingBoard(counter_pos) + counter = CuttingBoard(counter_pos, transitions={}) sim.env.counters.append(counter) - tomato = CuttableItem(name="Tomato", item_info=None) + tomato = Item(name="Tomato", item_info=None) sim.register_player("p1", np.array([2, 3])) player = sim.env.players["p1"] player.holding = tomato @@ -236,12 +236,12 @@ def test_processing(): hold_down = Action("p1", "interact", "keydown") sim.enter_action(hold_down) - assert not tomato.finished, "Tomato is not finished yet." + assert not tomato.progress_percentage != 1.0, "Tomato is not finished yet." - sleep_time = (tomato.steps_needed / sim_frequency) + 0.1 + sleep_time = (tomato.progress_percentage / sim_frequency) + 0.1 time.sleep(sleep_time) - assert tomato.finished, "Tomato should be finished." + assert tomato.name == "ChoppedTomato", "Tomato should be finished." button_up = Action("p1", "interact", "keyup") sim.enter_action(button_up) @@ -254,7 +254,7 @@ def test_time_passed(): env = Environment( ROOT_DIR / "game_content" / "environment_config.yaml", layouts_folder / "empty.layout", - ROOT_DIR / "game_content" / "item_info.yaml", + ROOT_DIR / "game_content" / "item_info_new.yaml", ) env.reset_env_time() passed_time = timedelta(seconds=10) -- GitLab