From 7d4a75f9732165dfadfb8dac5abaffa8ba544589 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Wed, 11 Feb 2026 20:48:04 -0500 Subject: [PATCH] Show portals in look output Look command now displays portals separately from ground items. Portals at the player's position are shown after ground items with the format "Portals: name1, name2". This separates portals from regular items since they serve a different purpose in gameplay. --- src/mudlib/commands/look.py | 13 +++- tests/test_portal_display.py | 118 +++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 tests/test_portal_display.py diff --git a/src/mudlib/commands/look.py b/src/mudlib/commands/look.py index 1b0e6c7..68eb58c 100644 --- a/src/mudlib/commands/look.py +++ b/src/mudlib/commands/look.py @@ -103,13 +103,24 @@ async def cmd_look(player: Player, args: str) -> None: player.writer.write("\r\n".join(output_lines) + "\r\n") # Show items on the ground at player's position + from mudlib.portal import Portal + + contents_here = zone.contents_at(player.x, player.y) ground_items = [ - obj for obj in zone.contents_at(player.x, player.y) if isinstance(obj, Thing) + obj + for obj in contents_here + if isinstance(obj, Thing) and not isinstance(obj, Portal) ] + portals = [obj for obj in contents_here if isinstance(obj, Portal)] + if ground_items: names = ", ".join(_format_thing_name(item) for item in ground_items) player.writer.write(f"On the ground: {names}\r\n") + if portals: + names = ", ".join(p.name for p in portals) + player.writer.write(f"Portals: {names}\r\n") + await player.writer.drain() diff --git a/tests/test_portal_display.py b/tests/test_portal_display.py new file mode 100644 index 0000000..47b054a --- /dev/null +++ b/tests/test_portal_display.py @@ -0,0 +1,118 @@ +"""Tests for portal display in look command.""" + +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from mudlib.player import Player +from mudlib.portal import Portal +from mudlib.zone import Zone + + +@pytest.fixture +def mock_writer(): + writer = MagicMock() + writer.write = MagicMock() + writer.drain = AsyncMock() + return writer + + +@pytest.fixture +def mock_reader(): + return MagicMock() + + +@pytest.fixture +def test_zone(): + terrain = [["." for _ in range(10)] for _ in range(10)] + return Zone( + name="testzone", + width=10, + height=10, + toroidal=True, + terrain=terrain, + ) + + +@pytest.fixture +def player(mock_reader, mock_writer, test_zone): + p = Player( + name="TestPlayer", + x=5, + y=5, + reader=mock_reader, + writer=mock_writer, + location=test_zone, + ) + return p + + +@pytest.mark.asyncio +async def test_look_shows_portal_at_position(player, test_zone, mock_writer): + """look command shows portals at player position.""" + from mudlib.commands.look import cmd_look + + Portal( + name="shimmering doorway", + location=test_zone, + x=5, + y=5, + target_zone="elsewhere", + target_x=0, + target_y=0, + ) + + await cmd_look(player, "") + output = "".join([call[0][0] for call in mock_writer.write.call_args_list]) + assert "portal" in output.lower() and "shimmering doorway" in output.lower() + + +@pytest.mark.asyncio +async def test_look_shows_multiple_portals(player, test_zone, mock_writer): + """look command shows multiple portals at player position.""" + from mudlib.commands.look import cmd_look + + Portal( + name="red portal", + location=test_zone, + x=5, + y=5, + target_zone="redzone", + target_x=0, + target_y=0, + ) + Portal( + name="blue portal", + location=test_zone, + x=5, + y=5, + target_zone="bluezone", + target_x=0, + target_y=0, + ) + + await cmd_look(player, "") + output = "".join([call[0][0] for call in mock_writer.write.call_args_list]) + assert "red portal" in output.lower() + assert "blue portal" in output.lower() + + +@pytest.mark.asyncio +async def test_look_no_portals_at_position(player, test_zone, mock_writer): + """look command doesn't show portals when none at position.""" + from mudlib.commands.look import cmd_look + + Portal( + name="distant portal", + location=test_zone, + x=8, + y=8, + target_zone="elsewhere", + target_x=0, + target_y=0, + ) + + await cmd_look(player, "") + output = "".join([call[0][0] for call in mock_writer.write.call_args_list]) + # Should not mention portals when none are at player position + assert "portal" not in output.lower() or "distant portal" not in output.lower()