fantasy_realms_unofficial_api 0.1.0

An unofficial API for the Fantasy Realms card game.
Documentation
#[cfg(test)]
mod tests;
pub mod deck;
pub mod card_collection;
pub mod hand;

use crate::card_collection::{CardCollection, CardCollectionContains};
use crate::hand::{Hand, Turn};
use crate::deck::Card;

/// Represents a player with complete information.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Player {
    /// The players name.
    pub name: String,
    /// The players hand.
    pub hand: Hand,
    /// The cards in this players hand that are known by opponents.
    pub cards_known_to_opponents: CardCollection,
} impl Player {
    /// Creates a new `Player` instance.
    /// `cards_known_to_opponents` is initialized as empty,
    /// as no cards are known to opponents at the start of the game.
    /// # Arguments
    /// * `name` - A `String` representing the player's name.
    /// * `hand` - A `Hand` containing the cards initially dealt to the player.
    /// # Returns
    /// A new `Player` instance.
    pub fn new(name: String, hand: Hand) -> Self {
        Player {
            name: name, 
            hand: hand, 
            cards_known_to_opponents: CardCollection::new()
        }
    }
}

/// Represents a game with complete information. 
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Game {
    /// The current turn.
    /// This value is zero-indexed, and ranges from `0` to `number_of_players - 1`.
    pub current_turn: usize,
    /// A collection of cards in the discard pile.
    pub discard_pile: CardCollection,
    /// A collection of cards in the deck.
    pub deck: CardCollection,
    /// A list of the players playing the game.
    pub players: Vec<Player>,
    /// Weather or not the game is over.
    pub over: bool,
} impl Game {
    /// Creates a new `Game` instance.
    /// `current_turn` is initialized to 0.
    /// `discard_pile` is initialized as empty,
    /// as the discard pile is empty at the start of the game.
    /// `deck` is initialized to all cards not in the hands of players.
    /// `over` is initialized to false.
    /// # Arguments
    /// * `players` - A `Vec<Player>` that contains all players in the game
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * The number of `players` is not between 3 and 6 (inclusive).
    /// * The same card is found in multiple hands.
    /// # Returns
    /// A `Result<Self, String>` which is:
    /// * `Ok(Game)` if the game can be successfully initialized.
    /// * `Err(String)` containing an error message if validation fails.
    pub fn new(players: Vec<Player>) -> Result<Self, String> {
        if players.len() < 3 || players.len() > 6 {
            return Err("Number of players must be between 3 and 6.".to_string());
        }
        let unique_hand_cards: CardCollection = players
            .iter()
            .fold(CardCollection::new(), 
                |mut acc, player| {
                for card in &player.hand {
                    acc += *card;
                }
                acc
            });
        if unique_hand_cards.len() != 7 * players.len() as u32 {
            return Err("Same card found in multiple hands.".to_string());
        }
        Ok(Game {
            current_turn: 0,
            players: players,
            discard_pile: CardCollection::new(),
            deck: (!CardCollection::new()) - unique_hand_cards,
            over: false,
        })
    }

    /// Plays a single turn in the game based on the provided `Turn` action.
    /// This function validates a draw and discard action and updates the game state accordingly.
    /// # Arguments
    /// * `turn` - A `Turn` represents a draw and a discard action.
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * The `discard_pile` already contains 10 or more cards.
    /// * The card the player attempts to `draw` is not available in the deck or discard pile.
    /// * The card the current player attempts to `discard` is not found in their own hand.
    /// # Side Effects
    /// * **`discard_pile`**:
    ///    * Removes draw card if drawn from discard pile
    ///    * Adds discarded card to the
    /// * **`deck`**:
    ///    * Removes draw card if drawn from deck
    /// * **`players[self.current_turn].hand`**: The current player's hand,
    ///    * Removes discard card
    ///    * Adds draw card
    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
    ///    * Adds draw card if drawn from discard pile
    ///    * Removes discard card if known
    /// * **`current_turn`**: 
    ///    * Incremented to the next player's turn
    /// * **`over`**: 
    ///    * Set to `true` if the `discard_pile` reaches 10 cards
    /// # Returns
    /// A `Result<(), String>` which is:
    /// * `Ok(())` if the turn was executed successfully
    /// * `Err(String)` containing an error message if validation fails
    pub fn play_turn(&mut self, turn: Turn) -> Result<(), String> {
        if self.discard_pile.len() >= 10 {
            return Err("Game is over.".to_string());
        }
        if self.players.iter().any(|player| player.hand.contains(&turn.draw)) {
            return Err(format!("{:?} cannot be drawn.", turn.draw.name))
        }
        if turn.discard == turn.draw {
            if !self.discard_pile.contains(turn.draw) {
                self.discard_pile += turn.discard;
                self.deck -= turn.draw;
            }
            if self.discard_pile.len() == 10 {
                self.over = true;
            } else {
                self.current_turn = (self.current_turn + 1) % self.players.len();
            }
            return Ok(());
        }
        if !self.players[self.current_turn].hand.contains(&turn.discard) {
            return Err(format!("{:?} not found in hand.", turn.discard.name));
        }
        self.discard_pile += turn.discard;
        self.players[self.current_turn].hand.play(&turn);
        self.players[self.current_turn].cards_known_to_opponents -= turn.discard;
        if self.discard_pile.contains(turn.draw) {
            self.discard_pile -= turn.draw;
            self.players[self.current_turn].cards_known_to_opponents += turn.draw;
        } else {
            self.deck -= turn.draw;
        }
        if self.discard_pile.len() == 10 {
            self.over = true;
        } else {
            self.current_turn = (self.current_turn + 1) % self.players.len();
        }
        Ok(())
    }
}

