playin-cards 0.2.0

♠ Library for French-suited playing cards
Documentation
use std::fmt;
use std::fmt::Display;

use crate::{Card, Color, Rank, Suit};

impl From<Rank> for char {
    /// Convert Rank to `char` output values are in `A23456789TJQK`
    fn from(rank: Rank) -> Self {
        match rank {
            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',
        }
    }
}

impl From<Suit> for char {
    /// Convert `Suit` to `char`. Output values are one of `♥♦♠♣`
    fn from(suit: Suit) -> Self {
        match suit {
            Suit::Hearts => '',
            Suit::Diamonds => '',
            Suit::Spades => '',
            Suit::Clubs => '',
        }
    }
}

impl From<Color> for char {
    fn from(c: Color) -> char {
        match c {
            Color::Red => 'R',
            Color::Black => 'B',
        }
    }
}

impl fmt::Display for Rank {
    /// Format a Rank as a single ASCII character in `A23456789TJQK`
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        char::from(*self).fmt(f)
    }
}

impl fmt::Display for Suit {
    /// Format a Suit as a single Unicode suit char: e.g `♠`, `♥`, `♦`, or `♣`
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        char::from(*self).fmt(f)
    }
}

impl fmt::LowerHex for Suit {
    /// Format a Suit as a single ASCII char: `H`, `D`, `S`, or `C`
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Hearts => 'H',
            Self::Diamonds => 'D',
            Self::Spades => 'S',
            Self::Clubs => 'C',
        }
        .fmt(f)
    }
}

impl fmt::Display for Color {
    /// Format a `Color` as a single ASCII char: `R` for `Red`, `B` for `Black`
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        char::from(*self).fmt(f)
    }
}

impl fmt::Display for Card {
    /// Format a Card as a Rank, represented by a single char in `A2345678TJQK`,
    /// and a suit, represented as a single Unicode char in `♠♥♦♣`. e.g. `A♣`
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Regular { rank, suit } => write!(f, "{rank}{suit}"),
            Self::Joker(color) => write!(f, "{}J", color),
        }
    }
}

impl fmt::UpperHex for Card {
    /// Format a `Card` as a single Unicode card character, e.g. 🃞
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Joker(color) => match color {
                Color::Red => write!(f, "🂿"),
                Color::Black => write!(f, "🃏"),
            },
            Self::Regular { rank, suit } => {
                let suit: u32 = match suit {
                    Suit::Spades => 0x1F0A,
                    Suit::Hearts => 0x1F0B,
                    Suit::Diamonds => 0x1F0C,
                    Suit::Clubs => 0x1F0D,
                };
                let rank: u32 = match rank {
                    // Have to skip the Knight rank from Unicode
                    Rank::Queen => 0x0D,
                    Rank::King => 0x0E,
                    Rank::Ace => 0x01,
                    _ => *rank as u32,
                };
                let codepoint: u32 = (suit << 4) + rank;
                write!(f, "{}", char::from_u32(codepoint).unwrap())
            }
        }
    }
}

