"""Tests for the Zone class.""" from mudlib.object import Object from mudlib.zone import Zone # --- construction --- def test_zone_basic_creation(): """Zone created with name, dimensions, and terrain.""" terrain = [["." for _ in range(10)] for _ in range(5)] zone = Zone(name="test", width=10, height=5, terrain=terrain) assert zone.name == "test" assert zone.width == 10 assert zone.height == 5 assert zone.toroidal is True # default assert zone.location is None # zones are top-level def test_zone_bounded(): """Zone can be created with toroidal=False (bounded).""" terrain = [["." for _ in range(5)] for _ in range(5)] zone = Zone(name="room", width=5, height=5, terrain=terrain, toroidal=False) assert zone.toroidal is False def test_zone_default_impassable(): """Zone has default impassable set of mountain and water.""" terrain = [["."]] zone = Zone(name="test", width=1, height=1, terrain=terrain) assert "^" in zone.impassable assert "~" in zone.impassable # --- can_accept --- def test_zone_can_accept_returns_true(): """Zones accept everything.""" terrain = [["."]] zone = Zone(name="test", width=1, height=1, terrain=terrain) obj = Object(name="rock") assert zone.can_accept(obj) is True # --- wrap --- def test_wrap_toroidal(): """Toroidal zone wraps coordinates around both axes.""" terrain = [["." for _ in range(100)] for _ in range(100)] zone = Zone(name="overworld", width=100, height=100, terrain=terrain) assert zone.wrap(105, 205) == (5, 5) assert zone.wrap(-1, -1) == (99, 99) assert zone.wrap(50, 50) == (50, 50) def test_wrap_bounded(): """Bounded zone clamps coordinates to edges.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="room", width=10, height=10, terrain=terrain, toroidal=False) assert zone.wrap(15, 15) == (9, 9) assert zone.wrap(-5, -3) == (0, 0) assert zone.wrap(5, 5) == (5, 5) # --- get_tile --- def test_get_tile(): """get_tile returns the terrain character at a position.""" terrain = [ [".", "T", "^"], ["~", ".", ":"], ] zone = Zone(name="test", width=3, height=2, terrain=terrain) assert zone.get_tile(0, 0) == "." assert zone.get_tile(1, 0) == "T" assert zone.get_tile(2, 0) == "^" assert zone.get_tile(0, 1) == "~" assert zone.get_tile(2, 1) == ":" def test_get_tile_wraps(): """get_tile wraps coordinates on toroidal zone.""" terrain = [["." for _ in range(5)] for _ in range(5)] terrain[0][0] = "T" zone = Zone(name="test", width=5, height=5, terrain=terrain) assert zone.get_tile(5, 5) == "T" # wraps to (0, 0) # --- is_passable --- def test_is_passable_grass(): """Grass tiles are passable.""" terrain = [["."]] zone = Zone(name="test", width=1, height=1, terrain=terrain) assert zone.is_passable(0, 0) is True def test_is_passable_mountain(): """Mountain tiles are impassable.""" terrain = [["^"]] zone = Zone(name="test", width=1, height=1, terrain=terrain) assert zone.is_passable(0, 0) is False def test_is_passable_water(): """Water tiles are impassable.""" terrain = [["~"]] zone = Zone(name="test", width=1, height=1, terrain=terrain) assert zone.is_passable(0, 0) is False def test_is_passable_custom_impassable(): """Zone can have custom impassable set.""" terrain = [[".", "X"]] zone = Zone(name="test", width=2, height=1, terrain=terrain, impassable={"X"}) assert zone.is_passable(0, 0) is True assert zone.is_passable(1, 0) is False # --- get_viewport --- def test_get_viewport_centered(): """Viewport returns terrain slice centered on given position.""" terrain = [["." for _ in range(21)] for _ in range(11)] terrain[5][10] = "@" # center zone = Zone(name="test", width=21, height=11, terrain=terrain) vp = zone.get_viewport(10, 5, 5, 3) # 5 wide, 3 tall, centered on (10, 5) # Should be columns 8-12, rows 4-6 assert len(vp) == 3 assert len(vp[0]) == 5 assert vp[1][2] == "@" # center of viewport def test_get_viewport_wraps_toroidal(): """Viewport wraps around edges of toroidal zone.""" terrain = [["." for _ in range(10)] for _ in range(10)] terrain[0][0] = "T" # top-left corner zone = Zone(name="test", width=10, height=10, terrain=terrain) # Viewport centered at (0, 0), size 5x5 vp = zone.get_viewport(0, 0, 5, 5) # Center of viewport is (2, 2), should be "T" assert vp[2][2] == "T" # Top-left of viewport wraps to bottom-right of zone assert len(vp) == 5 assert len(vp[0]) == 5 def test_get_viewport_full_size(): """Viewport with standard 21x11 size works.""" terrain = [["." for _ in range(100)] for _ in range(100)] zone = Zone(name="test", width=100, height=100, terrain=terrain) vp = zone.get_viewport(50, 50, 21, 11) assert len(vp) == 11 assert len(vp[0]) == 21 # --- contents_at --- def test_contents_at_empty(): """contents_at returns empty list when nothing at position.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) assert zone.contents_at(5, 5) == [] def test_contents_at_finds_objects(): """contents_at returns objects at the given coordinates.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) obj = Object(name="rock", location=zone, x=3, y=7) result = zone.contents_at(3, 7) assert obj in result def test_contents_at_excludes_other_positions(): """contents_at only returns objects at exact coordinates.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) Object(name="rock", location=zone, x=3, y=7) Object(name="tree", location=zone, x=5, y=5) result = zone.contents_at(3, 7) assert len(result) == 1 assert result[0].name == "rock" def test_contents_at_multiple(): """contents_at returns all objects at the same position.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) rock = Object(name="rock", location=zone, x=5, y=5) gem = Object(name="gem", location=zone, x=5, y=5) result = zone.contents_at(5, 5) assert rock in result assert gem in result assert len(result) == 2 # --- contents_near --- def test_contents_near_empty(): """contents_near returns empty list when nothing nearby.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) assert zone.contents_near(5, 5, 3) == [] def test_contents_near_includes_exact_position(): """contents_near includes objects at the exact coordinates.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain) obj = Object(name="rock", location=zone, x=5, y=5) result = zone.contents_near(5, 5, 3) assert obj in result def test_contents_near_within_range(): """contents_near returns objects within Manhattan distance range.""" terrain = [["." for _ in range(20)] for _ in range(20)] zone = Zone(name="test", width=20, height=20, terrain=terrain) center = Object(name="center", location=zone, x=10, y=10) nearby1 = Object(name="nearby1", location=zone, x=11, y=10) # distance 1 nearby2 = Object(name="nearby2", location=zone, x=10, y=12) # distance 2 nearby3 = Object(name="nearby3", location=zone, x=12, y=12) # distance 4 far = Object(name="far", location=zone, x=15, y=15) # distance 10 result = zone.contents_near(10, 10, 3) assert center in result assert nearby1 in result assert nearby2 in result assert nearby3 not in result # distance 4 > range 3 assert far not in result def test_contents_near_wrapping_toroidal(): """contents_near uses wrapping-aware distance on toroidal zones.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain, toroidal=True) # Object at (1, 1), query at (9, 9) # Without wrapping: dx=8, dy=8 # With wrapping: dx=min(8, 10-8)=2, dy=min(8, 10-8)=2, total=4 obj = Object(name="wrapped", location=zone, x=1, y=1) result = zone.contents_near(9, 9, 5) assert obj in result # within range due to wrapping result_tight = zone.contents_near(9, 9, 3) assert obj not in result_tight # outside tighter range def test_contents_near_no_wrapping_bounded(): """contents_near uses straight distance on bounded zones.""" terrain = [["." for _ in range(10)] for _ in range(10)] zone = Zone(name="test", width=10, height=10, terrain=terrain, toroidal=False) # Object at (1, 1), query at (9, 9) # Straight distance: dx=8, dy=8, total=16 obj = Object(name="far", location=zone, x=1, y=1) result = zone.contents_near(9, 9, 5) assert obj not in result # too far on bounded zone def test_contents_near_multiple(): """contents_near returns all objects within range.""" terrain = [["." for _ in range(20)] for _ in range(20)] zone = Zone(name="test", width=20, height=20, terrain=terrain) obj1 = Object(name="obj1", location=zone, x=10, y=10) obj2 = Object(name="obj2", location=zone, x=11, y=10) obj3 = Object(name="obj3", location=zone, x=10, y=11) Object(name="far", location=zone, x=20, y=20) # far away result = zone.contents_near(10, 10, 2) assert len(result) == 3 assert obj1 in result assert obj2 in result assert obj3 in result