-
Florian Schröder authoredFlorian Schröder authored
game_items.py 11.27 KiB
from __future__ import annotations
import collections
import dataclasses
import datetime
import logging
from enum import Enum
from typing import Optional
log = logging.getLogger(__name__)
class ItemType(Enum):
Ingredient = "Ingredient"
Meal = "Meal"
Equipment = "Equipment"
@dataclasses.dataclass
class ItemInfo:
type: ItemType = dataclasses.field(compare=False)
name: str = dataclasses.field(compare=True)
seconds: float = dataclasses.field(compare=False, default=0)
needs: list[ItemInfo] = dataclasses.field(compare=False, default_factory=list)
equipment: ItemInfo | None = dataclasses.field(compare=False, default=None)
_start_meals: list[ItemInfo] = dataclasses.field(
compare=False, default_factory=list
)
def __post_init__(self):
self.type = ItemType(self.type)
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]):
# 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 _return_start_meal(self, items: list[Item]) -> ItemInfo | None:
for meal in self._start_meals:
satisfied = [False for _ in range(len(items))]
for i, p in enumerate(items):
for _, n in enumerate(meal.needs):
if not satisfied[i] and p.name == n:
satisfied[i] = True
break
if all(satisfied):
return meal
class Item:
"""Base class for game items which can be held by a player."""
def __init__(self, name: str, item_info: ItemInfo, *args, **kwargs):
self.name = self.__class__.__name__ if name is None else name
self.item_info = item_info
self.progress_equipment = None
self.progress_percentage = 0.0
def __repr__(self):
if self.progress_equipment is None:
return f"{self.name}({self.extra_repr})"
else:
return f"{self.name}(progress={round(self.progress_percentage * 100, 2)}%,{self.extra_repr})"
def __eq__(self, other):
return other and self.name == other.name
@property
def extra_repr(self):
return ""
def can_combine(self, other):
return False
def combine(self, other) -> Item | None:
pass
def progress(self, equipment: str, percent: float):
"""Progresses the item process on the given equipment as long as it is not finished."""
if self.progress_equipment is None:
self.progress_equipment = equipment
if self.progress_equipment == equipment:
self.progress_percentage += percent
self.progress_percentage = min(self.progress_percentage, 1.0)
else:
log.warning(
f"{self.name} expected progress on {self.progress_equipment}, but got {percent * 100}% on {equipment}"
)
def reset(self):
self.progress_equipment = None
self.progress_percentage = 0.0
class CookingEquipment(Item):
def __init__(self, transitions: dict, *args, **kwargs):
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_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 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_list + other
)
return any(
ingredients <= recipe["recipe"] for recipe in self.transitions.values()
)
"""
def can_combine(self, other):
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):
return other.can_release_content()
# TODO check other is start of a meal, create meal
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])
if self.content.can_combine(other):
return True
if isinstance(other, CookingEquipment) and other.content:
other = other.content
return self.item_info.can_start_meal(
[other]
+ ([self.content] if self.content.progressed_steps else self.content.parts)
)
"""
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_list.append(other)
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"])
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):
if self.content is None:
if isinstance(other, CookingEquipment):
self.content = other.release()
return other
if isinstance(other, Meal) and "Plate" in self.name:
self.content = other
return
# find starting meal for other
self.content = self.item_info.start_meal([other])
return
if not self.content.can_combine(other):
if isinstance(other, CookingEquipment) and other.content:
content = other.release()
self.content = self.item_info.start_meal(
[content]
+ (
[self.content]
if self.content.progressed_steps
else self.content.parts
)
)
return other
else:
self.content = self.item_info.start_meal(
[other]
+ (
[self.content]
if self.content.progressed_steps
else self.content.parts
)
)
return
self.content.combine(other)
"""
def can_progress(self) -> bool:
return self.active_transition is not None
def progress(self, passed_time: datetime.timedelta, now: datetime.datetime):
percent = passed_time.total_seconds() / self.active_transition["seconds"]
super().progress(equipment=self.name, percent=percent)
if self.progress_percentage == 1.0:
self.content_list = [self.active_transition["result"]]
self.reset()
# 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_list
self.reset_content()
return content
@property
def extra_repr(self):
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 Plate(CookingEquipment):
def __init__(self, transitions, clean, content=None, *args, **kwargs):
self.clean = clean
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, equipment: str, percent: float):
Item.progress(self, equipment, percent)
# 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 can_progress(self, counter_type="Sink") -> bool:
# return not self.clean
def create_name(self):
return "Plate" 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
"""