use crate::basic::types::basic_card::BasicCard;
use crate::basic::types::pips::{Pip, PipType};
use crate::basic::types::traits::DeckedBase;
use crate::common::errors::CardError;
use crate::localization::{FluentName, Named};
use colored::{Color, Colorize};
use fluent_templates::LanguageIdentifier;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize,
)]
pub struct Card<DeckType>
where
DeckType: DeckedBase,
{
pub base_card: BasicCard,
pub deck: PhantomData<DeckType>,
}
impl<DeckType: DeckedBase> Card<DeckType> {
#[must_use]
pub fn new(base_card: BasicCard) -> Self {
Self::from(base_card)
}
#[must_use]
pub fn base(&self) -> BasicCard {
self.base_card
}
#[must_use]
pub fn color(&self) -> Color {
let binding = DeckType::colors();
let color = binding.get(&self.base_card.suit);
color.map_or(Color::White, |color| *color)
}
#[must_use]
pub fn color_index_string(&self) -> String {
self.color_string(self.base_card.index())
}
#[must_use]
pub fn color_symbol_string(&self) -> String {
self.color_string(self.base_card.to_string())
}
fn color_string(&self, s: String) -> String {
match self.color() {
Color::Red => s.red().to_string(),
Color::Blue => s.blue().to_string(),
Color::Green => s.green().to_string(),
Color::Yellow => s.yellow().to_string(),
Color::Magenta => s.magenta().to_string(),
Color::Cyan => s.cyan().to_string(),
Color::BrightBlack => s.bright_black().to_string(),
Color::BrightRed => s.bright_red().to_string(),
Color::BrightGreen => s.bright_green().to_string(),
Color::BrightYellow => s.bright_yellow().to_string(),
Color::BrightBlue => s.bright_blue().to_string(),
Color::BrightMagenta => s.bright_magenta().to_string(),
Color::BrightCyan => s.bright_cyan().to_string(),
_ => s,
}
}
#[must_use]
pub fn index(&self) -> String {
self.base_card.index()
}
#[must_use]
pub fn is_blank(&self) -> bool {
self.base_card.is_blank()
}
#[must_use]
pub fn is_valid(&self) -> bool {
<DeckType as DeckedBase>::basic_pile().contains(&self.base_card)
}
#[must_use]
pub fn fluent_name_default(&self) -> String {
self.fluent_name(&FluentName::US_ENGLISH)
}
#[must_use]
pub fn fluent_name(&self, lid: &LanguageIdentifier) -> String {
match self.base_card.suit.pip_type {
PipType::Special => self.fluent_rank_name(lid),
PipType::Joker => {
format!("Joker {}", self.fluent_rank_name(lid))
}
_ => {
format!(
"{}{}{}",
self.fluent_rank_name(lid),
Self::fluent_connector(lid),
self.fluent_suit_name(lid)
)
}
}
}
fn fluent_connector(lid: &LanguageIdentifier) -> String {
match lid {
&FluentName::DEUTSCH => " ".to_string(),
_ => " of ".to_string(),
}
}
#[must_use]
pub fn fluent_rank_name(&self, lid: &LanguageIdentifier) -> String {
let s: String = match self.base_card.suit.pip_type {
PipType::Special => {
format!(
"{}-special-{}",
DeckType::fluent_name_base(),
self.base_card.rank.index.to_lowercase()
)
}
_ => {
format!(
"{}-{}",
DeckType::fluent_name_base(),
self.base_card.rank.index.to_lowercase()
)
}
};
FluentName::new("name-rank").fluent_value(s.as_str(), lid)
}
#[must_use]
pub fn fluent_suit_name(&self, lid: &LanguageIdentifier) -> String {
let s = format!(
"{}-{}",
DeckType::fluent_name_base(),
self.base_card.suit.index.to_lowercase()
);
FluentName::new("name-suit").fluent_value(s.as_str(), lid)
}
}
impl<DeckType: DeckedBase> DeckedBase for Card<DeckType> {
fn base_vec() -> Vec<BasicCard> {
DeckType::base_vec()
}
fn colors() -> HashMap<Pip, Color> {
DeckType::colors()
}
fn deck_name() -> String {
DeckType::deck_name()
}
fn fluent_deck_key() -> String {
DeckType::fluent_deck_key()
}
}
impl<DeckType: Default + Copy + Ord + DeckedBase> Display for Card<DeckType> {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.base_card)
}
}
impl<DeckType: DeckedBase> From<BasicCard> for Card<DeckType> {
fn from(pips: BasicCard) -> Self {
Self {
base_card: pips,
deck: PhantomData,
}
}
}
impl<DeckType: DeckedBase> From<&BasicCard> for Card<DeckType> {
fn from(pips: &BasicCard) -> Self {
Self::from(*pips)
}
}
impl<DeckType: DeckedBase> FromStr for Card<DeckType> {
type Err = CardError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.trim().to_uppercase();
let mut cards = Self::base_vec();
cards.push(BasicCard::default());
cards
.iter()
.find(|c| (c.to_string() == s) || (c.index() == s))
.copied()
.map(Self::from)
.ok_or_else(|| CardError::InvalidCard(s.clone()))
}
}
#[cfg(test)]
#[allow(non_snake_case, unused_imports)]
mod basic__types__card_tests {
use super::*;
use crate::basic::decks::cards::french::FrenchBasicCard;
use crate::basic::decks::french::French;
use crate::prelude::SkatBasicCard;
#[test]
fn new() {
let card = Card::<French>::new(FrenchBasicCard::ACE_SPADES);
assert_eq!(FrenchBasicCard::ACE_SPADES, card.base());
assert_eq!(card.to_string(), "Aâ™ ");
}
#[test]
fn new__invalid_basic_card() {
let card = Card::<French>::new(SkatBasicCard::KÖNIG_LAUB);
assert_eq!(SkatBasicCard::KÖNIG_LAUB, card.base());
}
#[test]
fn is_blank() {
let card = Card::<French>::default();
assert!(card.is_blank());
}
#[test]
fn is_valid() {
assert!(Card::<French>::new(FrenchBasicCard::ACE_SPADES).is_valid());
assert!(!Card::<French>::new(SkatBasicCard::KÖNIG_LAUB).is_valid());
}
#[test]
fn fluent_name() {
let nine_of_clubs: Card<French> = FrenchBasicCard::NINE_CLUBS.into();
assert_eq!(
"Nine of Clubs",
nine_of_clubs.fluent_name(&FluentName::US_ENGLISH)
);
assert_eq!("Neun Klee", nine_of_clubs.fluent_name(&FluentName::DEUTSCH));
}
#[test]
fn fluent_name_default() {
let eight_of_diamonds: Card<French> = FrenchBasicCard::EIGHT_DIAMONDS.into();
assert_eq!("Eight of Diamonds", eight_of_diamonds.fluent_name_default());
}
#[test]
fn fluent_rank_name() {
let card: Card<French> = FrenchBasicCard::NINE_CLUBS.into();
assert_eq!("Nine", card.fluent_rank_name(&FluentName::US_ENGLISH));
}
#[test]
fn fluent_suit_name() {
let card: Card<French> = FrenchBasicCard::NINE_CLUBS.into();
assert_eq!("Clubs", card.fluent_suit_name(&FluentName::US_ENGLISH));
}
#[test]
fn decked_base__vec() {
let cards = Card::<French>::base_vec();
let s: String = cards
.iter()
.map(|c| c.to_string())
.collect::<Vec<String>>()
.join(", ");
assert_eq!(
"B🃟, L🃟, A♠, K♠, Q♠, J♠, T♠, 9♠, 8♠, 7♠, 6♠, 5♠, 4♠, 3♠, 2♠, A♥, K♥, Q♥, J♥, T♥, 9♥, 8♥, 7♥, 6♥, 5♥, 4♥, 3♥, 2♥, A♦, K♦, Q♦, J♦, T♦, 9♦, 8♦, 7♦, 6♦, 5♦, 4♦, 3♦, 2♦, A♣, K♣, Q♣, J♣, T♣, 9♣, 8♣, 7♣, 6♣, 5♣, 4♣, 3♣, 2♣",
s
);
}
#[test]
fn display() {
let basecard = FrenchBasicCard::ACE_SPADES;
let card: Card<French> = basecard.into();
assert_eq!("Aâ™ ", card.to_string());
assert_eq!("Aâ™ ", basecard.to_string());
}
#[test]
fn from_str() {
assert_eq!("AS", Card::<French>::from_str("as").unwrap().index());
assert_eq!("__", Card::<French>::from_str("__").unwrap().index());
}
#[test]
fn to_string_from_str() {
let base_cards = Card::<French>::base_vec();
for base_card in base_cards {
let card: Card<French> = Card::<French>::from(base_card);
let s = card.to_string();
let index = card.index();
assert_eq!(card, Card::<French>::from_str(&s).unwrap());
assert_eq!(card, Card::<French>::from_str(&index).unwrap());
assert_eq!(card, Card::<French>::from_str(&s.to_lowercase()).unwrap());
assert_eq!(
card,
Card::<French>::from_str(&index.to_lowercase()).unwrap()
);
}
}
fn with_colors_enabled<F: FnOnce()>(f: F) {
colored::control::set_override(true);
f();
colored::control::unset_override();
}
macro_rules! color_deck {
($name:ident, $color:expr) => {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct $name;
impl DeckedBase for $name {
fn base_vec() -> Vec<BasicCard> {
vec![FrenchBasicCard::ACE_HEARTS]
}
fn colors() -> HashMap<Pip, colored::Color> {
use crate::basic::decks::cards::french::FrenchSuit;
let mut m = HashMap::new();
m.insert(FrenchSuit::HEARTS, $color);
m
}
fn deck_name() -> String {
stringify!($name).to_string()
}
fn fluent_deck_key() -> String {
"french".to_string()
}
}
};
}
color_deck!(RedDeck, colored::Color::Red);
color_deck!(BlueDeck, colored::Color::Blue);
color_deck!(GreenDeck, colored::Color::Green);
color_deck!(YellowDeck, colored::Color::Yellow);
color_deck!(MagentaDeck, colored::Color::Magenta);
color_deck!(CyanDeck, colored::Color::Cyan);
color_deck!(BrightBlackDeck, colored::Color::BrightBlack);
color_deck!(BrightRedDeck, colored::Color::BrightRed);
color_deck!(BrightGreenDeck, colored::Color::BrightGreen);
color_deck!(BrightYellowDeck, colored::Color::BrightYellow);
color_deck!(BrightBlueDeck, colored::Color::BrightBlue);
color_deck!(BrightMagentaDeck, colored::Color::BrightMagenta);
color_deck!(BrightCyanDeck, colored::Color::BrightCyan);
#[test]
fn color_string__all_variants() {
use colored::Colorize as _;
let ace_hearts = FrenchBasicCard::ACE_HEARTS;
let index = ace_hearts.index();
with_colors_enabled(|| {
assert_eq!(
Card::<RedDeck>::new(ace_hearts).color_index_string(),
index.red().to_string()
);
assert_eq!(
Card::<BlueDeck>::new(ace_hearts).color_index_string(),
index.blue().to_string()
);
assert_eq!(
Card::<GreenDeck>::new(ace_hearts).color_index_string(),
index.green().to_string()
);
assert_eq!(
Card::<YellowDeck>::new(ace_hearts).color_index_string(),
index.yellow().to_string()
);
assert_eq!(
Card::<MagentaDeck>::new(ace_hearts).color_index_string(),
index.magenta().to_string()
);
assert_eq!(
Card::<CyanDeck>::new(ace_hearts).color_index_string(),
index.cyan().to_string()
);
assert_eq!(
Card::<BrightBlackDeck>::new(ace_hearts).color_index_string(),
index.bright_black().to_string()
);
assert_eq!(
Card::<BrightRedDeck>::new(ace_hearts).color_index_string(),
index.bright_red().to_string()
);
assert_eq!(
Card::<BrightGreenDeck>::new(ace_hearts).color_index_string(),
index.bright_green().to_string()
);
assert_eq!(
Card::<BrightYellowDeck>::new(ace_hearts).color_index_string(),
index.bright_yellow().to_string()
);
assert_eq!(
Card::<BrightBlueDeck>::new(ace_hearts).color_index_string(),
index.bright_blue().to_string()
);
assert_eq!(
Card::<BrightMagentaDeck>::new(ace_hearts).color_index_string(),
index.bright_magenta().to_string()
);
assert_eq!(
Card::<BrightCyanDeck>::new(ace_hearts).color_index_string(),
index.bright_cyan().to_string()
);
});
}
#[test]
fn fluent_name__joker() {
let joker_card = Card::<French>::new(FrenchBasicCard::BIG_JOKER);
let name = joker_card.fluent_name(&FluentName::US_ENGLISH);
assert!(
name.starts_with("Joker"),
"Expected Joker prefix, got: {name}"
);
assert!(!name.is_empty());
}
#[test]
fn decked_base__colors__passthrough() {
assert!(!Card::<crate::prelude::Standard52>::colors().is_empty());
}
#[test]
fn decked_base__deck_name__passthrough() {
let name = Card::<crate::prelude::Standard52>::deck_name();
assert!(!name.is_empty());
assert_ne!(name, "xyzzy");
assert_eq!(name, "Standard 52");
}
#[test]
fn decked_base__fluent_deck_key__passthrough() {
let key = Card::<crate::prelude::Standard52>::fluent_deck_key();
assert!(!key.is_empty());
assert_ne!(key, "xyzzy");
}
}