60 lines
2.0 KiB
Python
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())
|