diff --git a/src/mudlib/portal.py b/src/mudlib/portal.py new file mode 100644 index 0000000..568af2d --- /dev/null +++ b/src/mudlib/portal.py @@ -0,0 +1,25 @@ +"""Portal — a transition point between zones.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from mudlib.thing import Thing + + +@dataclass +class Portal(Thing): + """A portal connecting zones. + + Portals are non-portable Things that exist in zones and define + transitions to other zones via target coordinates. + """ + + target_zone: str = "" + target_x: int = 0 + target_y: int = 0 + + def __post_init__(self) -> None: + """Force portals to be non-portable.""" + self.portable = False + super().__post_init__() diff --git a/tests/test_portal.py b/tests/test_portal.py new file mode 100644 index 0000000..00ce906 --- /dev/null +++ b/tests/test_portal.py @@ -0,0 +1,96 @@ +"""Tests for the Portal class.""" + +from mudlib.object import Object +from mudlib.portal import Portal +from mudlib.thing import Thing +from mudlib.zone import Zone + +# --- construction --- + + +def test_portal_creation_minimal(): + """Portal can be created with just a name.""" + p = Portal(name="portal") + assert p.name == "portal" + assert p.location is None + assert p.target_zone == "" + assert p.target_x == 0 + assert p.target_y == 0 + + +def test_portal_creation_with_target(): + """Portal can be created with target zone and coordinates.""" + p = Portal(name="gateway", target_zone="dungeon", target_x=5, target_y=10) + assert p.target_zone == "dungeon" + assert p.target_x == 5 + assert p.target_y == 10 + + +def test_portal_is_thing_subclass(): + """Portal inherits from Thing.""" + p = Portal(name="portal") + assert isinstance(p, Thing) + + +def test_portal_is_object_subclass(): + """Portal inherits from Object (via Thing).""" + p = Portal(name="portal") + assert isinstance(p, Object) + + +def test_portal_always_non_portable(): + """Portal is always non-portable (cannot be picked up).""" + p = Portal(name="portal") + assert p.portable is False + + +def test_portal_forced_non_portable(): + """Portal forces portable=False even if explicitly set True.""" + # Even if we try to make it portable, it should be forced to False + p = Portal(name="portal", portable=True) + assert p.portable is False + + +def test_portal_inherits_description(): + """Portal can have a description (from Thing).""" + p = Portal(name="gateway", description="a shimmering portal") + assert p.description == "a shimmering portal" + + +def test_portal_inherits_aliases(): + """Portal can have aliases (from Thing).""" + p = Portal(name="gateway", aliases=["portal", "gate"]) + assert p.aliases == ["portal", "gate"] + + +def test_portal_in_zone(): + """Portal can exist in a zone with coordinates.""" + terrain = [["." for _ in range(10)] for _ in range(10)] + zone = Zone(name="test", width=10, height=10, terrain=terrain) + p = Portal( + name="gateway", + location=zone, + x=3, + y=7, + target_zone="dungeon", + target_x=5, + target_y=5, + ) + assert p.location is zone + assert p.x == 3 + assert p.y == 7 + assert p in zone.contents + + +def test_portal_rejects_contents(): + """Portal cannot accept other objects (uses Thing's default behavior).""" + p = Portal(name="portal") + obj = Object(name="rock") + assert p.can_accept(obj) is False + + +def test_portal_rejects_things(): + """Portal cannot accept things.""" + p = Portal(name="portal") + thing = Thing(name="sword") + assert p.can_accept(thing) is False