digu/
game.rs

1use super::digu::{eval_hand, Score};
2use super::stack::{Stack, DECK};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6
7#[derive(Serialize, Deserialize, JsonSchema)]
8pub enum Action {
9    InitiateDraw,
10    FinalizeDraw(Option<usize>),
11    Swap(usize),
12    Forfeit,
13}
14
15#[derive(Clone, Serialize, Deserialize, JsonSchema)]
16pub struct Outcome {
17    pub winner: u8,
18    pub scores: Vec<Score>,
19}
20
21#[derive(Serialize, Deserialize, JsonSchema)]
22pub struct PublicState {
23    pub completed: bool,
24    pub steps: u32,
25    pub draw_in_progress: bool,
26    pub active_player: u8,
27    pub n_players: u8,
28    pub forfeitures: HashSet<u8>,
29    pub pile: Vec<u8>,
30    pub outcome: Option<Outcome>,
31}
32
33#[derive(Clone, Serialize, Deserialize, JsonSchema)]
34pub struct PrivateState {
35    pub hand: [u8; 10],
36    pub deck_top: Option<u8>,
37}
38
39#[derive(Serialize, Deserialize, JsonSchema)]
40pub struct Game {
41    completed: bool,
42    steps: u32,
43    draw_in_progress: bool,
44    active_player: u8,
45    n_players: u8,
46    forfeitures: HashSet<u8>,
47    deck: Stack,
48    pile: Stack,
49    hands: HashMap<u8, [u8; 10]>,
50    outcome: Option<Outcome>,
51}
52
53impl Game {
54    pub fn new(n_players: u8) -> Result<(Self, PublicState, Vec<PrivateState>), String> {
55        if n_players == 1 || n_players > 4 {
56            return Err(String::from("Invalid number of players"));
57        }
58
59        let mut deck = Stack::new(DECK.to_vec());
60        deck.shuffle();
61
62        let mut hands: HashMap<u8, [u8; 10]> = HashMap::new();
63        for i in 0..n_players {
64            let mut hand: [u8; 10] = [0; 10];
65            for i in 0..10 {
66                hand[i] = deck.deal().unwrap();
67            }
68            hands.insert(i, hand);
69        }
70
71        let forfeitures: HashSet<u8> = HashSet::new();
72        let pile = Stack::new(vec![]);
73
74        let gme = Self {
75            completed: false,
76            steps: 0,
77            draw_in_progress: false,
78            active_player: 0,
79            n_players,
80            forfeitures: forfeitures.clone(),
81            deck,
82            pile: pile.clone(),
83            hands,
84            outcome: None,
85        };
86
87        let public_state = PublicState {
88            completed: gme.completed,
89            steps: 0,
90            draw_in_progress: gme.draw_in_progress,
91            active_player: gme.active_player,
92            n_players: gme.n_players,
93            forfeitures: forfeitures,
94            pile: pile.list(),
95            outcome: None,
96        };
97
98        let mut private_states: Vec<PrivateState> = vec![];
99        for i in 0..n_players {
100            private_states.push(PrivateState {
101                hand: *gme.hands.get(&i).unwrap(),
102                deck_top: None,
103            });
104        }
105
106        Ok((gme, public_state, private_states))
107    }
108
109    pub fn step(&mut self, action: Action) -> Result<(PublicState, Vec<PrivateState>), String> {
110        if self.completed {
111            return Err(String::from("Game is already over"));
112        }
113
114        // the only allowed actions while draw is in progress are FinalizeDraw and Forfeit
115        if self.draw_in_progress {
116            if !(matches!(action, Action::FinalizeDraw(_)) || matches!(action, Action::Forfeit)) {
117                return Err(String::from(
118                    "Invalid action. Expected FinalizeDraw or Forfeit",
119                ));
120            }
121        }
122
123        let active_player = self.active_player;
124        let hand = self.hands.get_mut(&active_player).unwrap();
125        match action {
126            Action::InitiateDraw => {
127                self.draw_in_progress = true;
128            }
129            Action::FinalizeDraw(possible_discarded_index) => {
130                if let Some(discarded_index) = possible_discarded_index {
131                    match discarded_index {
132                        0..=9 => {
133                            self.pile.stack(hand[discarded_index]);
134                            hand[discarded_index] = self.deck.deal().unwrap();
135                        }
136                        _ => {
137                            return Err(String::from(
138                                "Invalid index, please provide a value between 0 and 9",
139                            ));
140                        }
141                    }
142                } else {
143                    self.pile.stack(self.deck.deal().unwrap());
144                }
145
146                self.draw_in_progress = false;
147            }
148            Action::Swap(discarded_index) => {
149                if self.pile.is_empty() {
150                    return Err(String::from("Pile is empty, Please choose another action"));
151                }
152
153                match discarded_index {
154                    0..=9 => {
155                        let discarded_card = hand[discarded_index];
156                        hand[discarded_index] = self.pile.deal().unwrap();
157                        self.pile.stack(discarded_card);
158                    }
159                    _ => {
160                        return Err(String::from(
161                            "Invalid index, please provide a value between 0 and 9",
162                        ))
163                    }
164                }
165            }
166            Action::Forfeit => {
167                for (_, &c) in hand.iter().enumerate() {
168                    self.deck.stack(c);
169                }
170                self.deck.shuffle();
171                self.forfeitures.insert(active_player);
172                self.draw_in_progress = false;
173            }
174        }
175
176        self.steps += 1;
177
178        if !self.draw_in_progress {
179            loop {
180                self.active_player = (self.active_player + 1) % self.n_players;
181                if !self.forfeitures.contains(&self.active_player) {
182                    break;
183                }
184                if self.active_player == active_player {
185                    break;
186                }
187            }
188        }
189
190        // if the deck has run out, transfer pile to deck and shuffle
191        if self.deck.is_empty() {
192            self.deck = Stack::new(self.pile.dump());
193            self.deck.shuffle();
194        }
195
196        // check win condition
197        let score = eval_hand(hand);
198        if score.winner || self.forfeitures.len() == usize::from(self.n_players - 1) {
199            let mut outcome = Outcome {
200                winner: active_player,
201                scores: vec![],
202            };
203
204            for p in 0..self.n_players {
205                if p == active_player {
206                    outcome.scores.push(score.clone());
207                    continue;
208                }
209
210                let hand = self.hands.get(&p).unwrap();
211                let score = eval_hand(hand);
212                outcome.scores.push(score);
213            }
214
215            self.outcome = Some(outcome);
216            self.completed = true;
217        }
218
219        let public_state = PublicState {
220            completed: self.completed,
221            steps: self.steps,
222            draw_in_progress: self.draw_in_progress,
223            active_player: self.active_player,
224            n_players: self.n_players,
225            forfeitures: self.forfeitures.clone(),
226            pile: self.pile.list(),
227            outcome: self.outcome.clone(),
228        };
229
230        let mut private_states: Vec<PrivateState> = vec![];
231        for i in 0..self.n_players {
232            private_states.push(PrivateState {
233                hand: *self.hands.get(&i).unwrap(),
234                deck_top: match self.draw_in_progress && i == active_player {
235                    true => Some(self.deck.top().unwrap()),
236                    false => None,
237                },
238            });
239        }
240
241        Ok((public_state, private_states))
242    }
243}