File size: 3,651 Bytes
9fca766
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""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))