Files
ProgressQuest-cli/tests/game_trace.py
T
Dash 0a4358e2b8 misc: drop typing as T, use builtin generics
Python 3.10 is the minimum, so annotations use `X | None`, `list[X]`
and friends, enforced by ruff's pyupgrade rules. The remaining typing
names are imported directly, with abstract types coming from
collections.abc. first() is now generic over the item and default.
2026-10-17 15:55:55 +02:00

85 lines
2.7 KiB
Python

import datetime
from pathlib import Path
from pqcli import random
from pqcli.config import StatType
from pqcli.game_data import load_game_data
from pqcli.mechanic import Player, Simulation, Stats, monster_task
from pqcli.random import Alea
STATS = {
StatType.strength: 12,
StatType.condition: 11,
StatType.dexterity: 10,
StatType.intelligence: 13,
StatType.wisdom: 14,
StatType.charisma: 9,
StatType.hp_max: 6,
StatType.mp_max: 5,
}
def run_trace(seed: str, ticks: int = 3000) -> list[str]:
"""Play a deterministic game and return everything that happened."""
data = load_game_data()
player = Player(
name="Tester",
birthday=datetime.datetime(2000, 1, 1),
race=data.races[0],
class_=data.classes[0],
stats=Stats(dict(STATS)),
rng=Alea(seed),
)
random.seed(seed)
simulation = Simulation(player, data)
lines: list[str] = []
player.connect(
"new_task",
lambda: lines.append(f"task: {player.task.description}"),
)
for _ in range(ticks):
simulation.tick(100000.0)
with random.using(player.rng):
# plot cinematics and quests are rare in a short run, so force them
for _ in range(30):
simulation.interplot_cinematic()
simulation.complete_quest()
lines.extend(f"queued: {task.description}" for task in player.queue)
player.queue.clear()
# high level monsters are unreachable in a short run
for level in range(1, 100):
task = monster_task(data, level, player.quest_book.monster)
lines.append(f"monster: {task.description} {task.duration}")
lines.append(f"level: {player.level}")
lines.append(f"act: {player.quest_book.act}")
lines.append(f"gold: {player.inventory.gold}")
for stat, value in player.stats:
lines.append(f"stat: {stat.value} {value}")
for equipment_type, item in player.equipment:
lines.append(f"equipment: {equipment_type.value} {item}")
for inventory_item in player.inventory:
lines.append(
f"inventory: {inventory_item.name} {inventory_item.quantity}"
)
for spell in player.spell_book:
lines.append(f"spell: {spell.name} {spell.level}")
for quest in player.quest_book.quests:
lines.append(f"quest: {quest}")
return lines
SEEDS = ["alpha", "beta", "gamma"]
FIXTURES = Path(__file__).parent / "fixtures"
if __name__ == "__main__":
# Re-record the parity fixtures. Only do this on purpose, when a change
# is meant to alter gameplay.
for seed in SEEDS:
lines = run_trace(seed)
(FIXTURES / f"parity_{seed}.txt").write_text("\n".join(lines) + "\n")