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