Skip to content
Snippets Groups Projects
Commit eff27363 authored by Florian Schröder's avatar Florian Schröder
Browse files

Merge branch...

Merge branch '66-replace-transitions-values-from-dict-just-to-the-iteminfo-because-it-already-stores-all-the-info' into 'main'

Resolve "replace transitions values from dict just to the iteminfo because it already stores all the info"

Closes #66

See merge request scs/cocosy/overcooked-simulator!32
parents e005a7ad 05c413f0
No related branches found
No related tags found
1 merge request!32Resolve "replace transitions values from dict just to the iteminfo because it already stores all the info"
Pipeline #44813 passed
......@@ -39,7 +39,7 @@ import uuid
from collections import deque
from collections.abc import Iterable
from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Optional, Callable, TypedDict
from typing import TYPE_CHECKING, Optional, Callable, Set
if TYPE_CHECKING:
from overcooked_simulator.overcooked_environment import (
......@@ -62,26 +62,6 @@ log = logging.getLogger(__name__)
COUNTER_CATEGORY = "Counter"
class TransitionsValueDict(TypedDict):
"""The values in the transitions dicts of the `CookingEquipment`."""
seconds: int | float
"""The needed seconds to progress for the transition."""
needs: list[str]
"""The names of the needed items for the transition."""
info: ItemInfo | str
"""The ItemInfo of the resulting item."""
class TransitionsValueByNameDict(TypedDict):
"""The values in the transitions dicts of the `CuttingBoard` and the `Sink`."""
seconds: int | float
"""The needed seconds to progress for the transition."""
result: str
"""The new name of the item after the transition."""
class Counter:
"""Simple class for a counter at a specified position (center of counter). Can hold things on top.
......@@ -198,11 +178,12 @@ class CuttingBoard(Counter):
The character `C` in the `layout` file represents the CuttingBoard.
"""
def __init__(
self, pos: np.ndarray, transitions: dict[str, TransitionsValueByNameDict]
):
def __init__(self, pos: np.ndarray, transitions: dict[str, ItemInfo]):
self.progressing = False
self.transitions = transitions
self.inverted_transition_dict = {
info.needs[0]: info for name, info in self.transitions.items()
}
super().__init__(pos=pos)
def progress(self, passed_time: timedelta, now: datetime):
......@@ -219,20 +200,20 @@ class CuttingBoard(Counter):
if (
self.occupied
and self.progressing
and self.occupied_by.name in self.transitions
and self.occupied_by.name in self.inverted_transition_dict
):
percent = (
passed_time.total_seconds()
/ self.transitions[self.occupied_by.name]["seconds"]
/ self.inverted_transition_dict[self.occupied_by.name].seconds
)
self.occupied_by.progress(
equipment=self.__class__.__name__, percent=percent
)
if self.occupied_by.progress_percentage == 1.0:
self.occupied_by.reset()
self.occupied_by.name = self.transitions[self.occupied_by.name][
"result"
]
self.occupied_by.name = self.inverted_transition_dict[
self.occupied_by.name
].name
def start_progress(self):
"""Starts the cutting process."""
......@@ -390,7 +371,7 @@ class PlateDispenser(Counter):
pos: npt.NDArray[float],
dispensing: ItemInfo,
plate_config: PlateConfig,
plate_transitions: dict,
plate_transitions: dict[str, ItemInfo],
**kwargs,
) -> None:
super().__init__(pos=pos, **kwargs)
......@@ -399,7 +380,7 @@ class PlateDispenser(Counter):
self.out_of_kitchen_timer = []
self.plate_config = plate_config
self.next_plate_time = datetime.max
self.plate_transitions: dict[str, TransitionsValueDict] = plate_transitions
self.plate_transitions = plate_transitions
self.setup_plates()
def pick_up(self, on_hands: bool = True) -> Item | None:
......@@ -565,17 +546,25 @@ class Sink(Counter):
def __init__(
self,
pos: npt.NDArray[float],
transitions: dict[str, TransitionsValueByNameDict],
transitions: dict[str, ItemInfo],
sink_addon: SinkAddon = None,
):
super().__init__(pos=pos)
self.progressing = False
self.sink_addon: SinkAddon = sink_addon
"""The connected sink addon which will receive the clean plates"""
self.occupied_by = deque()
self.occupied_by: deque[Plate] = deque()
"""The queue of dirty plates. Only the one on the top is progressed."""
self.transitions = transitions
"""The allowed transitions for the items in the sink. Here only clean plates transfer from dirty plates."""
self.transition_needs: Set[str] = set()
"""Set of all first needs of the transition item info."""
for name, info in transitions.items():
assert (
len(info.needs) >= 1
), "transitions in a Sink need at least one item need."
self.transition_needs.update([info.needs[0]])
@property
def occupied(self):
......@@ -586,23 +575,21 @@ class Sink(Counter):
if (
self.occupied
and self.progressing
and self.occupied_by[-1].name in self.transitions
and self.occupied_by[-1].name in self.transition_needs
):
percent = (
passed_time.total_seconds()
/ self.transitions[self.occupied_by[-1].name]["seconds"]
)
self.occupied_by[-1].progress(
equipment=self.__class__.__name__, percent=percent
)
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()
plate.clean = True
self.sink_addon.add_clean_plate(plate)
for name, info in self.transitions.items():
if info.needs[0] == self.occupied_by[-1].name:
percent = passed_time.total_seconds() / info.seconds
self.occupied_by[-1].progress(
equipment=self.__class__.__name__, percent=percent
)
if self.occupied_by[-1].progress_percentage == 1.0:
self.occupied_by[-1].reset()
self.occupied_by[-1].name = name
plate = self.occupied_by.pop()
plate.clean = True
self.sink_addon.add_clean_plate(plate)
break
def start_progress(self):
"""Starts the cutting process."""
......
......@@ -26,7 +26,7 @@ import datetime
import logging
import uuid
from enum import Enum
from typing import Optional
from typing import Optional, TypedDict
log = logging.getLogger(__name__)
......@@ -85,10 +85,22 @@ class ItemInfo:
equipment: ItemInfo | None = dataclasses.field(compare=False, default=None)
"""On which the item can be created. `null`, `~` (None) converts to Plate."""
recipe: collections.Counter | None = None
"""Internally set in CookingEquipment"""
def __post_init__(self):
self.type = ItemType(self.type)
class ActiveTransitionTypedDict(TypedDict):
"""The values in the active transitions dicts of `CookingEquipment`."""
seconds: int | float
"""The needed seconds to progress for the transition."""
result: str
"""The new name of the item after the transition."""
class Item:
"""Base class for game items which can be held by a player."""
......@@ -151,13 +163,12 @@ class Item:
class CookingEquipment(Item):
"""Pot, Pan, ... that can hold items. It holds the progress of the content (e.g., the soup) in itself (
progress_percentage) and not in the items in the content list."""
item_category = "Cooking Equipment"
def __init__(self, transitions: dict, *args, **kwargs):
def __init__(self, transitions: dict[str, ItemInfo], *args, **kwargs):
super().__init__(*args, **kwargs)
self.transitions = transitions
self.active_transition: Optional[dict] = None
self.active_transition: Optional[ActiveTransitionTypedDict] = None
"""The info how and when to convert the content_list to a new item."""
# TODO change content ready just to str (name of the item)?
......@@ -171,7 +182,7 @@ class CookingEquipment(Item):
log.debug(f"Initialize {self.name}: {self.transitions}")
for transition in self.transitions.values():
transition["recipe"] = collections.Counter(transition["needs"])
transition.recipe = collections.Counter(transition.needs)
def can_combine(self, other) -> bool:
# already cooking or nothing to combine
......@@ -187,9 +198,7 @@ class CookingEquipment(Item):
ingredients = collections.Counter(
item.name for item in self.content_list + other
)
return any(
ingredients <= recipe["recipe"] for recipe in self.transitions.values()
)
return any(ingredients <= recipe.recipe for recipe in self.transitions.values())
def combine(self, other) -> Item | None:
return_value = None
......@@ -204,14 +213,13 @@ class CookingEquipment(Item):
ingredients = collections.Counter(item.name for item in self.content_list)
for result, transition in self.transitions.items():
recipe = transition["recipe"]
if ingredients == recipe:
if transition["seconds"] == 0:
self.content_ready = Item(name=result, item_info=transition["info"])
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["info"]),
"seconds": transition.seconds,
"result": Item(name=result, item_info=transition),
}
break
else:
......@@ -279,9 +287,7 @@ class Plate(CookingEquipment):
"""All meals can be hold by a clean plate"""
super().__init__(
name=self.create_name(),
transitions={
k: v for k, v in transitions.items() if not v["info"].equipment
},
transitions={k: v for k, v in transitions.items() if not v.equipment},
*args,
**kwargs,
)
......
......@@ -239,6 +239,7 @@ class Visualizer:
case "image":
if "center_offset" in part:
d = np.array(part["center_offset"]) * grid_size
pos = np.array(pos)
pos += d
self.draw_image(
......
......@@ -138,15 +138,6 @@ class Environment:
},
)
"""The manager for the orders and score update."""
plate_transitions = {
item: {
"seconds": info.seconds,
"needs": info.needs,
"info": info,
}
for item, info in self.item_info.items()
if info.type == ItemType.Meal
}
cooking_counter_equipments = {
cooking_counter: [
......@@ -161,13 +152,10 @@ class Environment:
self.SYMBOL_TO_CHARACTER_MAP = {
"#": Counter,
"C": lambda pos: CuttingBoard(
pos,
{
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 == "CuttingBoard"
},
pos=pos,
transitions=self.filter_item_info(
self.item_info, by_equipment_name="CuttingBoard"
),
),
"X": Trashcan,
"W": lambda pos: ServingWindow(
......@@ -184,7 +172,9 @@ class Environment:
"E": lambda pos: Dispenser(pos, self.item_info["Cheese"]), # chEEEEse
"G": lambda pos: Dispenser(pos, self.item_info["Sausage"]), # sausaGe
"P": lambda pos: PlateDispenser(
plate_transitions=plate_transitions,
plate_transitions=self.filter_item_info(
item_info=self.item_info, by_item_type=ItemType.Meal
),
pos=pos,
dispensing=self.item_info["Plate"],
plate_config=PlateConfig(
......@@ -205,15 +195,9 @@ class Environment:
occupied_by=CookingEquipment(
name="Pot",
item_info=self.item_info["Pot"],
transitions={
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"
},
transitions=self.filter_item_info(
self.item_info, by_equipment_name="Pot"
),
),
), # Stove with pot: U because it looks like a pot
"Q": lambda pos: CookingCounter(
......@@ -223,15 +207,9 @@ class Environment:
occupied_by=CookingEquipment(
name="Pan",
item_info=self.item_info["Pan"],
transitions={
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"
},
transitions=self.filter_item_info(
self.item_info, by_equipment_name="Pan"
),
),
), # Stove with pan: Q because it looks like a pan
"O": lambda pos: CookingCounter(
......@@ -241,15 +219,9 @@ class Environment:
occupied_by=CookingEquipment(
name="Peel",
item_info=self.item_info["Peel"],
transitions={
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 == "Peel"
},
transitions=self.filter_item_info(
self.item_info, by_equipment_name="Peel"
),
),
),
"F": lambda pos: CookingCounter(
......@@ -259,27 +231,18 @@ class Environment:
occupied_by=CookingEquipment(
name="Basket",
item_info=self.item_info["Basket"],
transitions={
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 == "Basket"
},
transitions=self.filter_item_info(
self.item_info, by_equipment_name="Basket"
),
),
), # 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,
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"
},
transitions=self.filter_item_info(
item_info=self.item_info, by_equipment_name="Sink"
),
),
"+": SinkAddon,
}
......@@ -311,19 +274,19 @@ class Environment:
"""The relative env time when it will stop/end"""
log.debug(f"End time: {self.env_time_end}")
@property
def game_ended(self) -> bool:
"""Whether the game is over or not based on the calculated `Environment.env_time_end`"""
return self.env_time >= self.env_time_end
def get_env_time(self):
"""the internal time of the environment. An environment starts always with the time from `create_init_env_time`.
Utility method to pass a reference to the serving window."""
return self.env_time
@property
def game_ended(self) -> bool:
"""Whether the game is over or not based on the calculated `Environment.env_time_end`"""
return self.env_time >= self.env_time_end
def load_item_info(self, data) -> dict[str, ItemInfo]:
"""Load `item_info.yml` if only the path is given, create ItemInfo classes and replace equipment strings with item infos."""
"""Load `item_info.yml`, create ItemInfo classes and replace equipment strings with item infos."""
if self.as_files:
with open(data, "r") as file:
item_lookup = yaml.safe_load(file)
......@@ -797,3 +760,25 @@ class Environment:
"""Reset the env time to the initial time, defined by `create_init_env_time`."""
self.env_time = create_init_env_time()
log.debug(f"Reset env time to {self.env_time}")
@staticmethod
def filter_item_info(
item_info: dict[str, ItemInfo],
by_item_type: ItemType = None,
by_equipment_name: str = None,
) -> dict[str, ItemInfo]:
"""Filter the item info dict by item type or equipment name"""
if by_item_type is not None:
return {
name: info
for name, info in item_info.items()
if info.type == by_item_type
}
if by_equipment_name is not None:
return {
name: info
for name, info in item_info.items()
if info.equipment is not None
and info.equipment.name == by_equipment_name
}
return item_info
......@@ -5,7 +5,7 @@ import pytest
from overcooked_simulator import ROOT_DIR
from overcooked_simulator.counters import Counter, CuttingBoard
from overcooked_simulator.game_items import Item
from overcooked_simulator.game_items import Item, ItemInfo, ItemType
from overcooked_simulator.overcooked_environment import (
Action,
Environment,
......@@ -227,7 +227,15 @@ def test_processing(env_config, layout_config, item_info):
counter_pos = np.array([2, 2])
counter = CuttingBoard(
counter_pos,
transitions={"Tomato": {"seconds": 1, "result": "ChoppedTomato"}},
transitions={
"ChoppedTomato": ItemInfo(
name="ChoppedTomato",
seconds=0.5,
equipment=ItemInfo(name="CuttingBoard", type=ItemType.Equipment),
type=ItemType.Ingredient,
needs=["Tomato"],
)
},
)
env.counters.append(counter)
......
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