File size: 2,454 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
"""Run state: the journey between combats.

Phase 0 keeps this minimal — a linear node sequence and the player's
persistent resources. The branching map graph, challenges, and items
arrive in later phases.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum

from .cards import Card


class NodeKind(Enum):
    BATTLE = "battle"
    SHELL = "shell"          # sandbox exploration between fights
    ALTAR = "altar"          # the Warden's command-for-power deal
    CARD_CHOICE = "card_choice"
    FORK = "fork"            # the path splits; payload is a fork id


@dataclass(frozen=True)
class Node:
    kind: NodeKind
    payload: str = ""  # encounter id for battles


@dataclass
class RunState:
    deck: list[Card]
    nodes: list[Node]
    position: int = 0
    cycles: int = 0
    ttys: int = 2  # losses remaining before the run ends
    over: bool = False
    victorious: bool = False

    @property
    def current(self) -> Node | None:
        return self.nodes[self.position] if self.position < len(self.nodes) else None

    def advance(self) -> None:
        self.position += 1
        if self.current is None:
            self.over = True
            self.victorious = True

    def lose_tty(self) -> None:
        self.ttys -= 1
        if self.ttys <= 0:
            self.over = True
            self.victorious = False

    def resolve_fork(self, encounter_id: str) -> None:
        """The player chose a door: the FORK node becomes that battle."""
        node = self.current
        if node is None or node.kind is not NodeKind.FORK:
            raise ValueError("not standing at a fork")
        self.nodes[self.position] = Node(NodeKind.BATTLE, encounter_id)


def new_run(
    starter_deck: list[Card],
    encounter_ids: list[str],
    fork_ids: frozenset[str] = frozenset(),
) -> RunState:
    """battle → shell → altar → draft → battle → … → final battle.

    Ids in fork_ids become FORK nodes: the battle is chosen at the door.
    """
    nodes: list[Node] = []
    for i, enc in enumerate(encounter_ids):
        kind = NodeKind.FORK if enc in fork_ids else NodeKind.BATTLE
        nodes.append(Node(kind, enc))
        if i < len(encounter_ids) - 1:
            nodes.append(Node(NodeKind.SHELL))
            nodes.append(Node(NodeKind.ALTAR))
            nodes.append(Node(NodeKind.CARD_CHOICE))
    return RunState(deck=list(starter_deck), nodes=nodes)