Skip to content
Snippets Groups Projects
Commit d0d9e558 authored by Fabian Heinrich's avatar Fabian Heinrich
Browse files

Merge branch '38-pot-and-stove-and-cooking-tomato-soup' into 'main'

Resolve "Pot and stove and cooking tomato soup"

Closes #38

See merge request scs/cocosy/overcooked-simulator!10
parents c3a9f5fe 34396b6b
No related branches found
No related tags found
1 merge request!10Resolve "Pot and stove and cooking tomato soup"
Pipeline #41818 passed
...@@ -10,32 +10,42 @@ import numpy.typing as npt ...@@ -10,32 +10,42 @@ import numpy.typing as npt
from overcooked_simulator.game_items import ( from overcooked_simulator.game_items import (
CuttableItem, CuttableItem,
HoldableItem, Item,
ProgressibleItem, ProgressibleItem,
Plate, Plate,
Tomato, Tomato,
Pot,
CookingEquipment,
) )
class Counter: class Counter:
"""Simple class for a counter at a specified position (center of counter). Can hold things on top.""" """Simple class for a counter at a specified position (center of counter). Can hold things on top."""
def __init__(self, pos: npt.NDArray[float]): def __init__(self, pos: npt.NDArray[float], occupied_by: Optional[Item] = None):
self.pos: npt.NDArray[float] = pos self.pos: npt.NDArray[float] = pos
self.occupied_by: Optional[HoldableItem] = None self.occupied_by: Optional[Item] = occupied_by
def pick_up(self): def pick_up(self, on_hands: bool = True):
"""Gets called upon a player performing the pickup action. If the counter can give something to """Gets called upon a player performing the pickup action. If the counter can give something to
the player, it does so. In the standard counter this is when an item is on the counter. the player, it does so. In the standard counter this is when an item is on the counter.
Returns: The item which the counter is occupied by. None if nothing is there. Returns: The item which the counter is occupied by. None if nothing is there.
""" """
give_player = self.occupied_by if on_hands:
if self.occupied_by:
occupied_by = self.occupied_by
self.occupied_by = None
return occupied_by
return None
if self.occupied_by and isinstance(self.occupied_by, CookingEquipment):
return self.occupied_by.release()
occupied_by = self.occupied_by
self.occupied_by = None self.occupied_by = None
return give_player return occupied_by
def can_drop_off(self, item: HoldableItem) -> bool: def can_drop_off(self, item: Item) -> bool:
"""Checks whether an item by the player can be dropped of. More relevant for example with """Checks whether an item by the player can be dropped of. More relevant for example with
ingredient dispensers, which should always be occupied and cannot take an item. ingredient dispensers, which should always be occupied and cannot take an item.
...@@ -47,7 +57,7 @@ class Counter: ...@@ -47,7 +57,7 @@ class Counter:
""" """
return self.occupied_by is None or self.occupied_by.can_combine(item) return self.occupied_by is None or self.occupied_by.can_combine(item)
def drop_off(self, item: HoldableItem) -> HoldableItem | None: def drop_off(self, item: Item) -> Item | None:
"""Takes the thing dropped of by the player. """Takes the thing dropped of by the player.
Args: Args:
...@@ -59,7 +69,7 @@ class Counter: ...@@ -59,7 +69,7 @@ class Counter:
if self.occupied_by is None: if self.occupied_by is None:
self.occupied_by = item self.occupied_by = item
elif self.occupied_by.can_combine(item): elif self.occupied_by.can_combine(item):
self.occupied_by.combine(item) return self.occupied_by.combine(item)
return None return None
def interact_start(self): def interact_start(self):
...@@ -71,7 +81,9 @@ class Counter: ...@@ -71,7 +81,9 @@ class Counter:
pass pass
def __repr__(self): def __repr__(self):
return f"Counter(pos:{str(self.pos)},holds:{self.occupied_by})" return (
f"{self.__class__.__name__}(pos={self.pos},occupied_by={self.occupied_by})"
)
class CuttingBoard(Counter): class CuttingBoard(Counter):
...@@ -101,33 +113,31 @@ class CuttingBoard(Counter): ...@@ -101,33 +113,31 @@ class CuttingBoard(Counter):
"""Handles player interaction, stopping to hold key down.""" """Handles player interaction, stopping to hold key down."""
self.pause_progress() self.pause_progress()
def __repr__(self):
return f"CuttingBoard({self.occupied_by})"
class ServingWindow(Counter): class ServingWindow(Counter):
def __init__(self, pos, game_score: GameScore): def __init__(self, pos, game_score: GameScore):
self.game_score = game_score self.game_score = game_score
super().__init__(pos) super().__init__(pos)
def drop_off(self, item) -> HoldableItem | None: def drop_off(self, item) -> Item | None:
reward = 5 reward = 5
print(item)
# TODO define rewards # TODO define rewards
self.game_score.increment_score(reward) self.game_score.increment_score(reward)
return None return None
def can_score(self, item): def can_score(self, item):
if isinstance(item, Plate) and isinstance(item.holds, ProgressibleItem): if isinstance(item, Plate):
return item.holds.finished if isinstance(item.holds, ProgressibleItem):
return item.holds.finished
else:
return bool(item.holds)
def can_drop_off(self, item: HoldableItem) -> bool: def can_drop_off(self, item: Item) -> bool:
return self.can_score(item) return self.can_score(item)
def pick_up(self): def pick_up(self, on_hands: bool = True):
return None pass
def __repr__(self):
return "ServingWindow"
class PlateReturn(Counter): class PlateReturn(Counter):
...@@ -135,7 +145,7 @@ class PlateReturn(Counter): ...@@ -135,7 +145,7 @@ class PlateReturn(Counter):
super().__init__(pos) super().__init__(pos)
self.occupied_by = [Plate()] self.occupied_by = [Plate()]
def pick_up(self): def pick_up(self, on_hands: bool = True):
"""Gets called upon a player performing the pickup action. Gives back a plate (possibly with ingredient. """Gets called upon a player performing the pickup action. Gives back a plate (possibly with ingredient.
Returns: A plate possibly with an ingredient on it. Returns: A plate possibly with an ingredient on it.
...@@ -146,7 +156,7 @@ class PlateReturn(Counter): ...@@ -146,7 +156,7 @@ class PlateReturn(Counter):
self.occupied_by.append(Plate()) self.occupied_by.append(Plate())
return give_player return give_player
def drop_off(self, item: HoldableItem) -> HoldableItem | None: def drop_off(self, item: Item) -> Item | None:
"""Takes the ingredient dropped of by the player. """Takes the ingredient dropped of by the player.
Args: Args:
...@@ -158,11 +168,10 @@ class PlateReturn(Counter): ...@@ -158,11 +168,10 @@ class PlateReturn(Counter):
self.occupied_by.append(item) self.occupied_by.append(item)
return None return None
if self.occupied_by[-1].can_combine(item): if self.occupied_by[-1].can_combine(item):
self.occupied_by[-1].combine(item) return self.occupied_by[-1].combine(item)
return None
return item return item
def can_drop_off(self, item: HoldableItem) -> bool: def can_drop_off(self, item: Item) -> bool:
"""Checks whether an ingredient by the player can be dropped of. """Checks whether an ingredient by the player can be dropped of.
Args: Args:
...@@ -176,39 +185,49 @@ class PlateReturn(Counter): ...@@ -176,39 +185,49 @@ class PlateReturn(Counter):
isinstance(self.occupied_by[-1], Plate) and isinstance(item, Plate) isinstance(self.occupied_by[-1], Plate) and isinstance(item, Plate)
) or self.occupied_by[-1].can_combine(item) ) or self.occupied_by[-1].can_combine(item)
def __repr__(self):
return "PlateReturn"
class TomatoDispenser(Counter): class TomatoDispenser(Counter):
def __init__(self, pos): def __init__(self, pos):
super().__init__(pos) super().__init__(pos)
def pick_up(self): def pick_up(self, on_hands: bool = True):
return Tomato() return Tomato()
def drop_off(self, item: HoldableItem) -> HoldableItem | None: def drop_off(self, item: Item) -> Item | None:
return None return None
def can_drop_off(self, item: HoldableItem) -> bool: def can_drop_off(self, item: Item) -> bool:
return False return False
def __repr__(self):
return f"{self.occupied_by}Dispenser"
class Trash(Counter): class Trash(Counter):
def pick_up(self): def pick_up(self, on_hands: bool = True):
pass pass
def drop_off(self, item: HoldableItem) -> HoldableItem | None: def drop_off(self, item: Item) -> Item | None:
if isinstance(item, Plate): if isinstance(item, Plate):
item.holds = None item.holds = None
return item return item
if isinstance(item, CookingEquipment):
item.content = None
return item
return None return None
def can_drop_off(self, item: HoldableItem) -> bool: def can_drop_off(self, item: Item) -> bool:
return True return True
def __repr__(self):
return "Trash" class Stove(Counter):
def __init__(self, pos: npt.NDArray[float], occupied_by: Optional[Item] = ...):
if occupied_by is ...:
occupied_by = Pot()
super().__init__(pos, occupied_by)
def progress(self):
"""Called by environment step function for time progression"""
if (
self.occupied_by
and isinstance(self.occupied_by, CookingEquipment)
and self.occupied_by.can_progress()
):
self.occupied_by.progress()
class HoldableItem: from __future__ import annotations
class Item:
"""Base class for game items which can be held by a player.""" """Base class for game items which can be held by a player."""
def __init__(self, name: str = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = self.__class__.__name__ if name is None else name
def can_combine(self, other): def can_combine(self, other):
return False return False
def combine(self, other): def combine(self, other) -> Item | None:
pass pass
def __repr__(self):
return f"{self.name}({self.extra_repr})"
class Plate(HoldableItem): @property
def __init__(self, holds: HoldableItem = None): def extra_repr(self):
self.clean = True return ""
self.holds = holds
class Plate(Item):
def __init__(self, holds: Item = None):
super().__init__() super().__init__()
self.clean = True
self.holds = holds
def can_combine(self, other: HoldableItem): def can_combine(self, other: Item) -> bool:
return self.holds is None and not isinstance(other, Plate) if self.holds is None:
if isinstance(other, CookingEquipment):
return other.can_release_content()
return not isinstance(other, Plate)
return False
def combine(self, other): def combine(self, other):
if isinstance(other, CookingEquipment):
self.holds = other.release()
return other
self.holds = other self.holds = other
def __repr__(self): @property
return f"Plate({self.holds})" def extra_repr(self):
return self.holds
class ProgressibleItem(HoldableItem): class ProgressibleItem:
"""Class for items which need to be processed (cut, cooked, ...)""" """Class for items which need to be processed (cut, cooked, ...)"""
def __init__(self, steps_needed): def __init__(
self.progressed_steps = 0 self,
finished: bool = False,
steps_needed: int = 1500,
finished_name: str = None,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)
self.progressed_steps = steps_needed if finished else 0
self.steps_needed = steps_needed self.steps_needed = steps_needed
self.finished = False self.finished = finished
super().__init__() self.finished_name = (
f"Cutted{self.name}" if finished_name is None else finished_name
)
def progress(self): def progress(self):
"""Progresses the item process as long as it is not finished.""" """Progresses the item process as long as it is not finished."""
if self.progressed_steps >= self.steps_needed: if self.progressed_steps >= self.steps_needed:
self.finished = True self.finished = True
self.progressed_steps = 0 self.finished_call()
if not self.finished: if not self.finished:
self.progressed_steps += 1 self.progressed_steps += 1
def can_progress(self) -> bool:
return True
def finished_call(self):
self.name = self.finished_name
def reset(self):
self.finished = False
self.progressed_steps = 0
def __repr__(self): def __repr__(self):
if self.finished: if self.finished:
return "CutTomato" return f"{self.name}({self.extra_repr})"
else: else:
return f"{self.__class__.__name__}({int(self.progressed_steps / self.steps_needed * 100)}%)" return f"{self.name}(progress={int(self.progressed_steps / self.steps_needed * 100)}%,{self.extra_repr})"
class CuttableItem(ProgressibleItem): class CuttableItem(ProgressibleItem):
...@@ -55,7 +96,7 @@ class CuttableItem(ProgressibleItem): ...@@ -55,7 +96,7 @@ class CuttableItem(ProgressibleItem):
pass pass
class Tomato(CuttableItem): class Tomato(CuttableItem, Item):
"""Item class representing a tomato. Can be cut on the cutting board""" """Item class representing a tomato. Can be cut on the cutting board"""
def can_combine(self, other): def can_combine(self, other):
...@@ -63,3 +104,89 @@ class Tomato(CuttableItem): ...@@ -63,3 +104,89 @@ class Tomato(CuttableItem):
def __init__(self): def __init__(self):
super().__init__(steps_needed=1500) super().__init__(steps_needed=1500)
class CookingEquipment(Item):
def __init__(self, content: Meal = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.content = content
def can_combine(self, other):
if self.content is None:
# TODO check other is start of a meal, create meal
return True
return self.content.can_combine(other)
def combine(self, other):
if not self.content:
# find starting meal for other
self.content = Soup()
self.content.combine(other)
def can_progress(self, counter_type="Stove") -> bool:
return (
self.content
and isinstance(self.content, ProgressibleItem)
and self.content.can_progress()
)
def progress(self):
self.content.progress()
def can_release_content(self) -> bool:
return (
self.content
and isinstance(self.content, ProgressibleItem)
and self.content.finished
)
def release(self):
content = self.content
self.content = None
return content
@property
def extra_repr(self):
return self.content
class Pot(CookingEquipment):
def __init__(self, holds: Meal = None):
super().__init__()
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:
return (
isinstance(other, Tomato)
and all([isinstance(o, other.__class__) for o in self.parts])
and len(self.parts) < 3
) # rules
def combine(self, other):
self.parts.append(other)
@property
def extra_repr(self):
return self.parts
class Soup(ProgressibleItem, Meal):
def can_progress(self) -> bool:
return len(self.parts) == 3
class Pan(CookingEquipment):
def __init__(self):
super().__init__(steps_needed=1500)
def can_combine(self, other):
return False
def combine(self, other):
pass
EEEEEEEEEEE EEEEEEEEEEE
ECCCCTCCCCE ECCUCTCCCCE
ECEEEEEEECE ECEEEEEEECE
ECEEEEEEECE ECEEEEEEECE
EWEEEEEEEEE EWEEEEEEEEE
......
...@@ -6,7 +6,6 @@ if TYPE_CHECKING: ...@@ -6,7 +6,6 @@ if TYPE_CHECKING:
from overcooked_simulator.player import Player from overcooked_simulator.player import Player
from pathlib import Path from pathlib import Path
import numpy as np import numpy as np
import numpy.typing as npt
from scipy.spatial import distance_matrix from scipy.spatial import distance_matrix
from overcooked_simulator.counters import ( from overcooked_simulator.counters import (
...@@ -16,6 +15,7 @@ from overcooked_simulator.counters import ( ...@@ -16,6 +15,7 @@ from overcooked_simulator.counters import (
TomatoDispenser, TomatoDispenser,
ServingWindow, ServingWindow,
PlateReturn, PlateReturn,
Stove,
) )
...@@ -69,6 +69,7 @@ class Environment: ...@@ -69,6 +69,7 @@ class Environment:
"T": TomatoDispenser, "T": TomatoDispenser,
"P": PlateReturn, "P": PlateReturn,
"E": None, "E": None,
"U": Stove, # Stove with pot: U because it looks like a pot
} }
self.counters: list[Counter] = self.create_counters(self.layout_path) self.counters: list[Counter] = self.create_counters(self.layout_path)
...@@ -192,7 +193,7 @@ class Environment: ...@@ -192,7 +193,7 @@ class Environment:
old_pos_other = collided_player.pos.copy() old_pos_other = collided_player.pos.copy()
collided_player.move(pushing_vector * (collided_player.move_dist / 2)) collided_player.move(pushing_vector * (collided_player.move_dist / 2))
if self.detect_collision_counters( if self.detect_collision_counters(
collided_player collided_player
) or self.detect_collision_world_bounds(player): ) or self.detect_collision_world_bounds(player):
collided_player.move_abs(old_pos_other) collided_player.move_abs(old_pos_other)
player.move_abs(old_pos) player.move_abs(old_pos)
...@@ -226,9 +227,9 @@ class Environment: ...@@ -226,9 +227,9 @@ class Environment:
Returns: True if the player is intersecting with any object in the environment. Returns: True if the player is intersecting with any object in the environment.
""" """
return ( return (
len(self.get_collided_players(player)) != 0 len(self.get_collided_players(player)) != 0
or self.detect_collision_counters(player) or self.detect_collision_counters(player)
or self.detect_collision_world_bounds(player) or self.detect_collision_world_bounds(player)
) )
def get_collided_players(self, player: Player) -> list[Player]: def get_collided_players(self, player: Player) -> list[Player]:
...@@ -323,7 +324,7 @@ class Environment: ...@@ -323,7 +324,7 @@ class Environment:
and time limits. and time limits.
""" """
for counter in self.counters: for counter in self.counters:
if isinstance(counter, CuttingBoard): if isinstance(counter, (CuttingBoard, Stove)):
counter.progress() counter.progress()
def get_state(self): def get_state(self):
......
...@@ -4,7 +4,7 @@ import numpy as np ...@@ -4,7 +4,7 @@ import numpy as np
import numpy.typing as npt import numpy.typing as npt
from overcooked_simulator.counters import Counter from overcooked_simulator.counters import Counter
from overcooked_simulator.game_items import HoldableItem from overcooked_simulator.game_items import Item
class Player: class Player:
...@@ -17,7 +17,7 @@ class Player: ...@@ -17,7 +17,7 @@ class Player:
def __init__(self, name: str, pos: npt.NDArray[float]): def __init__(self, name: str, pos: npt.NDArray[float]):
self.name: str = name self.name: str = name
self.pos: npt.NDArray[float] = np.array(pos, dtype=float) self.pos: npt.NDArray[float] = np.array(pos, dtype=float)
self.holding: Optional[HoldableItem] = None self.holding: Optional[Item] = None
self.radius: int = 18 self.radius: int = 18
self.move_dist: int = 5 self.move_dist: int = 5
...@@ -81,7 +81,7 @@ class Player: ...@@ -81,7 +81,7 @@ class Player:
self.holding = counter.drop_off(self.holding) self.holding = counter.drop_off(self.holding)
elif self.holding.can_combine(counter.occupied_by): elif self.holding.can_combine(counter.occupied_by):
returned_by_counter = counter.pick_up() returned_by_counter = counter.pick_up(on_hands=False)
self.holding.combine(returned_by_counter) self.holding.combine(returned_by_counter)
def perform_interact_hold_start(self, counter: Counter): def perform_interact_hold_start(self, counter: Counter):
......
overcooked_simulator/pygame_gui/images/tomato_soup.png

7.56 KiB

...@@ -11,8 +11,15 @@ from overcooked_simulator.counters import ( ...@@ -11,8 +11,15 @@ from overcooked_simulator.counters import (
TomatoDispenser, TomatoDispenser,
PlateReturn, PlateReturn,
ServingWindow, ServingWindow,
Stove,
)
from overcooked_simulator.game_items import (
ProgressibleItem,
Plate,
Item,
Pot,
Soup,
) )
from overcooked_simulator.game_items import ProgressibleItem, Plate, HoldableItem
from overcooked_simulator.game_items import Tomato from overcooked_simulator.game_items import Tomato
from overcooked_simulator.overcooked_environment import Action from overcooked_simulator.overcooked_environment import Action
from overcooked_simulator.simulation_runner import Simulator from overcooked_simulator.simulation_runner import Simulator
...@@ -31,8 +38,9 @@ BACKGROUND_LINES_COLOR = (200, 200, 200) ...@@ -31,8 +38,9 @@ BACKGROUND_LINES_COLOR = (200, 200, 200)
KNIFE_COLOR = (120, 120, 120) KNIFE_COLOR = (120, 120, 120)
PLATE_RETURN_COLOR = (170, 170, 240) PLATE_RETURN_COLOR = (170, 170, 240)
BOARD_COLOR = (239, 193, 151) BOARD_COLOR = (239, 193, 151)
POT_COLOR = (130, 130, 130)
USE_COOK_SPRITE = False USE_COOK_SPRITE = True
class PlayerKeySet: class PlayerKeySet:
...@@ -194,7 +202,7 @@ class PyGameGUI: ...@@ -194,7 +202,7 @@ class PyGameGUI:
holding_item_pos = player.pos + (20 * player.facing_direction) holding_item_pos = player.pos + (20 * player.facing_direction)
self.draw_item(holding_item_pos, player.holding) self.draw_item(holding_item_pos, player.holding)
def draw_item(self, pos, item: HoldableItem): def draw_item(self, pos, item: Item):
"""Visualisation of an item at the specified position. On a counter or in the hands of the player.""" """Visualisation of an item at the specified position. On a counter or in the hands of the player."""
if isinstance(item, Tomato): if isinstance(item, Tomato):
if item.finished: if item.finished:
...@@ -218,6 +226,31 @@ class PyGameGUI: ...@@ -218,6 +226,31 @@ class PyGameGUI:
if item.holds is not None: if item.holds is not None:
self.draw_item(pos, item.holds) self.draw_item(pos, item.holds)
if isinstance(item, Pot):
pot_size = 15
pygame.draw.circle(self.screen, GREY, pos, pot_size)
pygame.draw.circle(self.screen, POT_COLOR, pos, pot_size, width=2)
if item.content:
self.draw_item(pos, item.content)
if isinstance(item, Soup):
if not item.finished:
match len(item.parts):
case 1:
pygame.draw.circle(self.screen, RED, pos, 4)
case 2:
pygame.draw.circle(self.screen, RED, pos, 8)
case 3:
pygame.draw.circle(self.screen, RED, pos, 12)
else:
"""https://www.readersdigest.ca/wp-content/uploads/2020/11/The-Best-Ever-Tomato-Soup_EXPS_THSO18_222724_D03_06_5b-4.jpg"""
image = pygame.image.load(
self.images_path / "tomato_soup.png"
).convert_alpha()
image = pygame.transform.scale(image, (24, 24))
rect = image.get_rect()
rect.center = pos
self.screen.blit(image, rect)
if isinstance(item, ProgressibleItem) and not item.finished: if isinstance(item, ProgressibleItem) and not item.finished:
self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed) self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed)
...@@ -289,6 +322,7 @@ class PyGameGUI: ...@@ -289,6 +322,7 @@ class PyGameGUI:
) )
pygame.draw.rect(self.screen, RED, board_rect) pygame.draw.rect(self.screen, RED, board_rect)
self.draw_item(counter.pos, Tomato()) self.draw_item(counter.pos, Tomato())
if isinstance(counter, ServingWindow): if isinstance(counter, ServingWindow):
board_size = 33 board_size = 33
board_rect = pygame.Rect( board_rect = pygame.Rect(
...@@ -299,6 +333,17 @@ class PyGameGUI: ...@@ -299,6 +333,17 @@ class PyGameGUI:
) )
pygame.draw.rect(self.screen, YELLOW, board_rect) pygame.draw.rect(self.screen, YELLOW, board_rect)
if isinstance(counter, Stove):
stove_width = 35
stove_height = 25
stove_rect = pygame.Rect(
counter.pos[0] - (stove_width / 2),
counter.pos[1] - (stove_height / 2),
stove_width,
stove_height,
)
pygame.draw.rect(self.screen, BLACK, stove_rect)
pygame.draw.circle(self.screen, RED, center=counter.pos, radius=10)
if counter.occupied_by is not None: if counter.occupied_by is not None:
if isinstance(counter.occupied_by, list): if isinstance(counter.occupied_by, list):
for i, o in enumerate(counter.occupied_by): for i, o in enumerate(counter.occupied_by):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment