Skip to main content

cardinal_kernel/state/
gamestate.rs

1use crate::ids::{PlayerId, ZoneId, PhaseId, StepId, CardId};
2use crate::model::command::{PendingChoice, StackItem};
3use crate::rules::schema::Ruleset;
4
5#[derive(Debug, Clone)]
6pub struct GameState {
7    pub turn: TurnState,
8    pub players: Vec<PlayerState>,
9    pub zones: Vec<ZoneState>,
10    pub stack: Vec<StackItem>,
11    pub pending_choice: Option<PendingChoice>,
12    pub ended: Option<GameEnd>,
13}
14
15#[derive(Debug, Clone)]
16pub struct TurnState {
17    pub number: u32,
18    pub active_player: PlayerId,
19    pub priority_player: PlayerId,
20    pub phase: PhaseId,
21    pub step: StepId,
22    pub priority_passes: u32,  // Number of consecutive players who have passed priority
23}
24
25#[derive(Debug, Clone)]
26pub struct PlayerState {
27    pub id: PlayerId,
28    pub life: i32,
29    // resources, flags, etc
30}
31
32#[derive(Debug, Clone)]
33pub struct ZoneState {
34    pub id: ZoneId,
35    pub owner: Option<PlayerId>, // None for shared zones like stack
36    pub cards: Vec<CardId>,
37}
38
39#[derive(Debug, Clone)]
40pub struct GameEnd {
41    pub winner: Option<PlayerId>,
42    pub reason: String,
43}
44
45impl GameState {
46    /// Build an initial `GameState` from a `Ruleset`. This is intentionally conservative
47    /// and does not shuffle or populate decks; it just creates players, zones, and a starting turn.
48    pub fn from_ruleset(rules: &Ruleset) -> Self {
49        let min_players = rules.players.min_players as usize;
50        let mut players = Vec::new();
51        for i in 0..min_players {
52            players.push(PlayerState { id: PlayerId(i as u8), life: rules.players.starting_life });
53        }
54
55        // Build zones: player-owned zones get one ZoneState per player; shared zones get a single ZoneState
56        let mut zones = Vec::new();
57        for z in &rules.zones {
58            match z.owner_scope {
59                crate::rules::schema::ZoneOwnerScope::Player => {
60                    for i in 0..min_players {
61                        let zid_string = format!("{}@{}", z.id, i);
62                        let boxed = zid_string.into_boxed_str();
63                        let static_str: &'static str = Box::leak(boxed);
64                        zones.push(ZoneState { id: ZoneId(static_str), owner: Some(PlayerId(i as u8)), cards: Vec::new() });
65                    }
66                }
67                crate::rules::schema::ZoneOwnerScope::Shared => {
68                    let boxed = z.id.clone().into_boxed_str();
69                    let static_str: &'static str = Box::leak(boxed);
70                    zones.push(ZoneState { id: ZoneId(static_str), owner: None, cards: Vec::new() });
71                }
72            }
73        }
74
75        // Starting phase/step: use first defined phase/step if present, otherwise fallbacks
76        let (phase_id, step_id) = if let Some(first_phase) = rules.turn.phases.first() {
77            let ph_box: Box<str> = first_phase.id.clone().into_boxed_str();
78            let ph_static: &'static str = Box::leak(ph_box);
79            if let Some(first_step) = first_phase.steps.first() {
80                let st_box: Box<str> = first_step.id.clone().into_boxed_str();
81                let st_static: &'static str = Box::leak(st_box);
82                (PhaseId(ph_static), StepId(st_static))
83            } else {
84                (PhaseId(ph_static), StepId("start"))
85            }
86        } else {
87            (PhaseId("start"), StepId("untap"))
88        };
89
90        GameState {
91            turn: TurnState { number: 1, active_player: PlayerId(0), priority_player: PlayerId(0), phase: phase_id, step: step_id, priority_passes: 0 },
92            players,
93            zones,
94            stack: Vec::new(),
95            pending_choice: None,
96            ended: None,
97        }
98    }
99}