Add score command with stats display

Shows PL, stamina, K/D ratio, time played, and unlocked moves.
Registered as score/stats/profile, available in all modes.
This commit is contained in:
Jared Miller 2026-02-14 11:22:08 -05:00
parent e31af53577
commit 085a19a564
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 157 additions and 0 deletions

View file

@ -0,0 +1,58 @@
"""Score/stats/profile command."""
import time
from mudlib.commands import CommandDefinition, register
from mudlib.player import Player
def format_play_time(seconds: float) -> str:
"""Format play time as human-readable string."""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
parts = []
if hours:
parts.append(f"{hours}h")
if minutes:
parts.append(f"{minutes}m")
parts.append(f"{secs}s")
return " ".join(parts)
async def cmd_score(player: Player, _args: str) -> None:
"""Display character sheet with stats."""
# Calculate K/D ratio
kd_ratio = f"{player.kills / player.deaths:.1f}" if player.deaths > 0 else "N/A"
# Format play time (accumulate current session first)
total_seconds = player.play_time_seconds
if player.session_start > 0:
total_seconds += time.monotonic() - player.session_start
play_time = format_play_time(total_seconds)
lines = [
f"--- {player.name} ---",
f"PL: {player.pl:.0f}/{player.max_pl:.0f}",
f"Stamina: {player.stamina:.0f}/{player.max_stamina:.0f}",
f"Kills: {player.kills} Deaths: {player.deaths} K/D: {kd_ratio}",
f"Time played: {play_time}",
]
if player.unlocked_moves:
moves = ", ".join(sorted(player.unlocked_moves))
lines.append(f"Unlocked moves: {moves}")
output = "\r\n".join(lines) + "\r\n"
await player.send(output)
register(
CommandDefinition(
name="score",
handler=cmd_score,
aliases=["stats", "profile"],
mode="*",
help="Display your character stats and progression",
)
)

View file

@ -0,0 +1,99 @@
"""Tests for the score/stats/profile command."""
import pytest
from mudlib.commands.score import cmd_score
@pytest.mark.asyncio
async def test_score_shows_player_name(player, mock_writer):
"""Score command displays the player's name."""
player.name = "Goku"
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "Goku" in output
@pytest.mark.asyncio
async def test_score_shows_pl_and_stamina(player, mock_writer):
"""Score command displays PL and stamina gauges."""
player.pl = 75
player.max_pl = 100
player.stamina = 80
player.max_stamina = 100
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "75" in output
assert "100" in output
assert "80" in output
@pytest.mark.asyncio
async def test_score_shows_kill_death_stats(player, mock_writer):
"""Score command displays kills, deaths, and K/D ratio."""
player.kills = 10
player.deaths = 2
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "10" in output # kills
assert "2" in output # deaths
assert "5.0" in output # K/D ratio
@pytest.mark.asyncio
async def test_score_shows_kd_ratio_na_with_zero_deaths(player, mock_writer):
"""Score command shows N/A for K/D ratio when deaths is zero."""
player.kills = 5
player.deaths = 0
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "N/A" in output
@pytest.mark.asyncio
async def test_score_shows_time_played(player, mock_writer):
"""Score command formats and displays play time."""
player.play_time_seconds = 3661 # 1h 1m 1s
player.session_start = 0 # No active session
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "1h" in output
assert "1m" in output
assert "1s" in output
@pytest.mark.asyncio
async def test_score_shows_unlocked_moves(player, mock_writer):
"""Score command displays unlocked moves."""
player.unlocked_moves = {"roundhouse", "sweep"}
await cmd_score(player, "")
messages = [call[0][0] for call in player.writer.write.call_args_list]
output = "".join(messages)
assert "roundhouse" in output
assert "sweep" in output
@pytest.mark.asyncio
async def test_command_registered_with_aliases(player, mock_writer):
"""Score command is accessible via score, stats, and profile."""
from mudlib.commands import CommandDefinition, resolve_prefix
score_cmd = resolve_prefix("score")
stats_cmd = resolve_prefix("stats")
profile_cmd = resolve_prefix("profile")
assert isinstance(score_cmd, CommandDefinition)
assert isinstance(stats_cmd, CommandDefinition)
assert isinstance(profile_cmd, CommandDefinition)
assert score_cmd.handler == stats_cmd.handler == profile_cmd.handler