games-rs 0.5.0

Pre-implemented games written in rust.
Documentation
use crate::blackjack::errors::BlackJackError;
use crate::blackjack::hand::Hand;
use crate::cards::StandardCard as Card;
use crate::deck::{Deck, DefaultCollection};

//use failure::Error;

/// A struct to represent the games state
#[repr(C)]
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub enum GameState {
    /// Game is currently in progress
    InProgress,
    /// Player has won the game
    PlayerWon,
    /// Player has lost the game
    PlayerLost,
}

#[repr(C)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct FrozenGame {
    /// Player's Hand
    pub player: Vec<Card>,
    /// Dealers Hand
    pub dealer: Vec<Card>,
    /// Deck of cards at play
    pub deck: Vec<Card>,
    /// Whether or not the player
    /// has decided to stay
    pub player_stay: bool,
    /// Whether or not the dealer
    /// has decided to stay
    pub dealer_stay: bool,
    /// Whether or not this is the first turn
    pub first_turn: bool,
}

/// BlackJack Game
#[repr(C)]
#[derive(
    Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash,
)]
pub struct BlackJack {
    /// Player's Hand
    player: Hand,
    /// Dealer's Hand
    dealer: Hand,
    /// First Turn, used for display purposes
    first_turn: bool,
    /// Deck of cards in the game
    deck: Deck<crate::cards::StandardCard>,
    /// Has the player chosen to stay
    player_stay_status: bool,
    /// Has the dealer chosen to stay
    dealer_stay_status: bool,
}

impl BlackJack {
    /// Create a new `Blackjack` Game
    pub fn new_game(decks: usize) -> Self {
        // Create a new deck
        let mut deck = Deck::from(crate::cards::StandardCard::multiple_collections(decks));

        deck.shuffle();
        // Create The players hand
        let player = deck.draw_many(2).into();

        // Create the dealers hand
        let dealer = deck.draw_many(2).into();

        Self {
            player,
            dealer,
            deck,
            dealer_stay_status: false,
            player_stay_status: false,
            first_turn: true,
        }
    }
    /// Freeze the `BlackJack` Game
    /// Err(GameState::PlayerWon) - Player has one, no other data needed
    /// Err(GameState::PlayerLost) - Player has lost, no other data needed
    /// Ok(FrozenGame) - Game is in progress, can be restored later
    ///
    /// # Errors
    ///
    /// If the game state is not in progress, the function will error
    /// And will return the result of the game
    pub fn freeze(self) -> Result<FrozenGame, GameState> {
        match self.status() {
            GameState::InProgress => Ok(FrozenGame {
                player: self.player.into_vec(),
                dealer: self.dealer.into_vec(),
                deck: self.deck.into_vec(),
                first_turn: self.first_turn,
                player_stay: self.player_stay_status,
                dealer_stay: self.dealer_stay_status,
            }),
            gs => Err(gs),
        }
    }
    /// Restore a `BlackKJack` game from `GameState::Frozen`
    pub fn defrost(g: FrozenGame) -> Self {
        Self {
            player: g.player.into(),
            dealer: g.dealer.into(),
            deck: Deck::from(g.deck),
            first_turn: g.first_turn,
            player_stay_status: g.player_stay,
            dealer_stay_status: g.dealer_stay,
        }
    }
    /// Return the `GameState`
    /// Possible values:
    /// `GameState::InProgress` - Game is in progress
    /// `GameState::PlayerWon` - Player has won
    /// `GameState::PlayerLost` - Player has lost
    pub fn status(&self) -> GameState {
        if self.first_turn {
            return GameState::InProgress;
        }
        // Calculate the scores
        let player_score = self.player.score();
        let dealer_score = self.dealer.score();

        // If the player has 5 cards and less than 21 that's a win
        if self.player.cards_len() == 5 && player_score <= 21 {
            return GameState::PlayerWon;
        }

        // Same applies for the dealer
        if self.dealer.cards_len() == 5 && dealer_score <= 21 {
            return GameState::PlayerWon;
        }

        // 21 Is a win
        if player_score == 21 {
            return GameState::PlayerWon;
        }

        // Or a loss if the dealer has it
        if dealer_score == 21 {
            return GameState::PlayerLost;
        }

        // Over 21 is a loss
        if player_score > 21 {
            return GameState::PlayerLost;
        }

        if !(self.player_stay_status || self.dealer_stay_status) {
            return GameState::InProgress;
        }

        // A draw is a loss
        if player_score == dealer_score {
            return GameState::PlayerLost;
        }

        // Over 21 is a loss
        if dealer_score > 21 {
            return GameState::PlayerWon;
        }

        // Highest score decides win
        if player_score > dealer_score {
            return GameState::PlayerWon;
        }
        if player_score < dealer_score {
            return GameState::PlayerLost;
        }

        // Should be unreachable, but just in case
        GameState::InProgress
    }
    /// Draws a card if game is InProgress, will error otherwise
    ///
    /// # Errors
    ///
    /// If the game status is not InProgress this function will error
    /// returning the current game state within the error
    pub fn player_hit(&mut self) -> Result<(), BlackJackError> {
        // Check the game status
        match self.status() {
            GameState::InProgress => {
                self.first_turn = false;
                self.player.add_card(
                    self.deck
                        .draw()
                        .map_err(|_| BlackJackError::OutOfCardsError)?,
                );
                Ok(())
            }
            GameState::PlayerLost => Err(BlackJackError::PlayerLostError),
            GameState::PlayerWon => Err(BlackJackError::PlayerWonError),
        }
    }
    /// Finish the blackjack game (player chooses to stay)
    /// The dealer will draw cards as necessary
    /// game result will be returned
    pub fn finish(mut self) -> GameState {
        self.player_stay_status = true;
        self.dealer_logic();
        match self.status() {
            // Game state should only be Win/Lose
            GameState::InProgress => unreachable!(),
            gs => gs,
        }
    }
    /// Handles the dealers logic for `BlackJack::finish`
    fn dealer_logic(&mut self) {
        self.first_turn = false;
        // Dealer stops attempting at 17 / Higher
        // And action is only taken if the player didn't already win/lose
        while self.status() == GameState::InProgress && self.dealer.score() <= 17 {
            match self.deck.draw() {
                Ok(card) => self.dealer.add_card(card),
                Err(_) => break,
            }
        }
        self.dealer_stay_status = true;
    }

