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:
parent
e31af53577
commit
085a19a564
2 changed files with 157 additions and 0 deletions
58
src/mudlib/commands/score.py
Normal file
58
src/mudlib/commands/score.py
Normal 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",
|
||||||
|
)
|
||||||
|
)
|
||||||
99
tests/test_score_command.py
Normal file
99
tests/test_score_command.py
Normal 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
|
||||||
Loading…
Reference in a new issue