/// Format a Card using a ASCII suit char: e.g `H`, `D`, `S`, or `C`
impl fmt::LowerHex for Card {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Regular { rank, suit } => write!(f, "{rank}{suit:x}"),
            Self::Joker(color) => write!(f, "{}J", color),
        }
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::test::FULL_DECK_CARDS;

    // A full Deck of cards in suit-rank order
    #[rustfmt::skip]
    pub const ASCII_REPRS: [&str; 52] = [
        "AH", "2H", "3H", "4H", "5H", "6H", "7H", "8H", "9H", "TH", "JH", "QH", "KH",
        "AD", "2D", "3D", "4D", "5D", "6D", "7D", "8D", "9D", "TD", "JD", "QD", "KD",
        "AS", "2S", "3S", "4S", "5S", "6S", "7S", "8S", "9S", "TS", "JS", "QS", "KS",
        "AC", "2C", "3C", "4C", "5C", "6C", "7C", "8C", "9C", "TC", "JC", "QC", "KC",
    ];
    // Represents an ordered deck as 2-char mixed
    #[rustfmt::skip]
    pub const MIXED_REPRS: [&str; 52] = [
        "A♥", "2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "T♥", "J♥", "Q♥", "K♥",
        "A♦", "2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "T♦", "J♦", "Q♦", "K♦",
        "A♠", "2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "T♠", "J♠", "Q♠", "K♠",
        "A♣", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "T♣", "J♣", "Q♣", "K♣",
    ];
    // Represents an ordered deck as single-char Unicode
    #[rustfmt::skip]
    pub const UNICODE_REPRS: &str = "\
        \u{1F0B1}\u{1F0B2}\u{1F0B3}\u{1F0B4}\u{1F0B5}\u{1F0B6}\u{1F0B7}\u{1F0B8}\u{1F0B9}\
        \u{1F0BA}\u{1F0BB}\u{1F0BD}\u{1F0BE}\
        \u{1F0C1}\u{1F0C2}\u{1F0C3}\u{1F0C4}\u{1F0C5}\u{1F0C6}\u{1F0C7}\u{1F0C8}\u{1F0C9}\
        \u{1F0CA}\u{1F0CB}\u{1F0CD}\u{1F0CE}\
        \u{1F0A1}\u{1F0A2}\u{1F0A3}\u{1F0A4}\u{1F0A5}\u{1F0A6}\u{1F0A7}\u{1F0A8}\u{1F0A9}\
        \u{1F0AA}\u{1F0AB}\u{1F0AD}\u{1F0AE}\
        \u{1F0D1}\u{1F0D2}\u{1F0D3}\u{1F0D4}\u{1F0D5}\u{1F0D6}\u{1F0D7}\u{1F0D8}\u{1F0D9}\
        \u{1F0DA}\u{1F0DB}\u{1F0DD}\u{1F0DE}";

    #[test]
    fn card_mixed() {
        for (card, exp_repr) in FULL_DECK_CARDS.iter().zip(MIXED_REPRS.iter()) {
            assert_eq!(
                &format!("{}", card),
                exp_repr,
                "Unexpected format for card {}. expected {}",
                card,
                exp_repr
            );
        }
        assert_eq!(format!("{}", Card::Joker(Color::Black)), "BJ");
        assert_eq!(format!("{}", Card::Joker(Color::Red)), "RJ");
    }
    #[test]
    fn card_unicode() {
        for (card, exp_repr) in FULL_DECK_CARDS.iter().zip(UNICODE_REPRS.chars()) {
            assert_eq!(
                format!("{card:X}"),
                exp_repr.to_string(),
                "Unexpected format for card {}. Expected {}",
                card,
                exp_repr
            );
        }
    }
    #[test]
    fn card_ascii() {
        for (card, exp_repr) in FULL_DECK_CARDS.iter().zip(ASCII_REPRS) {
            assert_eq!(
                format!("{card:x}"),
                exp_repr.to_string(),
                "Unexpected format for card {}. Expected {}",
                card,
                exp_repr
            );
        }
    }

    #[test]
    fn rank() {
        assert_eq!("A", format!("{}", Rank::Ace));
        assert_eq!("2", format!("{}", Rank::Two));
        assert_eq!("3", format!("{}", Rank::Three));
        assert_eq!("6", format!("{}", Rank::Six));
        assert_eq!("9", format!("{}", Rank::Nine));
        assert_eq!("T", format!("{}", Rank::Ten));
        assert_eq!("J", format!("{}", Rank::Jack));
        assert_eq!("Q", format!("{}", Rank::Queen));
        assert_eq!("K", format!("{}", Rank::King));
    }
    #[test]
    fn suit() {
        assert_eq!("", format!("{}", Suit::Spades));
        assert_eq!("", format!("{}", Suit::Hearts));
        assert_eq!("", format!("{}", Suit::Diamonds));
        assert_eq!("", format!("{}", Suit::Clubs));
    }
    #[test]
    fn suit_ascii() {
        assert_eq!("S", format!("{:x}", Suit::Spades));
        assert_eq!("H", format!("{:x}", Suit::Hearts));
        assert_eq!("D", format!("{:x}", Suit::Diamonds));
        assert_eq!("C", format!("{:x}", Suit::Clubs));
    }
}