Wire IF mode into server shell loop and player model
This commit is contained in:
parent
d210033f33
commit
dc342224b1
3 changed files with 136 additions and 1 deletions
|
|
@ -10,6 +10,7 @@ from mudlib.entity import Entity
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from mudlib.editor import Editor
|
from mudlib.editor import Editor
|
||||||
|
from mudlib.if_session import IFSession
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
@ -22,6 +23,7 @@ class Player(Entity):
|
||||||
mode_stack: list[str] = field(default_factory=lambda: ["normal"])
|
mode_stack: list[str] = field(default_factory=lambda: ["normal"])
|
||||||
caps: ClientCaps = field(default_factory=lambda: ClientCaps(ansi=True))
|
caps: ClientCaps = field(default_factory=lambda: ClientCaps(ansi=True))
|
||||||
editor: Editor | None = None
|
editor: Editor | None = None
|
||||||
|
if_session: IFSession | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self) -> str:
|
def mode(self) -> str:
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,8 @@ async def shell(
|
||||||
# Show appropriate prompt based on mode
|
# Show appropriate prompt based on mode
|
||||||
if player.mode == "editor" and player.editor:
|
if player.mode == "editor" and player.editor:
|
||||||
_writer.write(f" {player.editor.cursor + 1}> ")
|
_writer.write(f" {player.editor.cursor + 1}> ")
|
||||||
|
elif player.mode == "if" and player.if_session:
|
||||||
|
_writer.write("> ")
|
||||||
else:
|
else:
|
||||||
_writer.write("mud> ")
|
_writer.write("mud> ")
|
||||||
await _writer.drain()
|
await _writer.drain()
|
||||||
|
|
@ -321,7 +323,7 @@ async def shell(
|
||||||
break
|
break
|
||||||
|
|
||||||
command = inp.strip()
|
command = inp.strip()
|
||||||
if not command and player.mode != "editor":
|
if not command and player.mode not in ("editor", "if"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Handle editor mode
|
# Handle editor mode
|
||||||
|
|
@ -332,6 +334,16 @@ async def shell(
|
||||||
if response.done:
|
if response.done:
|
||||||
player.editor = None
|
player.editor = None
|
||||||
player.mode_stack.pop()
|
player.mode_stack.pop()
|
||||||
|
# Handle IF mode
|
||||||
|
elif player.mode == "if" and player.if_session:
|
||||||
|
response = await player.if_session.handle_input(command)
|
||||||
|
if response.output:
|
||||||
|
await player.send(response.output)
|
||||||
|
if response.done:
|
||||||
|
await player.if_session.stop()
|
||||||
|
player.if_session = None
|
||||||
|
player.mode_stack.pop()
|
||||||
|
await player.send("you leave the terminal.\r\n")
|
||||||
else:
|
else:
|
||||||
# Dispatch normal command
|
# Dispatch normal command
|
||||||
await mudlib.commands.dispatch(player, command)
|
await mudlib.commands.dispatch(player, command)
|
||||||
|
|
|
||||||
121
tests/test_if_mode.py
Normal file
121
tests/test_if_mode.py
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
"""Tests for IF mode integration with player model and server shell."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mudlib.if_session import IFResponse, IFSession
|
||||||
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
|
||||||
|
@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 player(mock_reader, mock_writer):
|
||||||
|
return Player(name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer)
|
||||||
|
|
||||||
|
|
||||||
|
def test_player_has_if_session_attribute(player):
|
||||||
|
"""Player has if_session attribute that defaults to None."""
|
||||||
|
assert hasattr(player, "if_session")
|
||||||
|
assert player.if_session is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_player_if_session_can_be_set(player):
|
||||||
|
"""Player.if_session can be set to an IFSession instance."""
|
||||||
|
session = IFSession(player, "/path/to/story.z5", "story")
|
||||||
|
player.if_session = session
|
||||||
|
assert player.if_session == session
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_if_mode_routes_input_to_if_session(player):
|
||||||
|
"""When mode is 'if' and if_session is set, input routes to if_session.handle_input."""
|
||||||
|
# Create a mock IF session
|
||||||
|
mock_session = MagicMock()
|
||||||
|
mock_session.handle_input = AsyncMock(
|
||||||
|
return_value=IFResponse(output="You see a room.\r\n", done=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
player.if_session = mock_session
|
||||||
|
player.mode_stack.append("if")
|
||||||
|
|
||||||
|
# Simulate input routing (what shell loop should do)
|
||||||
|
if player.mode == "if" and player.if_session:
|
||||||
|
response = await player.if_session.handle_input("look")
|
||||||
|
assert response.output == "You see a room.\r\n"
|
||||||
|
assert response.done is False
|
||||||
|
|
||||||
|
mock_session.handle_input.assert_called_once_with("look")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_if_mode_done_clears_session_and_pops_mode(player):
|
||||||
|
"""When handle_input returns done=True, mode pops and if_session is cleared."""
|
||||||
|
# Create a mock IF session that returns done=True
|
||||||
|
mock_session = MagicMock()
|
||||||
|
mock_session.handle_input = AsyncMock(
|
||||||
|
return_value=IFResponse(output="Goodbye.\r\n", done=True)
|
||||||
|
)
|
||||||
|
mock_session.stop = AsyncMock()
|
||||||
|
|
||||||
|
player.if_session = mock_session
|
||||||
|
player.mode_stack.append("if")
|
||||||
|
|
||||||
|
# Simulate shell loop handling done response
|
||||||
|
if player.mode == "if" and player.if_session:
|
||||||
|
response = await player.if_session.handle_input("::quit")
|
||||||
|
if response.done:
|
||||||
|
await player.if_session.stop()
|
||||||
|
player.if_session = None
|
||||||
|
player.mode_stack.pop()
|
||||||
|
|
||||||
|
assert player.mode == "normal"
|
||||||
|
assert player.if_session is None
|
||||||
|
mock_session.stop.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mode_stack_push_and_pop_for_if(player):
|
||||||
|
"""Test mode stack mechanics for IF mode."""
|
||||||
|
assert player.mode_stack == ["normal"]
|
||||||
|
assert player.mode == "normal"
|
||||||
|
|
||||||
|
# Enter IF mode
|
||||||
|
player.mode_stack.append("if")
|
||||||
|
assert player.mode == "if"
|
||||||
|
assert player.mode_stack == ["normal", "if"]
|
||||||
|
|
||||||
|
# Exit IF mode
|
||||||
|
player.mode_stack.pop()
|
||||||
|
assert player.mode == "normal"
|
||||||
|
assert player.mode_stack == ["normal"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_empty_input_allowed_in_if_mode(player):
|
||||||
|
"""Test that empty input is allowed in IF mode (some IF games accept blank input)."""
|
||||||
|
# Create a mock IF session
|
||||||
|
mock_session = MagicMock()
|
||||||
|
mock_session.handle_input = AsyncMock(
|
||||||
|
return_value=IFResponse(output="Time passes.\r\n", done=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
player.if_session = mock_session
|
||||||
|
player.mode_stack.append("if")
|
||||||
|
|
||||||
|
# Empty input should still be passed to IF session
|
||||||
|
response = await player.if_session.handle_input("")
|
||||||
|
assert response.output == "Time passes.\r\n"
|
||||||
|
mock_session.handle_input.assert_called_once_with("")
|
||||||
Loading…
Reference in a new issue