fantasy_realms_unofficial_api 0.1.0

An unofficial API for the Fantasy Realms card game.
Documentation
use std::{collections::VecDeque, ops::{Deref, DerefMut}};
use crate::deck::{Card, Name, Suit};
use crate::card_collection::{CardCollection, CardCollectionContains};

/// Represents a turn in the game.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Turn {
    /// The card drawn.
    pub draw: Card,
    /// The card discarded.
    pub discard: Card,
} impl Turn {
    /// Creates a new `Turn` instance.
    /// # Arguments
    /// * `draw` - A `Card` representing the card drawn.
    /// * `discard` - A `Card` representing the card discarded.
    /// # Returns 
    /// A new `Turn` instance.
    pub fn new(draw: Card, discard: Card) -> Self {
        Turn {draw: draw, discard: discard}
    }
}

/// Represents a hand of cards.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Hand([Card; 7]);
impl Deref for Hand {
    type Target = [Card; 7];
    fn deref(&self) -> &Self::Target {
        &self.0
    }
} impl DerefMut for Hand {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
} impl<'a> IntoIterator for &'a Hand {
    type Item = &'a Card;
    type IntoIter = std::slice::Iter<'a, Card>;
    
    fn into_iter(self) -> Self::IntoIter {
        self.0.iter()
    }
} impl IntoIterator for Hand {
    type Item = Card;
    type IntoIter = std::array::IntoIter<Card, 7>;
    
    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
} impl Hand {
    /// Creates a new `Hand` instance.
    /// # Arguments
    /// * `hand` - A `[Card; 7]` representing all cards in the hand.
    /// # Returns
    /// A new `Hand` instance.
    pub fn new(hand: [Card; 7]) -> Self {
        Hand(hand)
    }

    /// Helper function for `Game` and `PartialGame`.
    /// Replaces the card discarded with the card drawn in the hand.
    pub(crate) fn play(&mut self, turn: &Turn) {
        if let Some(index_to_replace) = self.iter().position(|card| *card == turn.discard) {
            self[index_to_replace] = turn.draw;
        } else {
            unreachable!()
        }
    }

    /// Calculates the maximum score of the hand.
    /// Builds all possible card configurations of basic cards from wilds and cards that modify other cards.
    /// Scores each configuration to find the maximum score.
    /// # Arguments
    /// * `discard_pile` - An `&CardCollection` representing the cards in the discard pile.
    /// # Returns
    /// An `i16` representing the score of the hand.
    pub fn score(&self, discard_pile: &CardCollection) -> i16 {
        let mut queue: VecDeque<ProcessingHand> = VecDeque::from(
            [ProcessingHand::new(0, self.to_vec())]
        );
        let mut possible_hands: Vec<Vec<Card>> = Vec::new();
        while let Some(hand) = queue.pop_front() {
            let mut new_elements: Vec<Vec<Card>> = Vec::new();
            if let Some(adder) = hand.card().adder {
                new_elements.extend(adder(&hand.hand, discard_pile));
            } else {
                new_elements.push(hand.hand.clone());
            }
            if hand.index == hand.hand.len() - 1 {
                possible_hands.extend(new_elements);
            } else {
                queue.extend(new_elements
                    .into_iter()
                    .map(|item| ProcessingHand::new(hand.index + 1, item))
                    .collect::<Vec<ProcessingHand>>());
            }
        }
        let mut queue: VecDeque<ProcessingHand> = VecDeque::from(
            possible_hands
                .into_iter()
                .map(|hand| ProcessingHand::new(0, hand))
                .collect::<Vec<ProcessingHand>>()
        );
        let mut possible_hands: Vec<Vec<Card>> = Vec::new();
        while let Some(hand) = queue.pop_front() {
            let mut new_elements: Vec<Vec<Card>> = Vec::new();
            if let Some(wild) = hand.card().wild {
                new_elements.extend(wild(&hand.hand, hand.index));
            } else {
                new_elements.push(hand.hand.clone());
            }
            if hand.index == hand.hand.len() - 1 {
                possible_hands.extend(new_elements);
            } else {
                queue.extend(new_elements
                    .into_iter()
                    .map(|item| ProcessingHand::new(hand.index + 1, item))
                    .collect::<Vec<ProcessingHand>>());
            }
        }
        let mut queue: VecDeque<ProcessingHand> = VecDeque::from(
            possible_hands
                .into_iter()
                .map(|hand| ProcessingHand::new(0, hand))
                .collect::<Vec<ProcessingHand>>()
        );
        let mut possible_hands: Vec<Vec<Card>> = Vec::new();
        while let Some(hand) = queue.pop_front() {
            let mut new_elements: Vec<Vec<Card>> = Vec::new();
            if let Some(modifier) = hand.card().modifier {
                new_elements.extend(modifier(&hand.hand));
            } else {
                new_elements.push(hand.hand.clone());
            }
            if hand.index == hand.hand.len() - 1 {
                possible_hands.extend(new_elements);
            } else {
                queue.extend(new_elements
                    .into_iter()
                    .map(|item| ProcessingHand::new(hand.index + 1, item))
                    .collect::<Vec<ProcessingHand>>());
            }
        }
        possible_hands
            .into_iter()
            .map(|hand| Hand::build_scoring_hand(hand).score())
            .max()
            .unwrap_or(0)
    }

    /// A helper function for `score` that builds a `ScoringHand`.
    fn build_scoring_hand(mut cards: Vec<Card>) -> ScoringHand {
        let mut immunity_cards = CardCollection::new();
        let mut blanked_cards = CardCollection::new();
        for _ in 0..2 {
            for card in cards.iter() {
                if let Some(immunity) = card.immunity {
                    immunity(&mut immunity_cards);
                }
                if let Some(blanks) = card.blanks {
                    blanks(&cards, &immunity_cards, &mut blanked_cards);
                }
            }
            cards.retain(|card| !blanked_cards.contains(card.name));
        }
        ScoringHand {cards: cards, immunity: immunity_cards}
    }
}

