use std::str::FromStr;
use std::{error, fmt};
use crate::{Card, Rank, Suit};
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ParseError {
InvalidChar,
TooShort,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::TooShort => write!(f, "Input string too short"),
Self::InvalidChar => write!(f, "Invalid character encountered"),
}
}
}
impl error::Error for ParseError {}
type Result<T> = std::result::Result<T, ParseError>;
impl TryFrom<char> for Rank {
type Error = ParseError;
fn try_from(c: char) -> Result<Self> {
match c {
'2' => Ok(Self::Two),
'3' => Ok(Self::Three),
'4' => Ok(Self::Four),
'5' => Ok(Self::Five),
'6' => Ok(Self::Six),
'7' => Ok(Self::Seven),
'8' => Ok(Self::Eight),
'9' => Ok(Self::Nine),
'T' => Ok(Self::Ten),
'J' => Ok(Self::Jack),
'Q' => Ok(Self::Queen),
'K' => Ok(Self::King),
'A' => Ok(Self::Ace),
_ => Err(ParseError::InvalidChar),
}
}
}
impl TryFrom<char> for Suit {
type Error = ParseError;
fn try_from(c: char) -> Result<Self> {
match c {
'C' | 'c' | '♣' => Ok(Self::Clubs),
'S' | 's' | '♠' => Ok(Self::Spades),
'H' | 'h' | '♥' => Ok(Self::Hearts),
'D' | 'd' | '♦' => Ok(Self::Diamonds),
_ => Err(ParseError::InvalidChar),
}
}
}
impl TryFrom<char> for Card {
type Error = ParseError;
fn try_from(c: char) -> Result<Self> {
let suit = match c as u32 & 0x000F0 {
0x000A0 => Suit::Spades,
0x000B0 => Suit::Hearts,
0x000C0 => Suit::Diamonds,
0x000D0 => Suit::Clubs,
_ => return Err(ParseError::InvalidChar),
};
let rank = match c as u32 & 0x0000F {
0x00001 => Rank::Ace,
0x00002 => Rank::Two,
0x00003 => Rank::Three,
0x00004 => Rank::Four,
0x00005 => Rank::Five,
0x00006 => Rank::Six,
0x00007 => Rank::Seven,
0x00008 => Rank::Eight,
0x00009 => Rank::Nine,
0x0000A => Rank::Ten,
0x0000B => Rank::Jack,
0x0000D => Rank::Queen,
0x0000E => Rank::King,
_ => return Err(ParseError::InvalidChar),
};
Ok(Card::Regular { rank, suit })
}
}
impl FromStr for Card {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self> {
if s.len() < 2 {
return Err(ParseError::TooShort);
}
let mut chars = s.chars();
let rank = Rank::try_from(chars.next().unwrap())?;
let suit = Suit::try_from(chars.next().unwrap())?;
Ok(Self::Regular { rank, suit })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::tests::{ASCII_REPRS, MIXED_REPRS, UNICODE_REPRS};
use crate::test::FULL_DECK_CARDS;
#[test]
fn invlaid_char() {
assert_eq!("L4".parse::<Card>(), Err(ParseError::InvalidChar));
}
#[test]
fn tooshort() {
assert_eq!("T".parse::<Card>(), Err(ParseError::TooShort));
assert_eq!("J".parse::<Card>(), Err(ParseError::TooShort));
assert_eq!("L".parse::<Card>(), Err(ParseError::TooShort));
assert_eq!("5".parse::<Card>(), Err(ParseError::TooShort));
assert_eq!("C".parse::<Card>(), Err(ParseError::TooShort));
}
#[test]
fn cards_mixed() {
for (s, card) in MIXED_REPRS.iter().zip(FULL_DECK_CARDS) {
assert_eq!(s.parse(), Ok(card))
}
}
#[test]
fn cards_ascii() {
for (s, card) in ASCII_REPRS.iter().zip(FULL_DECK_CARDS) {
assert_eq!(s.parse(), Ok(card));
}
}
#[test]
fn cards_unicode() {
for (c, card) in UNICODE_REPRS.chars().zip(FULL_DECK_CARDS) {
assert_eq!(Card::try_from(c), Ok(card));
}
}
}