sim: Pitchwell match foundation (open/seed/T1/HQ) + smoke
This commit is contained in:
parent
7358d1ce1a
commit
bd5ade8ec4
7 changed files with 708 additions and 41 deletions
82
game/sim/__main__.py
Normal file
82
game/sim/__main__.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
"""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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue