Skip to main content

games/blackjack/
game.rs

1use crate::blackjack::errors::BlackJackError;
2use crate::blackjack::hand::Hand;
3use crate::cards::StandardCard as Card;
4use crate::deck::{Deck, DefaultCollection};
5
6//use failure::Error;
7
8/// A struct to represent the games state
9#[repr(C)]
10#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
11pub enum GameState {
12    /// Game is currently in progress
13    InProgress,
14    /// Player has won the game
15    PlayerWon,
16    /// Player has lost the game
17    PlayerLost,
18}
19
20#[repr(C)]
21#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
22pub struct FrozenGame {
23    /// Player's Hand
24    pub player: Vec<Card>,
25    /// Dealers Hand
26    pub dealer: Vec<Card>,
27    /// Deck of cards at play
28    pub deck: Vec<Card>,
29    /// Whether or not the player
30    /// has decided to stay
31    pub player_stay: bool,
32    /// Whether or not the dealer
33    /// has decided to stay
34    pub dealer_stay: bool,
35    /// Whether or not this is the first turn
36    pub first_turn: bool,
37}
38
39/// BlackJack Game
40#[repr(C)]
41#[derive(
42    Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash,
43)]
44pub struct BlackJack {
45    /// Player's Hand
46    player: Hand,
47    /// Dealer's Hand
48    dealer: Hand,
49    /// First Turn, used for display purposes
50    first_turn: bool,
51    /// Deck of cards in the game
52    deck: Deck<crate::cards::StandardCard>,
53    /// Has the player chosen to stay
54    player_stay_status: bool,
55    /// Has the dealer chosen to stay
56    dealer_stay_status: bool,
57}
58
59impl BlackJack {
60    /// Create a new `Blackjack` Game
61    pub fn new_game(decks: usize) -> Self {
62        // Create a new deck
63        let mut deck = Deck::from(crate::cards::StandardCard::multiple_collections(decks));
64
65        deck.shuffle();
66        // Create The players hand
67        let player = deck.draw_many(2).into();
68
69        // Create the dealers hand
70        let dealer = deck.draw_many(2).into();
71
72        Self {
73            player,
74            dealer,
75            deck,
76            dealer_stay_status: false,
77            player_stay_status: false,
78            first_turn: true,
79        }
80    }
81    /// Freeze the `BlackJack` Game
82    /// Err(GameState::PlayerWon) - Player has one, no other data needed
83    /// Err(GameState::PlayerLost) - Player has lost, no other data needed
84    /// Ok(FrozenGame) - Game is in progress, can be restored later
85    ///
86    /// # Errors
87    ///
88    /// If the game state is not in progress, the function will error
89    /// And will return the result of the game
90    pub fn freeze(self) -> Result<FrozenGame, GameState> {
91        match self.status() {
92            GameState::InProgress => Ok(FrozenGame {
93                player: self.player.into_vec(),
94                dealer: self.dealer.into_vec(),
95                deck: self.deck.into_vec(),
96                first_turn: self.first_turn,
97                player_stay: self.player_stay_status,
98                dealer_stay: self.dealer_stay_status,
99            }),
100            gs => Err(gs),
101        }
102    }
103    /// Restore a `BlackKJack` game from `GameState::Frozen`
104    pub fn defrost(g: FrozenGame) -> Self {
105        Self {
106            player: g.player.into(),
107            dealer: g.dealer.into(),
108            deck: Deck::from(g.deck),
109            first_turn: g.first_turn,
110            player_stay_status: g.player_stay,
111            dealer_stay_status: g.dealer_stay,
112        }
113    }
114    /// Return the `GameState`
115    /// Possible values:
116    /// `GameState::InProgress` - Game is in progress
117    /// `GameState::PlayerWon` - Player has won
118    /// `GameState::PlayerLost` - Player has lost
119    pub fn status(&self) -> GameState {
120        if self.first_turn {
121            return GameState::InProgress;
122        }
123        // Calculate the scores
124        let player_score = self.player.score();
125        let dealer_score = self.dealer.score();
126
127        // If the player has 5 cards and less than 21 that's a win
128        if self.player.cards_len() == 5 && player_score <= 21 {
129            return GameState::PlayerWon;
130        }
131
132        // Same applies for the dealer
133        if self.dealer.cards_len() == 5 && dealer_score <= 21 {
134            return GameState::PlayerWon;
135        }
136
137        // 21 Is a win
138        if player_score == 21 {
139            return GameState::PlayerWon;
140        }
141
142        // Or a loss if the dealer has it
143        if dealer_score == 21 {
144            return GameState::PlayerLost;
145        }
146
147        // Over 21 is a loss
148        if player_score > 21 {
149            return GameState::PlayerLost;
150        }
151
152        if !(self.player_stay_status || self.dealer_stay_status) {
153            return GameState::InProgress;
154        }
155
156        // A draw is a loss
157        if player_score == dealer_score {
158            return GameState::PlayerLost;
159        }
160
161        // Over 21 is a loss
162        if dealer_score > 21 {
163            return GameState::PlayerWon;
164        }
165
166        // Highest score decides win
167        if player_score > dealer_score {
168            return GameState::PlayerWon;
169        }
170        if player_score < dealer_score {
171            return GameState::PlayerLost;
172        }
173
174        // Should be unreachable, but just in case
175        GameState::InProgress
176    }
177    /// Draws a card if game is InProgress, will error otherwise
178    ///
179    /// # Errors
180    ///
181    /// If the game status is not InProgress this function will error
182    /// returning the current game state within the error
183    pub fn player_hit(&mut self) -> Result<(), BlackJackError> {
184        // Check the game status
185        match self.status() {
186            GameState::InProgress => {
187                self.first_turn = false;
188                self.player.add_card(
189                    self.deck
190                        .draw()
191                        .map_err(|_| BlackJackError::OutOfCardsError)?,
192                );
193                Ok(())
194            }
195            GameState::PlayerLost => Err(BlackJackError::PlayerLostError),
196            GameState::PlayerWon => Err(BlackJackError::PlayerWonError),
197        }
198    }
199    /// Finish the blackjack game (player chooses to stay)
200    /// The dealer will draw cards as necessary
201    /// game result will be returned
202    pub fn finish(mut self) -> GameState {
203        self.player_stay_status = true;
204        self.dealer_logic();
205        match self.status() {
206            // Game state should only be Win/Lose
207            GameState::InProgress => unreachable!(),
208            gs => gs,
209        }
210    }
211    /// Handles the dealers logic for `BlackJack::finish`
212    fn dealer_logic(&mut self) {
213        self.first_turn = false;
214        // Dealer stops attempting at 17 / Higher
215        // And action is only taken if the player didn't already win/lose
216        while self.status() == GameState::InProgress && self.dealer.score() <= 17 {
217            match self.deck.draw() {
218                Ok(card) => self.dealer.add_card(card),
219                Err(_) => break,
220            }
221        }
222        self.dealer_stay_status = true;
223    }
224
225    /// Return a struct meant for displaying the Game
226    pub fn display_game(&self) -> GameDisplay {
227        if self.first_turn {
228            let (score, cards) = match self.dealer.cards().first() {
229                Some(card) => (card.face().value(), vec![*card]),
230                None => (0, Vec::new()),
231            };
232            GameDisplay {
233                i_dealer_cards: cards,
234                i_dealer_score: score,
235                i_player_cards: self.player.cards().to_vec(),
236                i_player_score: self.player.score(),
237                i_game_state: self.status().into(),
238            }
239        } else {
240            GameDisplay {
241                i_dealer_cards: self.dealer.cards().to_vec(),
242                i_dealer_score: self.dealer.score(),
243                i_player_cards: self.player.cards().to_vec(),
244                i_player_score: self.player.score(),
245                i_game_state: self.status().into(),
246            }
247        }
248    }
249}
250
251impl Default for BlackJack {
252    fn default() -> Self {
253        // Create a new deck
254        let mut deck = Deck::from(crate::cards::StandardCard::default_collection());
255        deck.shuffle();
256        // Create The players hand
257        // Create The players hand
258        let player = deck.draw_many(2).into();
259
260        // Create the dealers hand
261        let dealer = deck.draw_many(2).into();
262
263        Self {
264            player,
265            dealer,
266            deck,
267            dealer_stay_status: false,
268            player_stay_status: false,
269            first_turn: true,
270        }
271    }
272}
273
274/// Game State
275#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
276pub enum GameDisplayState {
277    /// Player has won the game
278    PlayerWon,
279    /// Player has lost the game
280    PlayerLost,
281    /// The game is in progress
282    InProgress,
283}
284
285impl From<GameState> for GameDisplayState {
286    fn from(val: GameState) -> Self {
287        match val {
288            GameState::PlayerLost => GameDisplayState::PlayerLost,
289            GameState::PlayerWon => GameDisplayState::PlayerWon,
290            _ => GameDisplayState::InProgress,
291        }
292    }
293}
294
295/// A struct to allow for custom rending of the game
296#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
297pub struct GameDisplay {
298    /// State of the game
299    i_game_state: GameDisplayState,
300    /// Score of the dealer
301    i_dealer_score: u8,
302    /// Dealer's hand
303    i_dealer_cards: Vec<Card>,
304    /// Player's Score
305    i_player_score: u8,
306    /// Player's hand
307    i_player_cards: Vec<Card>,
308}
309
310impl GameDisplay {
311    /// Return the dealers score
312    pub fn dealer_score(&self) -> u8 {
313        self.i_dealer_score
314    }
315    /// Return the dealers hand
316    pub fn dealer_hand(&self) -> &[Card] {
317        &self.i_dealer_cards
318    }
319    /// Return the players score
320    pub fn player_score(&self) -> u8 {
321        self.i_player_score
322    }
323    /// Return the players hand
324    pub fn player_hand(&self) -> &[Card] {
325        &self.i_player_cards
326    }
327    /// Return the state of the game
328    pub fn state(&self) -> GameDisplayState {
329        self.i_game_state
330    }
331}
332
333impl core::fmt::Display for GameDisplayState {
334    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
335        match self {
336            GameDisplayState::InProgress => f.write_str("In Progress"),
337            GameDisplayState::PlayerWon => f.write_str("Player Won"),
338            GameDisplayState::PlayerLost => f.write_str("Player Lost"),
339        }
340    }
341}
342
343impl core::fmt::Display for GameDisplay {
344    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
345        writeln!(
346            f,
347            "{}\nDealer: {:#?}({})\nPlayer: {:#?}({})",
348            self.i_game_state,
349            self.i_dealer_cards,
350            self.i_dealer_score,
351            self.i_player_cards,
352            self.i_player_score
353        )
354    }
355}