    /// Return a struct meant for displaying the Game
    pub fn display_game(&self) -> GameDisplay {
        if self.first_turn {
            let (score, cards) = match self.dealer.cards().first() {
                Some(card) => (card.face().value(), vec![*card]),
                None => (0, Vec::new()),
            };
            GameDisplay {
                i_dealer_cards: cards,
                i_dealer_score: score,
                i_player_cards: self.player.cards().to_vec(),
                i_player_score: self.player.score(),
                i_game_state: self.status().into(),
            }
        } else {
            GameDisplay {
                i_dealer_cards: self.dealer.cards().to_vec(),
                i_dealer_score: self.dealer.score(),
                i_player_cards: self.player.cards().to_vec(),
                i_player_score: self.player.score(),
                i_game_state: self.status().into(),
            }
        }
    }
}

impl Default for BlackJack {
    fn default() -> Self {
        // Create a new deck
        let mut deck = Deck::from(crate::cards::StandardCard::default_collection());
        deck.shuffle();
        // Create The players hand
        // Create The players hand
        let player = deck.draw_many(2).into();

        // Create the dealers hand
        let dealer = deck.draw_many(2).into();

        Self {
            player,
            dealer,
            deck,
            dealer_stay_status: false,
            player_stay_status: false,
            first_turn: true,
        }
    }
}

/// Game State
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub enum GameDisplayState {
    /// Player has won the game
    PlayerWon,
    /// Player has lost the game
    PlayerLost,
    /// The game is in progress
    InProgress,
}

impl From<GameState> for GameDisplayState {
    fn from(val: GameState) -> Self {
        match val {
            GameState::PlayerLost => GameDisplayState::PlayerLost,
            GameState::PlayerWon => GameDisplayState::PlayerWon,
            _ => GameDisplayState::InProgress,
        }
    }
}

/// A struct to allow for custom rending of the game
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)]
pub struct GameDisplay {
    /// State of the game
    i_game_state: GameDisplayState,
    /// Score of the dealer
    i_dealer_score: u8,
    /// Dealer's hand
    i_dealer_cards: Vec<Card>,
    /// Player's Score
    i_player_score: u8,
    /// Player's hand
    i_player_cards: Vec<Card>,
}

impl GameDisplay {
    /// Return the dealers score
    pub fn dealer_score(&self) -> u8 {
        self.i_dealer_score
    }
    /// Return the dealers hand
    pub fn dealer_hand(&self) -> &[Card] {
        &self.i_dealer_cards
    }
    /// Return the players score
    pub fn player_score(&self) -> u8 {
        self.i_player_score
    }
    /// Return the players hand
    pub fn player_hand(&self) -> &[Card] {
        &self.i_player_cards
    }
    /// Return the state of the game
    pub fn state(&self) -> GameDisplayState {
        self.i_game_state
    }
}

impl core::fmt::Display for GameDisplayState {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            GameDisplayState::InProgress => f.write_str("In Progress"),
            GameDisplayState::PlayerWon => f.write_str("Player Won"),
            GameDisplayState::PlayerLost => f.write_str("Player Lost"),
        }
    }
}

impl core::fmt::Display for GameDisplay {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        writeln!(
            f,
            "{}\nDealer: {:#?}({})\nPlayer: {:#?}({})",
            self.i_game_state,
            self.i_dealer_cards,
            self.i_dealer_score,
            self.i_player_cards,
            self.i_player_score
        )
    }
}