OpenKKnD/game/sim/__main__.py

82 lines
2.8 KiB
Python

"""CLI: python3 -m game.sim — startable Pitchwell v0 skirmish stub.
Demo path (GAMEPLAY.md Match loop Open→Seed→Break-lite):
open both factions → Staff seeds Font/Cot/Yard → train Acolytes → attack Host HQ.
"""
from __future__ import annotations
import argparse
import json
import sys
from .catalog import FACTION_HOST, FACTION_STAFF, HZ
from .match import MatchPhase, open_match, seed_v0_path, train_t1_melee
def run_demo(ticks: int | None, hz: int, until_end: bool) -> int:
m = open_match(hz=hz)
print("OPEN", json.dumps(m.snapshot(), sort_keys=True))
notes = seed_v0_path(m, FACTION_STAFF)
print("SEED", " ".join(notes))
m.tick_hz(int(50 * hz))
print("AFTER_SEED", json.dumps(m.snapshot(), sort_keys=True))
one = train_t1_melee(m, FACTION_STAFF, n=1)
print("TRAIN_ONE", " ".join(one))
m.tick_hz(int(10 * hz))
m.grant_pitch(FACTION_STAFF, 500.0)
train = train_t1_melee(m, FACTION_STAFF, n=4)
print("TRAIN_ARMY", " ".join(train))
m.tick_hz(int(40 * hz))
ordered = m.order_attack_hq(FACTION_STAFF)
print(f"ATTACK_HQ ordered={ordered}")
if until_end:
# Default --ticks must not cap --until-end (was HZ*5 → false early PASS).
safety = ticks if ticks is not None else hz * 600
for _ in range(safety):
if m.phase == MatchPhase.ENDED:
break
m.tick_hz(1)
else:
m.tick_hz(max(1, ticks if ticks is not None else hz * 5))
snap = m.snapshot()
print("END", json.dumps(snap, sort_keys=True))
if m.phase == MatchPhase.ENDED and m.winner in (FACTION_STAFF, FACTION_HOST):
print(f"PASS: {m.winner} wins via HQ kill")
return 0
if m.phase == MatchPhase.ENDED and m.winner is None:
print("PASS: draw (both HQs down)")
return 0
staff = snap["sides"][FACTION_STAFF]
if "pitch_font" in staff["buildings"] and "coven_yard" in staff["buildings"]:
if any(u == "acolyte" for u in staff["units"]):
print("PASS: startable foundation (open+seed+T1) without forced end")
return 0
print("FAIL: foundation incomplete", file=sys.stderr)
return 1
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Pitchwell headless match (game.sim)")
p.add_argument("--hz", type=int, default=HZ)
p.add_argument(
"--ticks",
type=int,
default=None,
help="extra ticks after attack (default HZ*5); with --until-end overrides safety cap",
)
p.add_argument(
"--until-end",
action="store_true",
help="tick until HQ victory or safety cap (default HZ*600)",
)
args = p.parse_args(argv)
return run_demo(args.ticks, args.hz, args.until_end)
if __name__ == "__main__":
raise SystemExit(main())