/// Represents a collection of cards in the process of
/// being expanded into all possible hands containg only basic cards.
struct ProcessingHand {
    /// The index of the card being analized.
    index: usize,
    /// The current hand of cards.
    hand: Vec<Card>,
} impl ProcessingHand {
    /// Creates a new `ProcessingHand` instance.
    /// # Arguments
    /// * `index` - A `usize` representing the index of the card being processed.
    /// * `hand` - A `Vec<Card>` representing the hand being expanded.
    /// # Returns 
    /// A new `ProcessingHand` instance.
    fn new(index: usize, hand: Vec<Card>) -> Self {
        ProcessingHand {index: index, hand: hand}
    }
    /// Gets the current card being processed.
    /// # Returns 
    /// The `Card` that is at the index being analized.
    fn card(&self) -> Card {
        self.hand[self.index]
    }
}

/// Represents a hand of basic cards that cannot change
/// themselves or other cards in the hand.
pub(crate) struct ScoringHand {
    /// The cards in the hand
    pub(crate) cards: Vec<Card>,
    /// The cards that are removed from penalties of other cards.
    pub(crate) immunity: CardCollection,
} impl ScoringHand {
    /// Finds the score of a hand of basic cards.
    /// # Returns
    /// An `i16` representing the score of the hand.
    fn score(self) -> i16 {
        let mut total: i16 = self.cards.iter().map(|card| card.strength).sum();
        for card in self.cards.iter() {
            if let Some(bonus) = card.bonus {
                total += bonus(&self);
            }
            if let Some(penalty) = card.penalty {
                total -= penalty(&self, &self.immunity);
            }
        }
        total
    }

    /// A helper function for `score` that checks if the hand contains a card.
    /// # Arguments
    /// * `name` - The name of the card to look for.
    /// # Returns
    /// A `bool` weather or not the card is in the hand.
    pub(crate) fn contains_name(&self, name: Name) -> bool {
        self.cards.iter().any(|card| card.name == name)
    }

    /// A helper function for `score` that finds the 
    /// number of cards of a specific suit in the hand.
    /// # Arguments
    /// * `suit` - The suit to count.
    /// # Returns
    /// An `i16` the total number of cards of a specific suit.
    pub(crate) fn number_of_suit(&self, suit: Suit) -> i16 {
        self.cards.iter().filter(|card| card.suit == suit).count() as i16
    }
}