mod macros;
pub(crate) mod rank;
pub(crate) mod suit;
use std::{
cmp::Ordering,
convert::{TryFrom, TryInto},
fmt,
hash::Hash,
iter::{FromIterator, FusedIterator},
str::FromStr,
};
#[doc(inline)]
pub use self::{rank::Rank, suit::Suit};
use crate::{constants::PRIMES, error::ParseCardError};
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Card {
unique_integer: i32,
}
impl Card {
pub const fn new(rank: Rank, suit: Suit) -> Self {
let rank_int = rank.as_i32();
let suit_int = suit.as_i32();
let rank_prime = PRIMES[rank_int as usize];
let bit_rank = (1 << rank_int) << 16;
let card_suit = suit_int << 12;
let card_rank = rank_int << 8;
Self {
unique_integer: bit_rank | card_suit | card_rank | rank_prime,
}
}
pub fn try_from_chars(rank_char: char, suit_char: char) -> Result<Self, ParseCardError> {
let rank = rank_char
.try_into()
.map_err(|incorrect_char| ParseCardError::InvalidRank {
original_input: rank_char.to_string(),
incorrect_char,
})?;
let suit = suit_char
.try_into()
.map_err(|incorrect_char| ParseCardError::InvalidSuit {
original_input: suit_char.to_string(),
incorrect_char,
})?;
Ok(Self::new(rank, suit))
}
pub const fn rank(self) -> Rank {
let rank_int = (self.unique_integer >> 8) & 0xF;
Rank::from_i32(rank_int)
}
pub const fn suit(self) -> Suit {
let suit_int = (self.unique_integer >> 12) & 0xF;
Suit::from_i32(suit_int)
}
pub const fn unique_integer(self) -> i32 { self.unique_integer }
pub fn rank_suit_string(self) -> String {
let mut s = String::with_capacity(2);
s.push(self.rank().as_char());
s.push(self.suit().as_char());
s
}
pub fn parse_to_iter<S>(
strings: S,
) -> ParseToIter<impl Iterator<Item = Result<Self, ParseCardError>>>
where
S: IntoIterator,
S::Item: AsRef<str>,
{
ParseToIter(strings.into_iter().map(|s| s.as_ref().parse()))
}
}
impl FromStr for Card {
type Err = ParseCardError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut chars = value.chars();
if let (Some(rank), Some(suit), None) = (chars.next(), chars.next(), chars.next()) {
let rank =
Rank::try_from(rank).map_err(|incorrect_char| ParseCardError::InvalidRank {
original_input: value.to_string(),
incorrect_char,
})?;
let suit =
Suit::try_from(suit).map_err(|incorrect_char| ParseCardError::InvalidSuit {
original_input: value.to_string(),
incorrect_char,
})?;
Ok(Self::new(rank, suit))
} else {
Err(ParseCardError::InvalidLength {
original_input: value.to_string(),
})
}
}
}
impl PartialOrd for Card {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.rank().cmp(&other.rank()))
}
}
impl Ord for Card {
fn cmp(&self, other: &Self) -> Ordering { self.rank().cmp(&other.rank()) }
}
impl fmt::Debug for Card {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Card")
.field("unique_integer", &self.unique_integer())
.field("rank", &self.rank())
.field("suit", &self.suit())
.finish()
}
}
impl fmt::Display for Card {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ {}{} ]", self.rank(), self.suit())
}
}
#[derive(Debug, Clone)]
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ParseToIter<I>(I);
impl<I: Iterator> Iterator for ParseToIter<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> { self.0.next() }
fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
fn fold<B, F>(self, init: B, f: F) -> B
where
Self: Sized,
F: FnMut(B, Self::Item) -> B,
{
self.0.fold(init, f)
}
}
impl<I: FusedIterator> FusedIterator for ParseToIter<I> {}
impl<I: ExactSizeIterator> ExactSizeIterator for ParseToIter<I> {
fn len(&self) -> usize { self.0.len() }
}
impl<I: DoubleEndedIterator> DoubleEndedIterator for ParseToIter<I> {
fn next_back(&mut self) -> Option<Self::Item> { self.0.next_back() }
}
impl<I, T, E> ParseToIter<I>
where
I: Iterator<Item = Result<T, E>>,
{
pub fn try_collect<C: FromIterator<T>>(self) -> Result<C, E> { self.0.collect() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_str_fails() {
let mut result: Result<Card, ParseCardError>;
result = "2x".parse();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
ParseCardError::InvalidSuit {
original_input: "2x".into(),
incorrect_char: 'x'
}
);
result = "Rd".parse();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
ParseCardError::InvalidRank {
original_input: "Rd".into(),
incorrect_char: 'R'
}
);
result = "AsX".parse();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
ParseCardError::InvalidLength {
original_input: "AsX".into()
}
);
}
#[test]
fn try_from_works() {
for (rank_index, rank) in Rank::ALL_VARIANTS.iter().map(|r| r.as_char()).enumerate() {
for (suit_index, suit) in Suit::ALL_VARIANTS.iter().map(|s| s.as_char()).enumerate() {
let card_string = rank.to_string() + suit.to_string().as_str();
let result = card_string.parse();
assert_eq!(
result,
Ok(Card::new(
Rank::ALL_VARIANTS[rank_index],
Suit::ALL_VARIANTS[suit_index]
))
);
}
}
}
}