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
from overcooked_simulator.game_items import (
CuttableItem,
HoldableItem,
Item,
ProgressibleItem,
Plate,
Tomato,
Pot,
CookingEquipment,
)
class Counter:
"""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.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
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.
"""
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
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
ingredient dispensers, which should always be occupied and cannot take an item.
......@@ -47,7 +57,7 @@ class Counter:
"""
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.
Args:
......@@ -59,7 +69,7 @@ class Counter:
if self.occupied_by is None:
self.occupied_by = item
elif self.occupied_by.can_combine(item):
self.occupied_by.combine(item)
return self.occupied_by.combine(item)
return None
def interact_start(self):
......@@ -71,7 +81,9 @@ class Counter:
pass
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):
......@@ -101,33 +113,31 @@ class CuttingBoard(Counter):
"""Handles player interaction, stopping to hold key down."""
self.pause_progress()
def __repr__(self):
return f"CuttingBoard({self.occupied_by})"
class ServingWindow(Counter):
def __init__(self, pos, game_score: GameScore):
self.game_score = game_score
super().__init__(pos)
def drop_off(self, item) -> HoldableItem | None:
def drop_off(self, item) -> Item | None:
reward = 5
print(item)
# TODO define rewards
self.game_score.increment_score(reward)
return None
def can_score(self, item):
if isinstance(item, Plate) and isinstance(item.holds, ProgressibleItem):
return item.holds.finished
if isinstance(item, Plate):
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)
def pick_up(self):
return None
def __repr__(self):
return "ServingWindow"
def pick_up(self, on_hands: bool = True):
pass
class PlateReturn(Counter):
......@@ -135,7 +145,7 @@ class PlateReturn(Counter):
super().__init__(pos)
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.
Returns: A plate possibly with an ingredient on it.
......@@ -146,7 +156,7 @@ class PlateReturn(Counter):
self.occupied_by.append(Plate())
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.
Args:
......@@ -158,11 +168,10 @@ class PlateReturn(Counter):
self.occupied_by.append(item)
return None
if self.occupied_by[-1].can_combine(item):
self.occupied_by[-1].combine(item)
return None
return self.occupied_by[-1].combine(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.
Args:
......@@ -176,39 +185,49 @@ class PlateReturn(Counter):
isinstance(self.occupied_by[-1], Plate) and isinstance(item, Plate)
) or self.occupied_by[-1].can_combine(item)
def __repr__(self):
return "PlateReturn"
class TomatoDispenser(Counter):
def __init__(self, pos):
super().__init__(pos)
def pick_up(self):
def pick_up(self, on_hands: bool = True):
return Tomato()
def drop_off(self, item: HoldableItem) -> HoldableItem | None:
def drop_off(self, item: Item) -> Item | None:
return None
def can_drop_off(self, item: HoldableItem) -> bool:
def can_drop_off(self, item: Item) -> bool:
return False
def __repr__(self):
return f"{self.occupied_by}Dispenser"
class Trash(Counter):
def pick_up(self):
def pick_up(self, on_hands: bool = True):
pass
def drop_off(self, item: HoldableItem) -> HoldableItem | None:
def drop_off(self, item: Item) -> Item | None:
if isinstance(item, Plate):
item.holds = None
return item
if isinstance(item, CookingEquipment):
item.content = None
return item
return None
def can_drop_off(self, item: HoldableItem) -> bool:
def can_drop_off(self, item: Item) -> bool:
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."""
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):
return False
def combine(self, other):
def combine(self, other) -> Item | None:
pass
def __repr__(self):
return f"{self.name}({self.extra_repr})"
class Plate(HoldableItem):
def __init__(self, holds: HoldableItem = None):
self.clean = True
self.holds = holds
@property
def extra_repr(self):
return ""
class Plate(Item):
def __init__(self, holds: Item = None):
super().__init__()
self.clean = True
self.holds = holds
def can_combine(self, other: HoldableItem):
return self.holds is None and not isinstance(other, Plate)
def can_combine(self, other: Item) -> bool:
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):
if isinstance(other, CookingEquipment):
self.holds = other.release()
return other
self.holds = other
def __repr__(self):
return f"Plate({self.holds})"
@property
def extra_repr(self):
return self.holds
class ProgressibleItem(HoldableItem):
class ProgressibleItem:
"""Class for items which need to be processed (cut, cooked, ...)"""
def __init__(self, steps_needed):
self.progressed_steps = 0
def __init__(
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.finished = False
super().__init__()
self.finished = finished
self.finished_name = (
f"Cutted{self.name}" if finished_name is None else finished_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.progressed_steps = 0
self.finished_call()
if not self.finished:
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):
if self.finished:
return "CutTomato"
return f"{self.name}({self.extra_repr})"
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):
......@@ -55,7 +96,7 @@ class CuttableItem(ProgressibleItem):
pass
class Tomato(CuttableItem):
class Tomato(CuttableItem, Item):
"""Item class representing a tomato. Can be cut on the cutting board"""
def can_combine(self, other):
......@@ -63,3 +104,89 @@ class Tomato(CuttableItem):
def __init__(self):
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
ECCCCTCCCCE
ECCUCTCCCCE
ECEEEEEEECE
ECEEEEEEECE
EWEEEEEEEEE
......
......@@ -6,7 +6,6 @@ if TYPE_CHECKING:
from overcooked_simulator.player import Player
from pathlib import Path
import numpy as np
import numpy.typing as npt
from scipy.spatial import distance_matrix
from overcooked_simulator.counters import (
......@@ -16,6 +15,7 @@ from overcooked_simulator.counters import (
TomatoDispenser,
ServingWindow,
PlateReturn,
Stove,
)
......@@ -69,6 +69,7 @@ class Environment:
"T": TomatoDispenser,
"P": PlateReturn,
"E": None,
"U": Stove, # Stove with pot: U because it looks like a pot
}
self.counters: list[Counter] = self.create_counters(self.layout_path)
......@@ -192,7 +193,7 @@ class Environment:
old_pos_other = collided_player.pos.copy()
collided_player.move(pushing_vector * (collided_player.move_dist / 2))
if self.detect_collision_counters(
collided_player
collided_player
) or self.detect_collision_world_bounds(player):
collided_player.move_abs(old_pos_other)
player.move_abs(old_pos)
......@@ -226,9 +227,9 @@ class Environment:
Returns: True if the player is intersecting with any object in the environment.
"""
return (
len(self.get_collided_players(player)) != 0
or self.detect_collision_counters(player)
or self.detect_collision_world_bounds(player)
len(self.get_collided_players(player)) != 0
or self.detect_collision_counters(player)
or self.detect_collision_world_bounds(player)
)
def get_collided_players(self, player: Player) -> list[Player]:
......@@ -323,7 +324,7 @@ class Environment:
and time limits.
"""
for counter in self.counters:
if isinstance(counter, CuttingBoard):
if isinstance(counter, (CuttingBoard, Stove)):
counter.progress()
def get_state(self):
......
......@@ -4,7 +4,7 @@ import numpy as np
import numpy.typing as npt
from overcooked_simulator.counters import Counter
from overcooked_simulator.game_items import HoldableItem
from overcooked_simulator.game_items import Item
class Player:
......@@ -17,7 +17,7 @@ class Player:
def __init__(self, name: str, pos: npt.NDArray[float]):
self.name: str = name
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.move_dist: int = 5
......@@ -81,7 +81,7 @@ class Player:
self.holding = counter.drop_off(self.holding)
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)
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 (
TomatoDispenser,
PlateReturn,
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.overcooked_environment import Action
from overcooked_simulator.simulation_runner import Simulator
......@@ -31,8 +38,9 @@ BACKGROUND_LINES_COLOR = (200, 200, 200)
KNIFE_COLOR = (120, 120, 120)
PLATE_RETURN_COLOR = (170, 170, 240)
BOARD_COLOR = (239, 193, 151)
POT_COLOR = (130, 130, 130)
USE_COOK_SPRITE = False
USE_COOK_SPRITE = True
class PlayerKeySet:
......@@ -194,7 +202,7 @@ class PyGameGUI:
holding_item_pos = player.pos + (20 * player.facing_direction)
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."""
if isinstance(item, Tomato):
if item.finished:
......@@ -218,6 +226,31 @@ class PyGameGUI:
if item.holds is not None:
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:
self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed)
......@@ -289,6 +322,7 @@ class PyGameGUI:
)
pygame.draw.rect(self.screen, RED, board_rect)
self.draw_item(counter.pos, Tomato())
if isinstance(counter, ServingWindow):
board_size = 33
board_rect = pygame.Rect(
......@@ -299,6 +333,17 @@ class PyGameGUI:
)
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 isinstance(counter.occupied_by, list):
for i, o in enumerate(counter.occupied_by):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment