Add broadcast_to_spectators helper

Helper function sends messages to all players at the same x,y coordinates
as the source player, skipping the source player themselves. Used for IF
spectator broadcasting.
This commit is contained in:
Jared Miller 2026-02-09 16:24:48 -05:00
parent c3f8c8cf12
commit 3dd095b9ea
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 41 additions and 30 deletions

View file

@ -33,9 +33,7 @@ def _list_stories() -> list[str]:
if not _stories_dir.exists():
return []
return sorted(
p.stem
for p in _stories_dir.iterdir()
if p.suffix in _STORY_EXTENSIONS
p.stem for p in _stories_dir.iterdir() if p.suffix in _STORY_EXTENSIONS
)

View file

@ -3,6 +3,10 @@
import asyncio
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mudlib.player import Player
@dataclass
@ -103,3 +107,12 @@ class IFSession:
result = "".join(output)
return result.rstrip()
async def broadcast_to_spectators(player: "Player", message: str) -> None:
"""Send message to all other players at the same location."""
from mudlib.player import players
for other in players.values():
if other.name != player.name and other.x == player.x and other.y == player.y:
await other.send(message)

View file

@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, MagicMock
import pytest
from mudlib.if_session import IFResponse, IFSession
from mudlib.if_session import IFResponse
from mudlib.player import Player, players
@ -30,27 +30,33 @@ def clear_players():
@pytest.fixture
def player_a(mock_reader, mock_writer):
def player_a():
"""Player A at (5, 5) who will be playing IF."""
return Player(
name="PlayerA", x=5, y=5, reader=mock_reader, writer=mock_writer
)
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(name="PlayerA", x=5, y=5, reader=reader, writer=writer)
@pytest.fixture
def player_b(mock_reader, mock_writer):
def player_b():
"""Player B at (5, 5) who will be spectating."""
return Player(
name="PlayerB", x=5, y=5, reader=mock_reader, writer=mock_writer
)
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(name="PlayerB", x=5, y=5, reader=reader, writer=writer)
@pytest.fixture
def player_c(mock_reader, mock_writer):
def player_c():
"""Player C at different coords (10, 10)."""
return Player(
name="PlayerC", x=10, y=10, reader=mock_reader, writer=mock_writer
)
writer = MagicMock()
writer.write = MagicMock()
writer.drain = AsyncMock()
reader = MagicMock()
return Player(name="PlayerC", x=10, y=10, reader=reader, writer=writer)
@pytest.mark.asyncio
@ -79,11 +85,7 @@ async def test_spectator_sees_if_output(player_a, player_b):
await player_a.send(response.output)
# Broadcast to spectators
spectator_msg = (
f"[{player_a.name}'s terminal]\r\n"
f"> {command}\r\n"
f"{response.output}"
)
spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
await broadcast_to_spectators(player_a, spectator_msg)
# Player B should have received the message
@ -118,11 +120,7 @@ async def test_spectator_not_on_same_tile_sees_nothing(player_a, player_c):
await player_a.send(response.output)
# Broadcast to spectators
spectator_msg = (
f"[{player_a.name}'s terminal]\r\n"
f"> {command}\r\n"
f"{response.output}"
)
spectator_msg = f"[{player_a.name}'s terminal]\r\n> {command}\r\n{response.output}"
await broadcast_to_spectators(player_a, spectator_msg)
# Player C should NOT have received anything
@ -171,12 +169,14 @@ async def test_broadcast_to_spectators_skips_self(player_a, player_b):
@pytest.mark.asyncio
async def test_multiple_spectators(player_a, player_b, mock_reader, mock_writer):
async def test_multiple_spectators(player_a, player_b):
"""Multiple spectators at same location all see IF output."""
# Create a third player at same location
player_d = Player(
name="PlayerD", x=5, y=5, reader=mock_reader, writer=mock_writer
)
writer_d = MagicMock()
writer_d.write = MagicMock()
writer_d.drain = AsyncMock()
reader_d = MagicMock()
player_d = Player(name="PlayerD", x=5, y=5, reader=reader_d, writer=writer_d)
# Register all players
players[player_a.name] = player_a