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

Added game mechanic washing dishes. Served plates return in the plate return,...

Added game mechanic washing dishes. Served plates return in the plate return, can be washed in sink. TODO can wash multiple plates in the sink by holding interact button
parent 463faa1a
No related branches found
No related tags found
1 merge request!20Resolve "Plate washing cycle"
Pipeline #42372 passed
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from overcooked_simulator.overcooked_environment import GameScore
from overcooked_simulator.overcooked_environment import (
GameScore,
PlateManager,
SinkManager,
)
import numpy as np
import numpy.typing as npt
......@@ -14,10 +17,10 @@ from overcooked_simulator.game_items import (
Item,
CookingEquipment,
Meal,
Plate,
ItemInfo,
)
log = logging.getLogger(__name__)
class Counter:
"""Simple class for a counter at a specified position (center of counter). Can hold things on top."""
......@@ -115,15 +118,18 @@ class CuttingBoard(Counter):
class ServingWindow(Counter):
def __init__(self, pos, game_score: GameScore):
def __init__(self, pos, game_score: GameScore, plate_manager: PlateManager):
self.game_score = game_score
self.plate_manager = plate_manager
super().__init__(pos)
def drop_off(self, item) -> Item | None:
reward = 5
log.debug(f"Drop off {item}")
print(item)
# TODO define rewards
self.game_score.increment_score(reward)
self.plate_manager.number_returned_plates += 1
self.plate_manager.update_plate_return()
return None
def can_score(self, item):
......@@ -171,6 +177,37 @@ class Dispenser(Counter):
return f"{self.dispensing.name}Dispenser"
class DirtyPlateReturn(Counter):
def __init__(self, pos, dispensing, plate_manager: PlateManager):
self.plate_manager = plate_manager
self.dispensing = dispensing
super().__init__(pos)
self.occupied_by = [
self.dispensing.create_item()
for _ in range(self.plate_manager.number_returned_plates)
]
def pick_up(self, on_hands: bool = True):
return_this = self.occupied_by.pop()
if self.plate_manager.number_returned_plates > 1:
self.plate_manager.number_returned_plates -= 1
else:
self.occupied_by = []
return return_this
def can_drop_off(self, item: Item) -> bool:
return False
def update(self):
if self.plate_manager.number_returned_plates == 0:
self.occupied_by = []
else:
self.occupied_by.append(self.dispensing.create_item())
def __repr__(self):
return "PlateReturn"
class Trash(Counter):
def pick_up(self, on_hands: bool = True):
pass
......@@ -200,3 +237,69 @@ class Stove(Counter):
and self.occupied_by.can_progress()
):
self.occupied_by.progress()
class Sink(Counter):
def __init__(self, pos, sink_manager):
super().__init__(pos)
self.progressing = False
self.sink_manager: SinkManager = sink_manager
self.occupied_by = None
# TODO: can put multiple things in here and hold the button to do all.
def progress(self):
"""Called by environment step function for time progression"""
if self.progressing:
if isinstance(self.occupied_by, Plate):
self.occupied_by.progress()
if self.occupied_by.finished:
self.progressing = False
self.occupied_by = None
self.sink_manager.update_move_plate()
def start_progress(self):
"""Starts the cutting process."""
self.progressing = True
def pause_progress(self):
"""Pauses the cutting process"""
self.progressing = False
def interact_start(self):
"""Handles player interaction, starting to hold key down."""
print(self.occupied_by)
if isinstance(self.occupied_by, Plate):
print(self.occupied_by.can_progress())
self.start_progress()
def interact_stop(self):
"""Handles player interaction, stopping to hold key down."""
self.pause_progress()
def can_drop_off(self, item: Item) -> bool:
return isinstance(item, Plate) and not item.clean
class SinkAddon(Counter):
def __init__(self, pos, dispensing: ItemInfo):
super().__init__(pos)
self.dispensing = dispensing
self.number_clean_plates = 0
self.occupied_by = [
self.dispensing.create_item() for _ in range(self.number_clean_plates)
]
def can_drop_off(self, item: Item) -> bool:
return False
def add_clean_plate(self):
self.number_clean_plates += 1
plate = self.dispensing.create_item()
if isinstance(plate, Plate):
plate.finished_call()
self.occupied_by.append(plate)
def pick_up(self, on_hands: bool = True):
return_this = self.occupied_by.pop()
return return_this
counter_side_length: 40
world_width: 800
world_height: 600
max_number_plates: 25
\ No newline at end of file
......@@ -22,14 +22,14 @@ Burger:
Salad:
type: Meal
needs: [ ChoppedLettuce, ChoppedTomato ]
needs: [ ChoppedLettuce, Tomato ]
equipment: Plate
TomatoSoup:
type: Meal
finished_progress_name: TomatoSoup
steps_needed: 500
needs: [ ChoppedTomato, ChoppedTomato, ChoppedTomato ]
needs: [ Tomato, Tomato, Tomato ]
equipment: Pot
OnionSoup:
......@@ -41,6 +41,8 @@ OnionSoup:
Plate:
type: Equipment
is_cuttable: True
steps_needed: 200
Pot:
type: Equipment
_________________
_CCUCTNLCC_______
_C_______C_______
_C_______C_______
_##U#TNL##_______
_#_______#_______
_#_______#_______
_W_______________
_C__A__A_________
_#__A__A_________
_P_______________
_C_______C_______
_C_______X_______
_CCBBCCCCC_______
_#_______#_______
_#_______X_______
_##BB##S+#_______
_________________
......@@ -45,7 +45,16 @@ class ItemInfo:
)
return Item(name=self.name, item_info=self)
case ItemType.Equipment:
return CookingEquipment(name=self.name, item_info=self)
if "Plate" in self.name:
return Plate(
name=self.name,
steps_needed=self.steps_needed,
finished=False,
item_info=self,
clean="Clean" in self.name,
)
else:
return CookingEquipment(name=self.name, item_info=self)
case ItemType.Meal:
return Meal(
name=self.name,
......@@ -153,7 +162,7 @@ class CookingEquipment(Item):
if isinstance(other, CookingEquipment):
return other.can_release_content()
# TODO check other is start of a meal, create meal
if isinstance(other, Meal) and self.name == "Plate":
if isinstance(other, Meal) and "Plate" in self.name:
return not other.steps_needed or other.finished
return self.item_info.can_start_meal(other)
return self.content.can_combine(other)
......@@ -163,7 +172,7 @@ class CookingEquipment(Item):
if isinstance(other, CookingEquipment):
self.content = other.release()
return other
if isinstance(other, Meal) and self.name == "Plate":
if isinstance(other, Meal) and "Plate" in self.name:
self.content = other
return
# find starting meal for other
......@@ -230,3 +239,42 @@ class Meal(ProgressibleItem):
@property
def extra_repr(self):
return self.parts
class Plate(CookingEquipment):
def __init__(
self, clean, steps_needed, finished, content: Meal = None, *args, **kwargs
):
super().__init__(content, *args, **kwargs)
self.clean = clean
self.name = self.__repr__()
self.steps_needed = steps_needed
self.finished = finished
self.progressed_steps = steps_needed if finished else 0
def finished_call(self):
self.name = "CleanPlate"
self.clean = True
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 can_combine(self, other):
return super().can_combine(other) and self.clean
def combine(self, other):
return super().combine(other)
def __repr__(self):
if self.clean:
return "CleanPlate"
else:
return "DirtyPlate"
......@@ -3,6 +3,7 @@ from __future__ import annotations
import logging
import random
from pathlib import Path
from typing import Optional
import numpy as np
import numpy.typing as npt
......@@ -16,6 +17,9 @@ from overcooked_simulator.counters import (
Dispenser,
ServingWindow,
Stove,
Sink,
DirtyPlateReturn,
SinkAddon,
)
from overcooked_simulator.game_items import ItemInfo
# if TYPE_CHECKING:
......@@ -24,18 +28,6 @@ from overcooked_simulator.player import Player
log = logging.getLogger(__name__)
class GameScore:
def __init__(self):
self.score = 0
def increment_score(self, score: int):
self.score += score
log.debug(f"Score: {self.score}")
def read_score(self):
return self.score
class Action:
"""Action class, specifies player, action type and action itself."""
......@@ -53,6 +45,48 @@ class Action:
return f"Action({self.player},{self.act_type},{self.action})"
class GameScore:
def __init__(self):
self.score = 0
def increment_score(self, score: int):
self.score += score
log.debug(f"Score: {self.score}")
def read_score(self):
return self.score
class PlateManager:
def __init__(self, max_number_plates):
self.max_number_plates = max_number_plates
self.number_returned_plates = max_number_plates
self.plates_out_of_kitchen = 0
self.dirty_plate_return = None
def return_plate(self):
self.number_returned_plates += 1
def register_plate_return(self, plate_return: DirtyPlateReturn):
self.dirty_plate_return = plate_return
def update_plate_return(self):
self.dirty_plate_return.update()
class SinkManager:
def __init__(self):
self.sink: Optional[Sink] = None
self.sink_addon: Optional[SinkAddon] = None
def register_sink_addon(self, sink_addon: SinkAddon):
self.sink_addon = sink_addon
def update_move_plate(self):
self.sink_addon.add_clean_plate()
class Environment:
"""Environment class which handles the game logic for the overcooked-inspired environment.
......@@ -72,14 +106,19 @@ class Environment:
self.item_info = self.load_item_info()
self.game_score = GameScore()
self.plate_manager = PlateManager(environment_config["max_number_plates"])
self.sink_manager = SinkManager()
self.SYMBOL_TO_CHARACTER_MAP = {
"C": Counter,
"#": Counter, # because # looks a bit like a counter
"B": CuttingBoard,
"X": Trash,
"W": lambda pos: ServingWindow(pos, self.game_score),
"W": lambda pos: ServingWindow(pos, self.game_score, self.plate_manager),
"T": lambda pos: Dispenser(pos, self.item_info["Tomato"]),
"L": lambda pos: Dispenser(pos, self.item_info["Lettuce"]),
"P": lambda pos: Dispenser(pos, self.item_info["Plate"]),
"P": lambda pos: DirtyPlateReturn(
pos, self.item_info["Plate"], self.plate_manager
),
"N": lambda pos: Dispenser(pos, self.item_info["Onion"]), # N for oNioN
"_": "Free",
"A": "Agent",
......@@ -87,6 +126,8 @@ class Environment:
pos,
self.item_info["Pot"].create_item(),
), # Stove with pot: U because it looks like a pot
"S": lambda pos: Sink(pos, self.sink_manager),
"+": lambda pos: SinkAddon(pos, self.item_info["Plate"]),
}
(
......@@ -126,6 +167,8 @@ class Environment:
designated_player_positions: list[npt.NDArray] = []
free_positions: list[npt.NDArray] = []
# TODO: Sink and sink addon have to be next to each other!
with open(layout_file, "r") as layout_file:
lines = layout_file.readlines()
for line in lines:
......@@ -137,7 +180,11 @@ class Environment:
counter_class = self.SYMBOL_TO_CHARACTER_MAP[character]
if not isinstance(counter_class, str):
counter = counter_class(pos)
if isinstance(counter, DirtyPlateReturn):
self.plate_manager.register_plate_return(counter)
counters.append(counter)
if isinstance(counter, SinkAddon):
self.sink_manager.register_sink_addon(counter)
else:
if counter_class == "Agent":
designated_player_positions.append(
......@@ -396,7 +443,7 @@ class Environment:
and time limits.
"""
for counter in self.counters:
if isinstance(counter, (CuttingBoard, Stove)):
if isinstance(counter, (CuttingBoard, Stove, Sink)):
counter.progress()
def get_state(self):
......
......@@ -107,7 +107,7 @@ class Player:
elif counter.can_drop_off(self.holding):
self.holding = counter.drop_off(self.holding)
elif self.holding.can_combine(counter.occupied_by):
elif not isinstance(counter.occupied_by, list) and self.holding.can_combine(counter.occupied_by):
returned_by_counter = counter.pick_up(on_hands=False)
self.holding.combine(returned_by_counter)
......
overcooked_simulator/pygame_gui/images/plate_clean.png

334 KiB

overcooked_simulator/pygame_gui/images/plate_dirty.png

269 KiB

......@@ -15,13 +15,14 @@ from overcooked_simulator.game_items import (
Item,
CookingEquipment,
Meal,
Plate,
)
from overcooked_simulator.overcooked_environment import Action
from overcooked_simulator.pygame_gui.game_colors import BLUE
from overcooked_simulator.pygame_gui.game_colors import colors, Color
from overcooked_simulator.simulation_runner import Simulator
USE_PLAYER_COOK_SPRITES = False
USE_PLAYER_COOK_SPRITES = True
SHOW_INTERACTION_RANGE = False
......@@ -324,7 +325,7 @@ class PyGameGUI:
pos, self.visualization_config[item.name]["parts"], scale=scale
)
if isinstance(item, ProgressibleItem) and not item.finished:
if isinstance(item, (ProgressibleItem, Plate)) and not item.finished:
self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed)
if isinstance(item, CookingEquipment) and item.content:
......
......@@ -21,7 +21,7 @@ CuttingBoard:
center_offset: [ +0.15, -0.2 ]
color: silver
PlateDispenser:
DirtyPlateReturn:
parts:
- type: rect
height: 0.95
......@@ -76,6 +76,26 @@ Stove:
type: circle
radius: 0.25
Sink:
parts:
- color: black
type: rect
height: 0.875
width: 0.625
- color: darkslategray1
type: circle
radius: 0.4
SinkAddon:
parts:
- color: black
type: rect
height: 0.875
width: 0.625
- color: darkslategray1
type: circle
radius: 0.4
# Items
Tomato:
parts:
......@@ -175,11 +195,17 @@ Cook:
path: images/pixel_cook.png
size: 1
Plate:
CleanPlate:
parts:
- type: image
path: images/plate.png
size: 1
path: images/plate_clean.png
size: 0.8
DirtyPlate:
parts:
- type: image
path: images/plate_dirty.png
size: 0.8
Pot:
parts:
......
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