/// Represents a player with incomplete information.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PartialPlayer {
    /// The name of the player.
    pub name: String,
    /// The cards in this players hand that are known by opponents.
    pub cards_known_to_opponents: CardCollection,
} impl PartialPlayer {
    /// Creates a new `PartialPlayer` instance.
    /// `cards_known_to_opponents` is initialized as empty,
    /// as no cards are known to opponents at the start of the game.
    /// # Arguments
    /// * `name` - A `String` representing the player's name.
    /// # Returns
    /// A new `PartialPlayer` instance.
    pub fn new(name: String) -> Self {
        PartialPlayer {
            name: name, 
            cards_known_to_opponents: CardCollection::new()
        }
    }
}

/// Represents the two options for a player in a game with incomplete information.
/// # Variants
/// * `Human (PartialPlayer)` - A player with information unknown by the program.
/// * `Bot (Player)` - A player with all information know by the program.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum PartialGamePlayer {
    Human (PartialPlayer),
    Bot (Player),
}

/// Represents the two options for a player of a `PartialGame`.
/// # Variants
/// * `Human` (PartialTurn) - A turn with information unknown by the program.
/// * `Bot` (Turn) - A turn with all information know by the program.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PartialGameTurn {
    Human (PartialTurn),
    Bot (Turn),
}

/// Represents the two options for a card that is drawn.
/// # Variants
/// * `Deck` - A card drawn from the deck.
/// * `Discard (Card)` - A card drawn from the discard pile.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DrawCard {
    Deck,
    Discard (Card),
}

/// Represents a turn with incomplete information
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct PartialTurn {
    /// The card drawn, either a specific card from the 
    /// discard pile or an unkown card from the deck
    pub draw: DrawCard,
    /// The card discarded
    pub discard: Card,
} impl PartialTurn {
    /// Creates a new `PartialTurn` instance.
    /// # Arguments
    /// * `draw` - A `DrawCard` representing the card drawn.
    /// * `discard` - A `Card` representing the card discarded. 
    /// # Returns 
    /// A new `PartialTurn` instance.
    pub fn new(draw: DrawCard, discard: Card) -> Self {
        PartialTurn {draw: draw, discard: discard}
    }
}

