1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{Debug, Display, Formatter};

use super::{rank_from_string, Rank, Suit, ACE, JACK, KING, QUEEN};

/// Represents one card in the game.
///
/// Cards can be converted from Strings or `&str`s.
/// The formats for this are the same formats used by Display and Debug.
/// See the descriptions for `TryFrom<String>` and `TryFrom<&str>` below for details.
///
/// # Examples
///
/// Parsing cards from strings:
/// ```
/// use std::convert::TryFrom;
/// # use freecell::Card;
/// use freecell::{ACE, JACK, KING, QUEEN};
/// use freecell::Suit::{Club, Diamond, Heart, Spade};
///
/// // Short representation used by Debug
/// assert_eq!(Ok(Card { suit: Diamond, rank: ACE }), Card::try_from("AD"));
/// assert_eq!(Ok(Card { suit: Club, rank: 4 }), Card::try_from("4C"));
/// assert_eq!(Ok(Card { suit: Diamond, rank: 10 }), Card::try_from("TD"));
/// assert_eq!(Ok(Card { suit: Club, rank: JACK }), Card::try_from("jc"));
/// assert_eq!(Ok(Card { suit: Spade, rank: QUEEN }), Card::try_from("qS"));
/// assert_eq!(Ok(Card { suit: Heart, rank: KING }), Card::try_from("Kh"));
///
/// // Long representation used by Display
/// assert_eq!(Ok(Card { suit: Diamond, rank: JACK }), Card::try_from("Jack of Diamonds"));
/// assert_eq!(Ok(Card { suit: Club, rank: 10 }), Card::try_from("10 oF cLuBs"));
/// ```
///
/// A formatted card can be converted back to the original card:
/// ```
/// # use std::convert::TryFrom;
/// # use freecell::{Card, ACE};
/// # use freecell::Suit::Spade;
/// let ace_of_spades = Card { suit: Spade, rank: ACE };
/// // Formatted using Display
/// assert_eq!(Ok(ace_of_spades), Card::try_from(ace_of_spades.to_string()));
/// // Formatted using Debug
/// assert_eq!(Ok(ace_of_spades), Card::try_from(format!("{:?}", ace_of_spades)));
/// ```
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub struct Card {
    pub suit: Suit,
    pub rank: Rank,
}

impl Display for Card {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let rank_string = match self.rank {
            JACK => String::from("Jack"),
            QUEEN => String::from("Queen"),
            KING => String::from("King"),
            ACE => String::from("Ace"),
            _ => format!("{}", self.rank),
        };
        write!(f, "{} of {}s", rank_string, self.suit)
    }
}

impl Debug for Card {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let rank_string = match self.rank {
            JACK => String::from("J"),
            QUEEN => String::from("Q"),
            KING => String::from("K"),
            ACE => String::from("A"),
            10 => String::from("T"),
            _ => format!("{}", self.rank),
        };
        write!(f, "{}{:?}", rank_string, self.suit)
    }
}

// TODO [low priority] is there a solution for this:
// would like this to be
// impl<S: Into<String>> TryFrom<S> for Card
// but this doesn't work because of https://github.com/rust-lang/rust/issues/50133
impl TryFrom<String> for Card {
    type Error = String;

    /// Converts a String to a Card.
    ///
    /// The String must follow one of two formats.
    /// Both formats are case-insensitive.
    ///
    /// # The short format used by Debug
    ///
    /// The card is represented by a string of two characters.
    /// The first character denotes the card's rank:
    /// - 'A' or '1' - Ace
    /// - '2' - 2
    /// - ...
    /// - '9' - 9
    /// - 'T' - 10
    /// - 'J' - Jack
    /// - 'Q' - Queen
    /// - 'K' - King
    ///
    /// The second character denotes the suit:
    /// - 'C' - Club
    /// - 'S' - Spade
    /// - 'H' - Heart
    /// - 'D' - Diamond
    ///
    /// ## Examples
    ///
    /// ```
    /// # use std::convert::TryFrom;
    /// # use freecell::Card;
    /// use freecell::{ACE, JACK, KING, QUEEN};
    /// use freecell::Suit::{Club, Diamond, Heart, Spade};
    ///
    /// assert_eq!(Ok(Card { suit: Diamond, rank: ACE }), Card::try_from("AD"));
    /// assert_eq!(Ok(Card { suit: Club, rank: 4 }), Card::try_from("4C"));
    /// assert_eq!(Ok(Card { suit: Diamond, rank: 10 }), Card::try_from("TD"));
    /// assert_eq!(Ok(Card { suit: Club, rank: JACK }), Card::try_from("jc"));
    /// assert_eq!(Ok(Card { suit: Spade, rank: QUEEN }), Card::try_from("qS"));
    /// assert_eq!(Ok(Card { suit: Heart, rank: KING }), Card::try_from("Kh"));
    /// ```
    ///
    /// # The long format used by Display
    ///
    /// The card is represented by a string of the form "\<rank> of \<suit>s", where
    /// \<rank> can be the rank's number or its name.
    ///
    /// ## Examples
    ///
    /// ```
    /// # use std::convert::TryFrom;
    /// # use freecell::Card;
    /// use freecell::{JACK, QUEEN};
    /// use freecell::Suit::{Club, Diamond, Spade};
    ///
    /// assert_eq!(Ok(Card { suit: Diamond, rank: JACK }), Card::try_from("Jack of Diamonds"));
    /// assert_eq!(Ok(Card { suit: Club, rank: 3 }), Card::try_from("3 of clubs"));
    /// assert_eq!(Ok(Card { suit: Spade, rank: QUEEN }), Card::try_from("12 oF sPaDeS"));
    /// ```
    fn try_from(string: String) -> Result<Card, Self::Error> {
        // TODO [low priority] use regex
        let string = string.trim();

        if string.len() == 2 {
            // string uses the short Debug format
            // the first character is the rank, the second character is the suit
            let suit = Suit::try_from(&string[1..2])?;
            let rank = rank_from_string(&string[0..1])?;
            Ok(Card { suit, rank })
        } else {
            // string seems to use the long Display format
            // the first word is the rank, the second word is the suit
            let string: Vec<&str> = string.split(' ').collect();
            let suit = Suit::try_from(*string.last().unwrap())?;
            let rank = rank_from_string(string[0])?;
            Ok(Card { suit, rank })
        }
    }
}

impl TryFrom<&str> for Card {
    type Error = String;

    /// Converts a `&str` to a Card.
    ///
    /// See the description of `TryFrom<String>` for details.
    fn try_from(string: &str) -> Result<Card, Self::Error> {
        Card::try_from(string.to_string())
    }
}