Skip to main content

cardinal_kernel/engine/
init.rs

1use crate::{
2    state::gamestate::GameState,
3    util::rng::GameRng,
4    ids::{CardId, PlayerId},
5    rules::schema::Ruleset,
6};
7
8/// Initialize a game by:
9/// 1. Shuffling each player's deck
10/// 2. Drawing starting hands
11/// 3. Determining the first player
12/// 4. Setting up the initial turn state
13pub fn initialize_game(
14    mut state: GameState,
15    rules: &Ruleset,
16    seed: u64,
17) -> GameState {
18    let mut rng = GameRng::new(seed);
19    let num_players = state.players.len() as u32;
20
21    // 1. Shuffle each player's deck
22    for i in 0..num_players {
23        let player_id = PlayerId(i as u8);
24        shuffle_player_deck(&mut state, player_id, &mut rng, rules);
25    }
26
27    // 2. Determine first player based on rule
28    let first_player = determine_first_player(&rules.players.first_player_rule, num_players, &mut rng);
29    state.turn.active_player = first_player;
30    state.turn.priority_player = first_player;
31
32    // 3. Draw starting hands
33    let skip_first_draw = rules.turn.skip_first_turn_draw_for_first_player;
34    for i in 0..num_players {
35        let player_id = PlayerId(i as u8);
36        let should_skip = skip_first_draw && player_id == first_player;
37        
38        if !should_skip {
39            draw_cards(&mut state, player_id, rules.players.starting_hand_size as u32, rules);
40        }
41    }
42
43    state
44}
45
46/// Shuffle a player's deck in-place using the provided RNG
47fn shuffle_player_deck(
48    state: &mut GameState,
49    player: PlayerId,
50    rng: &mut GameRng,
51    _rules: &Ruleset,
52) {
53    // Find the deck zone for this player
54    let deck_zone_id_string = format!("deck@{}", player.0);
55    let deck_zone = state.zones.iter_mut()
56        .find(|z| z.id.0 == deck_zone_id_string);
57
58    if let Some(zone) = deck_zone {
59        // Fisher-Yates shuffle
60        for i in (1..zone.cards.len()).rev() {
61            let j: usize = rng.generate::<u32>() as usize % (i + 1);
62            zone.cards.swap(i, j);
63        }
64    }
65}
66
67/// Determine which player goes first based on the rule
68fn determine_first_player(
69    rule: &str,
70    num_players: u32,
71    rng: &mut GameRng,
72) -> PlayerId {
73    match rule {
74        "random" => {
75            let idx: u32 = rng.generate::<u32>() % num_players;
76            PlayerId(idx as u8)
77        }
78        "player_0" | "first" => PlayerId(0),
79        "player_1" | "second" => {
80            if num_players > 1 {
81                PlayerId(1)
82            } else {
83                PlayerId(0)
84            }
85        }
86        _ => {
87            // Default to random if unknown rule
88            let idx: u32 = rng.generate::<u32>() % num_players;
89            PlayerId(idx as u8)
90        }
91    }
92}
93
94/// Draw `count` cards from a player's deck to their hand
95fn draw_cards(
96    state: &mut GameState,
97    player: PlayerId,
98    count: u32,
99    rules: &Ruleset,
100) {
101    let deck_zone_id_string = format!("deck@{}", player.0);
102    let hand_zone_id_string = format!("hand@{}", player.0);
103
104    // Find deck and hand zones
105    let deck_cards: Vec<CardId> = state.zones.iter()
106        .find(|z| z.id.0 == deck_zone_id_string)
107        .map(|z| z.cards.clone())
108        .unwrap_or_default();
109
110    // Draw from the top of deck (first card in the Vec)
111    let cards_to_draw = deck_cards.iter()
112        .take(count as usize)
113        .cloned()
114        .collect::<Vec<_>>();
115
116    // Remove from deck
117    if let Some(deck_zone) = state.zones.iter_mut()
118        .find(|z| z.id.0 == deck_zone_id_string)
119    {
120        for _ in 0..cards_to_draw.len() {
121            if !deck_zone.cards.is_empty() {
122                deck_zone.cards.remove(0);
123            }
124        }
125    }
126
127    // Add to hand (respecting max hand size)
128    if let Some(hand_zone) = state.zones.iter_mut()
129        .find(|z| z.id.0 == hand_zone_id_string)
130    {
131        for card in cards_to_draw {
132            if hand_zone.cards.len() < rules.players.max_hand_size {
133                hand_zone.cards.push(card);
134            }
135        }
136    }
137}