Scrypt / scrypt /data /__init__.py
IMJONEZZ's picture
SCRYPT: initial commit — game, sandbox, Warden, Space web layer
9fca766
Raw
History Blame Contribute Delete
3.65 kB
"""Load authored game content (cards, decks, encounters) from YAML.
All IO lives here; the engine stays pure. Content errors (unknown sigils,
bad costs, decks referencing missing cards) raise at load time.
"""
from __future__ import annotations
from importlib import resources
import yaml
from scrypt.engine.cards import Card, Cost, CostType
from scrypt.engine.combat import EncounterScript, ScriptedPlay
def _parse_cost(raw: dict) -> Cost:
type_ = CostType(raw["type"])
amount = raw.get("amount", 0)
if type_ is not CostType.FREE and amount < 1:
raise ValueError(f"{type_.value} cost needs an amount >= 1")
return Cost(type_, amount)
def _parse_card(raw: dict) -> tuple[Card, bool]:
card = Card(
id=raw["id"],
name=raw["name"],
power=raw["power"],
health=raw["health"],
cost=_parse_cost(raw["cost"]),
sigils=tuple(raw.get("sigils", ())),
flavor=raw.get("flavor", ""),
art=raw.get("art", "").rstrip("\n"),
)
return card, raw.get("draftable", True)
class Content:
"""Parsed cards.yaml: card pool, starter decks, encounter scripts."""
def __init__(self, raw: dict):
self.pool: dict[str, Card] = {}
self.draftable: list[Card] = []
for raw_card in raw["cards"]:
card, draftable = _parse_card(raw_card)
if card.id in self.pool:
raise ValueError(f"duplicate card id {card.id!r}")
self.pool[card.id] = card
# Bits are fodder, not a draft prize.
if draftable and card.id != "bit":
self.draftable.append(card)
self.starter_decks: dict[str, dict] = {}
for deck_id, deck in raw.get("starter_decks", {}).items():
self.starter_decks[deck_id] = {
"name": deck["name"],
"description": deck.get("description", ""),
"cards": [self.card(cid) for cid in deck["cards"]],
}
self.encounters: dict[str, dict] = {}
for enc_id, enc in raw.get("encounters", {}).items():
script: EncounterScript = [
[ScriptedPlay(lane=p["lane"], card=self.card(p["card"])) for p in turn]
for turn in enc["script"]
]
self.encounters[enc_id] = {"name": enc["name"], "script": script}
self.forks: dict[str, dict] = {}
for fork_id, fork in raw.get("forks", {}).items():
options = []
for opt in fork["options"]:
if opt["encounter"] not in self.encounters:
raise ValueError(
f"fork {fork_id!r} references unknown encounter {opt['encounter']!r}"
)
bounty = opt.get("bounty", {})
if bounty and bounty.get("kind") not in ("cycles", "draft"):
raise ValueError(f"fork {fork_id!r} has unknown bounty kind")
options.append(
{
"encounter": opt["encounter"],
"label": opt["label"],
"blurb": opt.get("blurb", ""),
"bounty": bounty,
}
)
self.forks[fork_id] = {"prompt": fork.get("prompt", ""), "options": options}
def card(self, card_id: str) -> Card:
if card_id not in self.pool:
raise KeyError(f"unknown card id {card_id!r}")
return self.pool[card_id]
def load_content() -> Content:
text = (resources.files("scrypt.data") / "cards.yaml").read_text(encoding="utf-8")
return Content(yaml.safe_load(text))