From 621c42b833262d9b2f7130fb5e9bd69492d4c3e4 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Wed, 11 Feb 2026 20:38:40 -0500 Subject: [PATCH] Add Container class with capacity and open/closed state --- src/mudlib/container.py | 29 +++++++++ tests/test_container.py | 134 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/mudlib/container.py create mode 100644 tests/test_container.py diff --git a/src/mudlib/container.py b/src/mudlib/container.py new file mode 100644 index 0000000..e99195c --- /dev/null +++ b/src/mudlib/container.py @@ -0,0 +1,29 @@ +"""Container — a Thing that can hold other Things.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from mudlib.object import Object +from mudlib.thing import Thing + + +@dataclass +class Container(Thing): + """A container that can hold other items. + + Containers are Things with capacity limits and open/closed state. + The locked flag is for command-layer logic (unlock/lock commands). + """ + + capacity: int = 10 + closed: bool = False + locked: bool = False + + def can_accept(self, obj: Object) -> bool: + """Accept Things when open and below capacity.""" + if not isinstance(obj, Thing): + return False + if self.closed: + return False + return len(self._contents) < self.capacity diff --git a/tests/test_container.py b/tests/test_container.py new file mode 100644 index 0000000..d15d4b6 --- /dev/null +++ b/tests/test_container.py @@ -0,0 +1,134 @@ +"""Tests for the Container class.""" + +from mudlib.container import Container +from mudlib.object import Object +from mudlib.thing import Thing +from mudlib.zone import Zone + +# --- construction --- + + +def test_container_creation_minimal(): + """Container can be created with just a name.""" + c = Container(name="chest") + assert c.name == "chest" + assert c.capacity == 10 + assert c.closed is False + assert c.locked is False + + +def test_container_creation_with_custom_capacity(): + """Container can have a custom capacity.""" + c = Container(name="pouch", capacity=5) + assert c.capacity == 5 + + +def test_container_creation_closed(): + """Container can be created in closed state.""" + c = Container(name="chest", closed=True) + assert c.closed is True + + +def test_container_creation_locked(): + """Container can be created in locked state.""" + c = Container(name="chest", locked=True) + assert c.locked is True + + +def test_container_is_thing_subclass(): + """Container is a Thing subclass.""" + c = Container(name="chest") + assert isinstance(c, Thing) + assert isinstance(c, Object) + + +def test_container_inherits_thing_properties(): + """Container has all Thing properties.""" + c = Container( + name="ornate chest", + description="a beautifully carved wooden chest", + portable=False, + aliases=["chest", "box"], + ) + assert c.description == "a beautifully carved wooden chest" + assert c.portable is False + assert c.aliases == ["chest", "box"] + + +# --- can_accept --- + + +def test_container_accepts_thing_when_open(): + """Container accepts Things when open and has capacity.""" + c = Container(name="chest") + sword = Thing(name="sword") + assert c.can_accept(sword) is True + + +def test_container_rejects_when_closed(): + """Container rejects Things when closed.""" + c = Container(name="chest", closed=True) + sword = Thing(name="sword") + assert c.can_accept(sword) is False + + +def test_container_rejects_when_at_capacity(): + """Container rejects Things when at capacity.""" + c = Container(name="pouch", capacity=2) + # Add two items + Thing(name="rock1", location=c) + Thing(name="rock2", location=c) + # Third should be rejected + rock3 = Thing(name="rock3") + assert c.can_accept(rock3) is False + + +def test_container_accepts_when_below_capacity(): + """Container accepts Things when below capacity.""" + c = Container(name="pouch", capacity=2) + Thing(name="rock1", location=c) + # Second item should be accepted + rock2 = Thing(name="rock2") + assert c.can_accept(rock2) is True + + +def test_container_rejects_non_thing(): + """Container rejects objects that aren't Things.""" + c = Container(name="chest") + other = Object(name="abstract") + assert c.can_accept(other) is False + + +def test_container_locked_is_just_flag(): + """Locked flag is stored but doesn't affect can_accept (used by commands).""" + c = Container(name="chest", locked=True, closed=False) + sword = Thing(name="sword") + # can_accept doesn't check locked (commands will check it separately) + # This test documents current behavior — locked is for command layer + assert c.locked is True + # can_accept only checks closed and capacity + assert c.can_accept(sword) is True + + +# --- integration with zones --- + + +def test_container_in_zone(): + """Container can be placed in a zone with coordinates.""" + terrain = [["." for _ in range(10)] for _ in range(10)] + zone = Zone(name="test", width=10, height=10, terrain=terrain) + chest = Container(name="chest", location=zone, x=5, y=5) + assert chest.location is zone + assert chest.x == 5 + assert chest.y == 5 + assert chest in zone.contents + + +def test_container_with_contents(): + """Container can hold Things.""" + chest = Container(name="chest") + sword = Thing(name="sword", location=chest) + gem = Thing(name="gem", location=chest) + assert sword in chest.contents + assert gem in chest.contents + assert len(chest.contents) == 2