/// Represents a game with some incomplete information.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PartialGame {
    /// The current turn.
    /// This value is zero-indexed, and ranges from `0` to `number_of_players - 1`. 
    pub current_turn: usize,
    /// A collection of cards in the discard pile.
    pub discard_pile: CardCollection,
    /// A list of the players playing the game.
    pub players: Vec<PartialGamePlayer>,
    /// Weather or not the game is over.
    pub over: bool,
} impl PartialGame {
    /// Creates a new `PartialGame` instance.
    /// `current_turn` is initialized to 0.
    /// `discard_pile` is initialized as empty,
    /// as the discard pile is empty at the start of the game.
    /// `over` is initialized to false.
    /// # Arguments
    /// * `players` - A `Vec<PartialGamePlayer>` that contains all players in the game
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * The number of `players` is not between 3 and 6 (inclusive).
    /// # Returns
    /// A `Result<Self, String>` which is:
    /// * `Ok(Game)` if the game can be successfully initialized.
    /// * `Err(String)` containing an error message if validation fails.
    pub fn new(players: Vec<PartialGamePlayer>) -> Result<Self, String> {
        if players.len() < 3 || players.len() > 6 {
            return Err("Number of players must be between 3 and 6.".to_string());
        }
        Ok(PartialGame {
            current_turn: 0,
            players: players,
            discard_pile: CardCollection::new(),
            over: false,
        })
    }

    /// Plays a single turn in the game based on the provided `PartialGameTurn` action.
    /// This function validates a draw and discard action and updates the game state accordingly.
    /// # Arguments
    /// * `turn` - A `PartialGameTurn` represents a draw and a discard action.
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * The `discard_pile` already contains 10 or more cards.
    /// * The card the player attempts to `draw` is not available in the deck or discard pile.
    /// * The card the current player attempts to `discard` is not found in their own hand.
    /// # Side Effects
    /// * **`discard_pile`**:
    ///    * Removes draw card if drawn from discard pile
    ///    * Adds discarded card to the
    /// * **`current_turn`**: 
    ///    * Incremented to the next player's turn
    /// * **`over`**: 
    ///    * Set to `true` if the `discard_pile` reaches 10 cards
    /// ## Human Turn
    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
    ///    * Adds draw card if drawn from discard pile
    ///    * Removes discard card if known
    /// ## Bot Turn
    /// * **`players[self.current_turn].hand`**: The current player's hand,
    ///    * Removes discard card
    ///    * Adds draw card
    /// * **`players[self.current_turn].cards_known_to_opponents`**: The 
    ///    * Adds draw card if drawn from discard pile
    ///    * Removes discard card if known
    /// # Returns
    /// A `Result<(), String>` which is:
    /// * `Ok(())` if the turn was executed successfully
    /// * `Err(String)` containing an error message if validation fails
    pub fn play_turn(&mut self, turn: PartialGameTurn) -> Result<(), String> {
        if self.discard_pile.len() >= 10 {
            return Err("Game is over.".to_string());
        }
        match turn {
            PartialGameTurn::Human (turn) => self.play_human_turn(turn),
            PartialGameTurn::Bot (turn) => self.play_bot_turn(turn),
        }
    }

    /// Helper function of `play_turn`
    /// Plays a single turn in the game for a human provided a `PartialTurn` action.
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * the current turn is a `PartialGamePlayer (Bot)`
    /// # Returns
    /// A `Result<(), String>` which is:
    /// * `Ok(())` if the turn was executed successfully
    /// * `Err(String)` containing an error message if validation fails
    fn play_human_turn(&mut self, turn: PartialTurn) -> Result<(), String> {
        let player = match &mut self.players[self.current_turn] {
            PartialGamePlayer::Human (player) => player,
            PartialGamePlayer::Bot (_) => {
                return Err("Received a bot turn on a human player's turn".to_string());
            }
        };
        if let DrawCard::Discard (card) = turn.draw {
            player.cards_known_to_opponents += card;
            self.discard_pile -= card;
        }
        player.cards_known_to_opponents -= turn.discard;
        self.discard_pile += turn.discard;
        if self.discard_pile.len() == 10 {
             self.over = true;
        } else {
             self.current_turn = (self.current_turn + 1) % self.players.len();
        }
        Ok(())
    }

    /// Helper function of `play_turn`
    /// Plays a single turn in the game for a bot provided a `PartialTurn` action.
    /// # Errors
    /// This function returns an `Err(String)` if:
    /// * the current turn is a `PartialGamePlayer (Human)`
    /// # Returns
    /// A `Result<(), String>` which is:
    /// * `Ok(())` if the turn was executed successfully
    /// * `Err(String)` containing an error message if validation fails
    fn play_bot_turn(&mut self, turn: Turn) -> Result<(), String> {
        let player = match &mut self.players[self.current_turn] {
            PartialGamePlayer::Human (_) => {
                return Err("Received a human turn on a bot player's turn".to_string());
            }
            PartialGamePlayer::Bot (player) => player,
        };
        if self.discard_pile.contains(turn.draw){
            player.cards_known_to_opponents += turn.draw;
        }
        if turn.discard == turn.draw {
            if !self.discard_pile.contains(turn.draw) {
                self.discard_pile += turn.discard;
            }
            if self.discard_pile.len() == 10 {
                 self.over = true;
            } else {
                 self.current_turn = (self.current_turn + 1) % self.players.len();
            }
            return Ok(());
        }
        if !player.hand.contains(&turn.discard) {
            return Err(format!("{:?} not found in hand.", turn.discard.name));
        }
        player.hand.play(&turn);
        player.cards_known_to_opponents -= turn.discard;
        self.discard_pile += turn.discard;
        if self.discard_pile.contains(turn.draw) {
             self.discard_pile -= turn.draw;
             player.cards_known_to_opponents += turn.draw;
        }
        if self.discard_pile.len() == 10 {
             self.over = true;
        } else {
             self.current_turn = (self.current_turn + 1) % self.players.len();
        }
        Ok(())
    }
}