mirror of
https://github.com/rr-/pq-cli.git
synced 2026-11-12 22:40:36 -07:00
ty reported 20 diagnostics, and 0.0.89 adds a few more; this clears them all without suppressions. - The quest monster is held in a local before being stored on the quest book, so its name no longer reads from an Optional. - PgUp/PgDn narrow the focused window with isinstance against a runtime-checkable PageScrollable protocol instead of hasattr. - The ^t cheat used to monkeypatch SignalMixin.emit. SignalMixin now has a muted() context manager that emit() checks, and it restores emission even if a tick raises. - GameView keeps its children as GameWindow, a protocol declaring stop() and sync(), so the cheat's resync type-checks. - curses.ascii is imported explicitly where it is used. - ProgressBarWindow is now Focusable itself, instead of calling Focusable.focus_standout() on a class that is not Focusable. Its four subclasses drop the duplicate Focusable base; the MRO and initialisation order are unchanged. - MainMenu narrows the Optional pad before colouring the logo. - The tests that record tasks assert player.task is set. - Context managers are annotated as Generator rather than Iterator, which ty flags as deprecated for @contextmanager. - The redundant cast() around getch() is gone. - The frozen GameData test assigns through setattr(), since the direct assignment is an error by design. Bug fixes: - ListBox.select(None) crashed on `None < 0`; it now clears the selection, as DataTable.select(None) already does. - ListBox.get() caught ValueError, which indexing never raises; it now catches IndexError, so an out-of-range index returns None as intended.
87 lines
2.7 KiB
Python
87 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] = []
|
|
|
|
def record_task() -> None:
|
|
assert player.task is not None
|
|
lines.append(f"task: {player.task.description}")
|
|
|
|
player.connect("new_task", record_task)
|
|
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")
|