cribbage-core 0.1.5

A (work-in-progress) library providing the core functionality of the game Cribbage.
Documentation
use std::fmt::{self, Debug, Display, Formatter};
use std::str::FromStr;

use crate::CribbageCoreError;

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rank {
    Ace,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
}

impl Rank {
    pub fn ordinal(self) -> u8 {
        match self {
            Rank::Ace => 1,
            Rank::Two => 2,
            Rank::Three => 3,
            Rank::Four => 4,
            Rank::Five => 5,
            Rank::Six => 6,
            Rank::Seven => 7,
            Rank::Eight => 8,
            Rank::Nine => 9,
            Rank::Ten => 10,
            Rank::Jack => 11,
            Rank::Queen => 12,
            Rank::King => 13,
        }
    }

    pub fn value(self) -> u8 {
        match self {
            Rank::Ace => 1,
            Rank::Two => 2,
            Rank::Three => 3,
            Rank::Four => 4,
            Rank::Five => 5,
            Rank::Six => 6,
            Rank::Seven => 7,
            Rank::Eight => 8,
            Rank::Nine => 9,
            Rank::Ten | Rank::Jack | Rank::Queen | Rank::King => 10,
        }
    }
}

impl Display for Rank {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let s = match self {
            Rank::Ace => "A",
            Rank::Two => "2",
            Rank::Three => "3",
            Rank::Four => "4",
            Rank::Five => "5",
            Rank::Six => "6",
            Rank::Seven => "7",
            Rank::Eight => "8",
            Rank::Nine => "9",
            Rank::Ten => "T",
            Rank::Jack => "J",
            Rank::Queen => "Q",
            Rank::King => "K",
        };

        write!(f, "{}", s)
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Suit {
    Spades,
    Diamonds,
    Clubs,
    Hearts,
}

impl Display for Suit {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let s = match self {
            Suit::Hearts => "H",
            Suit::Clubs => "C",
            Suit::Diamonds => "D",
            Suit::Spades => "S",
        };

        write!(f, "{}", s)
    }
}

#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Card {
    rank: Rank,
    suit: Suit,
}

impl Card {
    pub fn new(rank: Rank, suit: Suit) -> Card {
        Card { rank, suit }
    }

    pub fn rank(self) -> Rank {
        self.rank
    }

    pub fn suit(self) -> Suit {
        self.suit
    }
}

impl FromStr for Card {
    type Err = CribbageCoreError;

    fn from_str(card_string: &str) -> Result<Card, CribbageCoreError> {
        let chars: Vec<char> = card_string.trim().chars().collect();
        if chars.len() != 2 {
            return Err(CribbageCoreError::InvalidCardString);
        }

        let rank = match chars[0].to_ascii_uppercase() {
            'A' => Rank::Ace,
            '2' => Rank::Two,
            '3' => Rank::Three,
            '4' => Rank::Four,
            '5' => Rank::Five,
            '6' => Rank::Six,
            '7' => Rank::Seven,
            '8' => Rank::Eight,
            '9' => Rank::Nine,
            'T' => Rank::Ten,
            'J' => Rank::Jack,
            'Q' => Rank::Queen,
            'K' => Rank::King,
            _ => return Err(CribbageCoreError::InvalidCardString),
        };

        let suit = match chars[1].to_ascii_uppercase() {
            'H' => Suit::Hearts,
            'C' => Suit::Clubs,
            'D' => Suit::Diamonds,
            'S' => Suit::Spades,
            _ => return Err(CribbageCoreError::InvalidCardString),
        };

        Ok(Card { rank, suit })
    }
}

impl Debug for Card {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_string())
    }
}

impl Display for Card {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}", self.rank, self.suit)
    }
}

#[cfg(test)]
mod test {
    use crate::card::{Card, Rank, Suit};
    use crate::CribbageCoreError;
    use std::str::FromStr;

    const RANKS: [Rank; 13] = [
        Rank::Ace,
        Rank::Two,
        Rank::Three,
        Rank::Four,
        Rank::Five,
        Rank::Six,
        Rank::Seven,
        Rank::Eight,
        Rank::Nine,
        Rank::Ten,
        Rank::Jack,
        Rank::Queen,
        Rank::King,
    ];

    const SUITS: [Suit; 4] = [Suit::Hearts, Suit::Clubs, Suit::Diamonds, Suit::Spades];

    #[test]
    fn test_rank_to_string() {
        assert_eq!(Rank::Ace.to_string(), "A".to_string());
        assert_eq!(Rank::Two.to_string(), "2".to_string());
        assert_eq!(Rank::Three.to_string(), "3".to_string());
        assert_eq!(Rank::Four.to_string(), "4".to_string());
        assert_eq!(Rank::Five.to_string(), "5".to_string());
        assert_eq!(Rank::Six.to_string(), "6".to_string());
        assert_eq!(Rank::Seven.to_string(), "7".to_string());
        assert_eq!(Rank::Eight.to_string(), "8".to_string());
        assert_eq!(Rank::Nine.to_string(), "9".to_string());
        assert_eq!(Rank::Ten.to_string(), "T".to_string());
        assert_eq!(Rank::Jack.to_string(), "J".to_string());
        assert_eq!(Rank::Queen.to_string(), "Q".to_string());
        assert_eq!(Rank::King.to_string(), "K".to_string());
    }

    #[test]
    fn test_suit_to_string() {
        assert_eq!(Suit::Hearts.to_string(), "H".to_string());
        assert_eq!(Suit::Clubs.to_string(), "C".to_string());
        assert_eq!(Suit::Diamonds.to_string(), "D".to_string());
        assert_eq!(Suit::Spades.to_string(), "S".to_string());
    }

    #[test]
    fn test_card_to_string() {
        for rank in &RANKS {
            for suit in &SUITS {
                let expected = format!("{}{}", rank.to_string(), suit.to_string());
                let card = Card {
                    rank: *rank,
                    suit: *suit,
                };
                assert_eq!(card.to_string(), expected);
            }
        }
    }

    #[test]
    fn test_card_from_str() {
        for rank in &RANKS {
            for suit in &SUITS {
                let card_string = format!("{}{}", rank.to_string(), suit.to_string());
                let expected = Card {
                    rank: *rank,
                    suit: *suit,
                };
                let card = Card::from_str(&card_string).unwrap();
                assert_eq!(card, expected);
            }
        }

        assert_eq!(
            Card::from_str(" AS"),
            Ok(Card {
                rank: Rank::Ace,
                suit: Suit::Spades
            })
        );
        assert_eq!(
            Card::from_str("AS "),
            Ok(Card {
                rank: Rank::Ace,
                suit: Suit::Spades
            })
        );

        assert_eq!(
            Card::from_str(""),
            Err(CribbageCoreError::InvalidCardString)
        );
        assert_eq!(
            Card::from_str("ASA"),
            Err(CribbageCoreError::InvalidCardString)
        );
        assert_eq!(
            Card::from_str("AX"),
            Err(CribbageCoreError::InvalidCardString)
        );
        assert_eq!(
            Card::from_str("XS"),
            Err(CribbageCoreError::InvalidCardString)
        );
    }
}