Mi2dRPGamEng/engine/inventory.py

60 lines
2.0 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from engine.item import ItemDef, ItemInstance
@dataclass
class PlacedItem:
item: ItemInstance
x: int
y: int
class Inventory:
"""A grid of cells that items occupy according to their w x h footprint."""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self._cells: list[str | None] = [None] * (width * height)
self._items: dict[str, PlacedItem] = {}
def _footprint(self, item_def: ItemDef, x: int, y: int) -> list[tuple[int, int]]:
return [(x + dx, y + dy) for dy in range(item_def.height) for dx in range(item_def.width)]
def can_place(self, item_def: ItemDef, x: int, y: int) -> bool:
for cx, cy in self._footprint(item_def, x, y):
if not (0 <= cx < self.width and 0 <= cy < self.height):
return False
if self._cells[cy * self.width + cx] is not None:
return False
return True
def place(self, item: ItemInstance, item_def: ItemDef, x: int, y: int) -> bool:
if not self.can_place(item_def, x, y):
return False
for cx, cy in self._footprint(item_def, x, y):
self._cells[cy * self.width + cx] = item.item_id
self._items[item.item_id] = PlacedItem(item, x, y)
return True
def remove(self, item_id: str, item_def: ItemDef) -> ItemInstance | None:
placed = self._items.pop(item_id, None)
if placed is None:
return None
for cx, cy in self._footprint(item_def, placed.x, placed.y):
self._cells[cy * self.width + cx] = None
return placed.item
def find_first_fit(self, item_def: ItemDef) -> tuple[int, int] | None:
for y in range(self.height - item_def.height + 1):
for x in range(self.width - item_def.width + 1):
if self.can_place(item_def, x, y):
return (x, y)
return None
def items(self) -> list[PlacedItem]:
return list(self._items.values())