ortalib/
lib.rs

1use std::{
2    fmt::{Debug, Display, Formatter},
3    str::FromStr,
4    sync::atomic::{AtomicUsize, Ordering},
5};
6
7use enum_iterator::{Sequence, all};
8use serde::{Deserialize, Serialize, de::Visitor};
9
10pub type Chips = f64;
11pub type Mult = f64;
12
13/// Represents the total inputs of one scoring round in Ortalab.
14///
15/// Contains:
16///
17/// * The [`Card`]s that the user has selected to play.
18/// * The [`Card`]s that the user has kept held in their hand (i.e. not played).
19/// * The player's (up to five) held [`JokerCard`]s.
20#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
21pub struct Round {
22    pub cards_played: Vec<Card>,
23
24    #[serde(default)]
25    pub cards_held_in_hand: Vec<Card>,
26
27    #[serde(default)]
28    pub jokers: Vec<JokerCard>,
29}
30
31/// Represents one playing card.
32///
33/// Contains:
34///
35/// * The [`Rank`] of the card (2-10 J Q K A)
36/// * The [`Suit`] (**♥Hearts**, **♣Clubs**, **♦Diamonds**, **♠Spades**)
37/// * A possible [`Enhancement`].
38/// * A possible [`Edition`].
39///
40/// `Card`s will **only** compare equal to themselves.
41/// i.e. if you have two distinct cards with the same values,
42///      but they were not minted from the same `Card`,
43///      they will compare non-equal.
44///      However, if you `Copy` or `Clone` a `Card`, it will compare
45///      equal to the original.
46///
47/// If you are looking for simple base card equality, instead compare
48/// the `rank` / `suit`. Of course, `enhancement` / `edition` can also be
49/// compared if needed.
50#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
51pub struct Card {
52    pub rank: Rank,
53    pub suit: Suit,
54    pub enhancement: Option<Enhancement>,
55    pub edition: Option<Edition>,
56    #[doc(hidden)]
57    unique_index: usize,
58    // Skipped: Seal
59}
60
61/// Card Ranks are the thirteen values that can be found on a playing card in Ortalab.
62/// These values are Aces, the numerals 2 to 10, and the three face cards: Jacks, Queens and Kings.
63/// The value of each card can be accessed with [`Rank::rank_value`] as follows:
64///
65/// ```
66/// # use ortalib::Rank;
67/// assert_eq!(Rank::Two.rank_value(),   2.0);
68/// assert_eq!(Rank::Five.rank_value(),  5.0);
69/// assert_eq!(Rank::Ten.rank_value(),   10.0);
70/// assert_eq!(Rank::Jack.rank_value(),  10.0);
71/// assert_eq!(Rank::Queen.rank_value(), 10.0);
72/// assert_eq!(Rank::King.rank_value(),  10.0);
73/// assert_eq!(Rank::Ace.rank_value(),   11.0);
74/// ```
75#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
76pub enum Rank {
77    Two,
78    Three,
79    Four,
80    Five,
81    Six,
82    Seven,
83    Eight,
84    Nine,
85    Ten,
86    Jack,
87    Queen,
88    King,
89    Ace,
90}
91
92/// In Ortalab, there are 4 suits: **♥Hearts**, **♣Clubs**, **♦Diamonds**, **♠Spades**.
93#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
94pub enum Suit {
95    Spades,
96    Hearts,
97    Clubs,
98    Diamonds,
99}
100
101/// Represents the color of a [`Suit`].
102///
103/// This is useful for determining which suits are "the same color".
104///
105/// Specifically, **♠Spades** and **♣Clubs** are black, and **♥Hearts** and **♦Diamonds** are red.
106#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
107pub enum SuitColor {
108    Black,
109    Red,
110}
111
112/// Represents one Joker card.
113/// Simply includes the Joker effect, and a possible edition.
114///
115/// `JokerCard`s will **only** compare equal to themselves.
116/// i.e. if you have two distinct `JokerCard`s with the same values,
117///      but they were not minted from the same `JokerCard`,
118///      they will compare non-equal.
119///      However, if you `Copy` or `Clone` a `JokerCard`, it will compare
120///      equal to the original.
121///
122/// If you are looking for simple base joker card equality, instead compare
123/// the `joker` field. Of course, `edition`` can also be
124/// compared if needed.
125
126#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
127pub struct JokerCard {
128    pub joker: Joker,
129    pub edition: Option<Edition>,
130    #[doc(hidden)]
131    unique_index: usize,
132}
133
134/// The various Joker effects supported in Ortalab.
135/// Check the enum variant documentation for details on
136/// each of their individual effects.
137#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
138pub enum Joker {
139    // Basic
140    /// **+4 Mult**.
141    ///
142    /// Activates: **Independent**
143    Joker,
144
145    /// **+8 Mult** if played hand contains a **Pair**.
146    ///
147    /// Activates: **Independent**
148    JollyJoker,
149
150    /// **+12 Mult** if played hand contains a **Three of a Kind**.
151    ///
152    /// Activates: **Independent**
153    ZanyJoker,
154
155    /// **+10 Mult** if played hand contains a **Two Pair**.
156    ///
157    /// Activates: **Independent**
158    MadJoker,
159
160    /// **+12 Mult** if played hand contains a **Straight**.
161    ///
162    /// Activates: **Independent**
163    CrazyJoker,
164
165    /// **+10 Mult** if played hand contains a **Flush**.
166    ///
167    /// Activates: **Independent**
168    DrollJoker,
169
170    /// **+50 Chips** if played hand contains a **Pair**.
171    ///
172    /// Activates: **Independent**
173    SlyJoker,
174
175    /// **+100 Chips** if played hand contains a **Three of a Kind**.
176    ///
177    /// Activates: **Independent**
178    WilyJoker,
179
180    /// **+80 Chips** if played hand contains a **Two Pair**.
181    ///
182    /// Activates: **Independent**
183    CleverJoker,
184
185    /// **+100 Chips** if played hand contains a **Straight**.
186    ///
187    /// Activates: **Independent**
188    DeviousJoker,
189
190    /// **+80 Chips** if played hand contains a **Flush**.
191    ///
192    /// Activates: **Independent**
193    CraftyJoker,
194
195    // Easy
196    /// **+3 Mult** for each Joker card.
197    ///
198    /// Activates: **Independent**
199    AbstractJoker,
200
201    /// Adds double the [Rank::rank_value] of lowest ranked card held in hand to Mult.
202    ///
203    /// Activates: **On Held**
204    RaisedFist,
205
206    /// **x3 Mult** if all cards held in hand are **♠Spades** or **♣Clubs**.
207    ///
208    /// Activates: **Independent**
209    Blackboard,
210
211    // Each King held in hand gives **x1.5 Mult**.
212    ///
213    /// Activates: **On Held**
214    Baron,
215
216    // Medium
217    /// Played cards with **♦Diamond** suit give **+3 Mult** when scored.
218    ///
219    /// Activates: **On Scored**
220    GreedyJoker,
221
222    /// Played cards with **♥Heart** suit give **+3 Mult** when scored.
223    ///
224    /// Activates: **On Scored**
225    LustyJoker,
226
227    /// Played cards with **♠Spade** suit give **+3 Mult** when scored.
228    ///
229    /// Activates: **On Scored**
230    WrathfulJoker,
231
232    /// Played cards with **♣Club** suit give **+3 Mult** when scored.
233    ///
234    /// Activates: **On Scored**
235    GluttonousJoker,
236
237    /// Each played Ace, 2, 3, 5, or 8 gives **+8 Mult** when scored.
238    ///
239    /// Activates: **On Scored**
240    Fibonacci,
241
242    /// Played face cards give +30 Chips when scored.
243    ///
244    /// Activates: **On Scored**
245    ScaryFace,
246
247    /// Played cards with even rank give **+4 Mult** when scored.
248    ///
249    /// Even ranks: (10, 8, 6, 4, 2)
250    ///
251    /// Activates: **On Scored**
252    EvenSteven,
253
254    /// Played cards with odd rank give +31 Chips when scored
255    ///
256    /// Odd ranks: (A, 9, 7, 5, 3)
257    ///
258    /// Activates: **On Scored**
259    OddTodd,
260
261    /// First played face card gives **x2 Mult** when scored.
262    ///
263    /// Activates: **On Scored**
264    Photograph,
265
266    /// Played face cards give **+5 Mult** when scored.
267    ///
268    /// Activates: **On Scored**
269    SmileyFace,
270
271    /// **x3 Mult** if poker hand contains a
272    /// **♦Diamond** card, **♣Club** card, **♥Heart** card, and **♠Spade** card.
273    ///
274    /// Activates: **Independent**
275    FlowerPot,
276
277    // Hard
278    /// All Flushes and Straights can be made with 4 cards.
279    ///
280    /// Example 1: 4♦ 5♣ 6♦ 7♠ J♣ is a straight (J♣ not counted).
281    ///
282    /// Example 2: 3♦ 7♦ 9♦ A♦ is a flush.
283    ///
284    /// Example 3: 6♥ 7♥ 8♥ 9♥ is a straight flush.
285    ///
286    /// Example 4: 3♥ 4♥ 5♣ 6♥ J♥ is a straight flush,
287    /// since it contains both a straight and a flush.
288    ///
289    /// Activates: **N/A**
290    FourFingers,
291
292    /// Allows Straights to be made with gaps of 1 rank
293    ///
294    /// (e.g. 10 8 6 5 3)
295    ///
296    /// Activates: **N/A**
297    Shortcut,
298
299    /// Retrigger all card held in hand abilities.
300    ///
301    /// Activates: **On Held**
302    Mime,
303
304    /// All cards are considered face cards.
305    ///
306    /// Activates: **N/A**
307    Pareidolia,
308
309    /// Every played card counts in scoring.
310    ///
311    /// Activates: **N/A**
312    Splash,
313
314    /// Retrigger all played face cards.
315    ///
316    /// Activates: **On Scored**
317    SockAndBuskin,
318
319    /// **♥Hearts** and **♦Diamonds** count as the same suit,
320    /// **♠Spades** and **♣Clubs** count as the same suit.
321    ///
322    /// Activates: **N/A**
323    SmearedJoker,
324
325    /// Copies the ability of Joker to the right.
326    ///
327    /// Activates: **Same as copied**
328    Blueprint,
329    // Skipped: Most of them!
330}
331
332/// Editions give [`Card`]s a bonus when scoring them.
333#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
334pub enum Enhancement {
335    /// Bonus card: **+30 Chips**.
336    Bonus,
337
338    /// Mult card: **+4 Mult**.
339    Mult,
340
341    /// Wild card: Is considered to be every suit simultaneously.
342    Wild,
343
344    /// Glass card: **x2 Mult**.
345    //  Skipped: 1 in 4 chance to destroy card after all scoring is finished.
346    Glass,
347
348    /// Steel card: **x1.5 Mult** when this card stays in hand.
349    Steel,
350    // Skipped: Gold, Lucky, Stone
351}
352
353/// Editions give [`Card`]s and [`Joker`]s a bonus when scoring them.
354#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
355pub enum Edition {
356    /// **+50 Chips**.
357    Foil,
358
359    /// **+10 Mult**.
360    Holographic,
361
362    /// **x1.5 Mult**.
363    Polychrome,
364    // Skipped: Negative
365}
366
367/// Poker Hands are sets of between one and five cards that can be played in Ortalab
368/// to obtain **Chips** and **Mult** for scoring.
369///
370/// This includes all of the standard poker hands, along with three "illegal" poker hands.
371/// These hands are possible in Ortalab because players can have duplicate cards,
372/// enabling hands that aren't typically possible in poker.
373///
374/// Higher tier hands take precedence over lower tier hands regardless of their level or scoring.
375/// e.g., if your hand is K♦ K♦ K♦ K♦ 2♦, the hand will always be a Four of a Kind and never a Flush.
376///
377/// `PokerHand` is declared in ascending tier order. You can read the tier numerically as follows:
378///
379/// ```
380/// # use ortalib::PokerHand;
381///
382/// assert_eq!(PokerHand::HighCard as u8,   0);
383/// assert_eq!(PokerHand::Pair as u8,       1);
384/// // ...
385/// assert_eq!(PokerHand::Flush as u8,      5);
386/// // ...
387/// assert_eq!(PokerHand::FlushHouse as u8, 10);
388/// assert_eq!(PokerHand::FlushFive as u8,  11);
389/// ```
390///
391/// Poker hands also include a base score,
392/// which provides initial values for the round's **Chips** and **Mult**.
393/// These base scores can be accessed with [`PokerHand::hand_value`] as follows:
394///
395/// ```
396/// # use ortalib::PokerHand;
397///
398/// let (chips, mult) = PokerHand::Flush.hand_value();
399/// assert_eq!(chips, 35.0);
400/// assert_eq!(mult, 4.0);
401/// ```
402///
403/// Only the card relevant to the hand are scored. All others are unscored.
404///
405/// Example 1:
406/// You play an ace high with 4 other cards.
407/// Only the High card base amount and the aces values are used for this hands score.
408/// The other cards are ignored.
409///
410/// Example 2:
411/// You play a pair of 3s and A K Q.
412/// Only the pair cards are scored.
413/// The other cards ignored.
414#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Sequence)]
415pub enum PokerHand {
416    /// When no other hand is possible, the one highest card in your hand.
417    /// Aces are considered the highest card.
418    ///
419    /// e.g. Q♦ 9♥ A♠ 3♥ 4♠ (Q♦ 9♥ 3♥ 4♠ not counted)
420    HighCard = 0,
421
422    /// Two cards with a matching rank. Suits may differ.
423    ///
424    /// e.g. K♠ 9♠ 9♦ 6♥ 3♥ (K♠ 6♥ 3♥ not counted)
425    Pair,
426
427    /// Two cards with a matching rank, and two cards with any other matching rank.
428    /// Suits may differ.
429    ///
430    /// e.g. Q♣ Q♦ A♠ 4♦ 4♠ (A♠ not counted)
431    TwoPair,
432
433    /// Three cards with a matching rank. Suits may differ.
434    ///
435    /// e.g. 9♣ 9♦ 9♠ A♠ 3♦ (A♠ 3♦ not counted)
436    ThreeOfAKind,
437
438    /// Five cards in consecutive order which are not all from the same suit.
439    /// Aces can be counted high or low, but not both at once.
440    ///
441    /// e.g. A♠ 2♦ 3♣ 4♠ 5♥ and 10♦ J♠ Q♦ K♣ A♠ are straights,
442    ///      but Q♦ K♣ A♠ 2♣ 3♠ is not.
443    Straight,
444
445    /// Five cards of any rank, all from a single suit.
446    ///
447    /// e.g. A♥ K♥ 9♥ 5♥ 4♥
448    Flush,
449
450    /// Three cards with a matching rank, and two cards with any other matching rank,
451    /// with cards from two or more suits.
452    ///
453    /// e.g. K♥ K♦ K♣ 2♥ 2♣
454    FullHouse,
455
456    /// Four cards with a matching rank. Suits may differ.
457    ///
458    /// e.g. J♠ J♣ J♥ J♦ 3♣ (3♣ not counted)
459    FourOfAKind,
460
461    /// Five cards in consecutive order, all from a single suit.
462    ///
463    /// e.g. Q♠ J♠ 10♠ 9♠ 8♠
464    StraightFlush,
465
466    // "Illegal" poker hands
467    /// An "illegal" hand.
468    ///
469    /// Five cards with the same rank which are not all the same suit.
470    ///
471    /// e.g. A♠ A♥ A♥ A♣ A♠
472    FiveOfAKind,
473
474    /// An "illegal" hand.
475    ///
476    /// Three cards with the same rank, and two cards with the same rank,
477    /// all from a single suit.
478    ///
479    /// e.g. 7♦ 7♦ 7♦ 4♦ 4♦
480    FlushHouse,
481
482    /// An "illegal" hand.
483    ///
484    /// Five cards with the same rank and same suit.
485    ///
486    /// e.g. A♠ A♠ A♠ A♠ A♠
487    FlushFive,
488}
489
490impl Card {
491    pub fn new(
492        rank: Rank,
493        suit: Suit,
494        enhancement: Option<Enhancement>,
495        edition: Option<Edition>,
496    ) -> Self {
497        static UNIQUE_INDEX: AtomicUsize = AtomicUsize::new(0);
498
499        Self {
500            rank,
501            suit,
502            enhancement,
503            edition,
504            unique_index: UNIQUE_INDEX.fetch_add(1, Ordering::SeqCst),
505        }
506    }
507}
508
509impl Serialize for Card {
510    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
511    where
512        S: serde::Serializer,
513    {
514        serializer.serialize_str(&self.to_string())
515    }
516}
517
518impl<'de> Deserialize<'de> for Card {
519    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
520    where
521        D: serde::Deserializer<'de>,
522    {
523        struct CardVisitor;
524        impl Visitor<'_> for CardVisitor {
525            type Value = Card;
526
527            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
528                write!(f, "Card")
529            }
530
531            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
532            where
533                E: serde::de::Error,
534            {
535                v.parse().map_err(|err| serde::de::Error::custom(err))
536            }
537        }
538
539        deserializer.deserialize_str(CardVisitor)
540    }
541}
542
543impl Serialize for JokerCard {
544    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
545    where
546        S: serde::Serializer,
547    {
548        serializer.serialize_str(&self.to_string())
549    }
550}
551
552impl<'de> Deserialize<'de> for JokerCard {
553    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
554    where
555        D: serde::Deserializer<'de>,
556    {
557        struct CardVisitor;
558        impl Visitor<'_> for CardVisitor {
559            type Value = JokerCard;
560
561            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
562                write!(f, "JokerCard")
563            }
564
565            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
566            where
567                E: serde::de::Error,
568            {
569                v.parse().map_err(|err| serde::de::Error::custom(err))
570            }
571        }
572
573        deserializer.deserialize_str(CardVisitor)
574    }
575}
576
577impl Debug for Card {
578    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
579        Display::fmt(self, f)
580    }
581}
582
583impl Display for Card {
584    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
585        let Self { rank, suit, .. } = self;
586
587        write!(f, "{rank}{suit}")?;
588
589        if let Some(enhancement) = self.enhancement {
590            write!(f, " {enhancement}")?;
591        }
592
593        if let Some(edition) = self.edition {
594            write!(f, " {edition}")?;
595        }
596
597        Ok(())
598    }
599}
600
601impl FromStr for Card {
602    type Err = String;
603
604    fn from_str(s: &str) -> Result<Self, Self::Err> {
605        let mut parts = s.split_ascii_whitespace();
606
607        let rank_suit = parts.next().ok_or("Cannot parse empty string")?;
608
609        let enhancement_or_edition_str = parts.next();
610        let edition_str = parts.next();
611
612        if let Some(invalid) = parts.next() {
613            return Err(format!("Card `{s}` contains too much data: `{invalid}`"));
614        }
615
616        // Suit is always exactly 1 char
617        let mut reversed = rank_suit.chars().rev();
618        let suit_str = reversed
619            .next()
620            .ok_or_else(|| format!("Card `{s}` missing rank / suit"))?
621            .to_string();
622        let rank_str: String = reversed.rev().collect();
623
624        let rank = rank_str
625            .parse()
626            .map_err(|err| format!("Card `{s}` has invalid rank: {err}"))?;
627
628        let suit = suit_str
629            .parse()
630            .map_err(|err| format!("Card `{s}` has invalid suit: {err}"))?;
631
632        let (enhancement, edition) = {
633            let edition = edition_str
634                .map(|edition| edition.parse())
635                .transpose()
636                .map_err(|err| format!("Card `{s}` has invalid edition: {err}"))?;
637
638            if edition.is_some() {
639                let enhancement = enhancement_or_edition_str
640                    .map(|enhancement| enhancement.parse())
641                    .transpose()
642                    .map_err(|err| format!("Card `{s}` has invalid enhancement: {err}"))?;
643
644                (enhancement, edition)
645            } else if let Some(enhancement_or_edition_str) = enhancement_or_edition_str {
646                let enhancement = enhancement_or_edition_str.parse::<Enhancement>();
647                let edition = enhancement_or_edition_str.parse::<Edition>();
648
649                match (enhancement, edition) {
650                    (Ok(_), Ok(_)) => unreachable!("Enhancements and editions have distinct names"),
651                    (Ok(enhancement), _) => (Some(enhancement), None),
652                    (Err(_), Ok(edition)) => (None, Some(edition)),
653                    (Err(_), Err(_)) => {
654                        return Err(format!(
655                            "Card `{s}` has invalid enhancement / edition: {enhancement_or_edition_str}"
656                        ));
657                    }
658                }
659            } else {
660                (None, None)
661            }
662        };
663
664        Ok(Card::new(rank, suit, enhancement, edition))
665    }
666}
667
668impl JokerCard {
669    pub fn new(joker: Joker, edition: Option<Edition>) -> Self {
670        static UNIQUE_INDEX: AtomicUsize = AtomicUsize::new(0);
671
672        Self {
673            joker,
674            edition,
675            unique_index: UNIQUE_INDEX.fetch_add(1, Ordering::SeqCst),
676        }
677    }
678}
679
680impl Debug for JokerCard {
681    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
682        Display::fmt(self, f)
683    }
684}
685
686impl Display for JokerCard {
687    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
688        let joker = self.joker;
689
690        write!(f, "{joker}")?;
691
692        if let Some(edition) = self.edition {
693            write!(f, " {edition}")?;
694        }
695
696        Ok(())
697    }
698}
699
700impl FromStr for JokerCard {
701    type Err = String;
702
703    fn from_str(s: &str) -> Result<Self, Self::Err> {
704        let mut joker_str = s;
705        let mut edition = None;
706
707        for possible_edition in all::<Edition>() {
708            if let Some(leftover) = s.strip_suffix(&possible_edition.to_string()) {
709                joker_str = leftover.trim();
710                edition = Some(possible_edition);
711
712                break;
713            }
714        }
715
716        let joker = joker_str
717            .parse()
718            .map_err(|err| format!("Invalid JokerCard `{s}`: {err}"))?;
719
720        Ok(JokerCard::new(joker, edition))
721    }
722}
723
724impl Debug for Joker {
725    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
726        Display::fmt(self, f)
727    }
728}
729
730impl Display for Joker {
731    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
732        use crate::Joker::*;
733
734        #[rustfmt::skip]
735        let name = match self {
736            Joker =>           "Joker",
737            JollyJoker =>      "Jolly Joker",
738            ZanyJoker =>       "Zany Joker",
739            MadJoker =>        "Mad Joker",
740            CrazyJoker =>      "Crazy Joker",
741            DrollJoker =>      "Droll Joker",
742            SlyJoker =>        "Sly Joker",
743            WilyJoker =>       "Wily Joker",
744            CleverJoker =>     "Clever Joker",
745            DeviousJoker =>    "Devious Joker",
746            CraftyJoker =>     "Crafty Joker",
747            AbstractJoker =>   "Abstract Joker",
748            RaisedFist =>      "Raised Fist",
749            Blackboard =>      "Blackboard",
750            Baron =>           "Baron",
751            GreedyJoker =>     "Greedy Joker",
752            LustyJoker =>      "Lusty Joker",
753            WrathfulJoker =>   "Wrathful Joker",
754            GluttonousJoker => "Gluttonous Joker",
755            Fibonacci =>       "Fibonacci",
756            ScaryFace =>       "Scary Face",
757            EvenSteven =>      "Even Steven",
758            OddTodd =>         "Odd Todd",
759            Photograph =>      "Photograph",
760            SmileyFace =>      "Smiley Face",
761            FlowerPot =>       "Flower Pot",
762            FourFingers =>     "Four Fingers",
763            Shortcut =>        "Shortcut",
764            Mime =>            "Mime",
765            Pareidolia =>      "Pareidolia",
766            Splash =>          "Splash",
767            SockAndBuskin =>   "Sock And Buskin",
768            SmearedJoker =>    "Smeared Joker",
769            Blueprint =>       "Blueprint",
770        };
771
772        write!(f, "{name}")
773    }
774}
775
776impl FromStr for Joker {
777    type Err = String;
778
779    fn from_str(s: &str) -> Result<Self, Self::Err> {
780        use crate::Joker::*;
781
782        #[rustfmt::skip]
783        let value = match s {
784            "Joker" =>            Joker,
785            "Jolly Joker" =>      JollyJoker,
786            "Zany Joker" =>       ZanyJoker,
787            "Mad Joker" =>        MadJoker,
788            "Crazy Joker" =>      CrazyJoker,
789            "Droll Joker" =>      DrollJoker,
790            "Sly Joker" =>        SlyJoker,
791            "Wily Joker" =>       WilyJoker,
792            "Clever Joker" =>     CleverJoker,
793            "Devious Joker" =>    DeviousJoker,
794            "Crafty Joker" =>     CraftyJoker,
795            "Abstract Joker" =>   AbstractJoker,
796            "Raised Fist" =>      RaisedFist,
797            "Blackboard" =>       Blackboard,
798            "Baron" =>            Baron,
799            "Greedy Joker" =>     GreedyJoker,
800            "Lusty Joker" =>      LustyJoker,
801            "Wrathful Joker" =>   WrathfulJoker,
802            "Gluttonous Joker" => GluttonousJoker,
803            "Fibonacci" =>        Fibonacci,
804            "Scary Face" =>       ScaryFace,
805            "Even Steven" =>      EvenSteven,
806            "Odd Todd" =>         OddTodd,
807            "Photograph" =>       Photograph,
808            "Smiley Face" =>      SmileyFace,
809            "Flower Pot" =>       FlowerPot,
810            "Four Fingers" =>     FourFingers,
811            "Shortcut" =>         Shortcut,
812            "Mime" =>             Mime,
813            "Pareidolia" =>       Pareidolia,
814            "Splash" =>           Splash,
815            "Sock And Buskin" =>  SockAndBuskin,
816            "Smeared Joker" =>    SmearedJoker,
817            "Blueprint" =>        Blueprint,
818            _ => return Err(format!("Invalid Joker: `{s}`")),
819        };
820
821        Ok(value)
822    }
823}
824
825impl Debug for Rank {
826    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
827        Display::fmt(self, f)
828    }
829}
830
831impl Display for Rank {
832    #[rustfmt::skip]
833    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
834        use Rank::*;
835
836        write!(f, "{}", match *self {
837            Two =>   "2",
838            Three => "3",
839            Four =>  "4",
840            Five =>  "5",
841            Six =>   "6",
842            Seven => "7",
843            Eight => "8",
844            Nine =>  "9",
845            Ten =>   "10",
846            Jack =>  "J",
847            Queen => "Q",
848            King =>  "K",
849            Ace =>   "A",
850        })
851    }
852}
853
854impl FromStr for Rank {
855    type Err = String;
856
857    fn from_str(s: &str) -> Result<Self, Self::Err> {
858        use Rank::*;
859
860        #[rustfmt::skip]
861        let value = match s {
862            "2" =>  Two,
863            "3" =>  Three,
864            "4" =>  Four,
865            "5" =>  Five,
866            "6" =>  Six,
867            "7" =>  Seven,
868            "8" =>  Eight,
869            "9" =>  Nine,
870            "10" => Ten,
871            "J" =>  Jack,
872            "Q" =>  Queen,
873            "K" =>  King,
874            "A" =>  Ace,
875            _ => return Err(format!("Invalid Rank: `{s}`")),
876        };
877
878        Ok(value)
879    }
880}
881
882impl Debug for Suit {
883    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
884        Display::fmt(self, f)
885    }
886}
887
888impl Display for Suit {
889    #[rustfmt::skip]
890    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
891        use Suit::*;
892
893        write!(f, "{}", match *self {
894            Spades =>   "♠",
895            Hearts =>   "♥",
896            Clubs =>    "♣",
897            Diamonds => "♦",
898        })
899    }
900}
901
902impl FromStr for Suit {
903    type Err = String;
904
905    fn from_str(s: &str) -> Result<Self, Self::Err> {
906        use Suit::*;
907
908        let value = match s {
909            "♠" => Spades,
910            "♥" => Hearts,
911            "♣" => Clubs,
912            "♦" => Diamonds,
913            _ => return Err(format!("Invalid Suit: `{s}`")),
914        };
915
916        Ok(value)
917    }
918}
919
920impl Debug for Enhancement {
921    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
922        Display::fmt(self, f)
923    }
924}
925
926impl Display for Enhancement {
927    #[rustfmt::skip]
928    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
929        use Enhancement::*;
930
931        write!(f, "{}", match *self {
932            Bonus => "Bonus",
933            Mult =>  "Mult",
934            Wild =>  "Wild",
935            Glass => "Glass",
936            Steel => "Steel",
937        })
938    }
939}
940
941impl FromStr for Enhancement {
942    type Err = String;
943
944    fn from_str(s: &str) -> Result<Self, Self::Err> {
945        use Enhancement::*;
946
947        #[rustfmt::skip]
948        let value = match s {
949            "Bonus" => Bonus,
950            "Mult" =>  Mult,
951            "Wild" =>  Wild,
952            "Glass" => Glass,
953            "Steel" => Steel,
954            _ => return Err(format!("Invalid Enhancement: `{s}`")),
955        };
956
957        Ok(value)
958    }
959}
960
961impl Debug for Edition {
962    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
963        Display::fmt(self, f)
964    }
965}
966
967impl Display for Edition {
968    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
969        use Edition::*;
970
971        write!(
972            f,
973            "{}",
974            match *self {
975                Foil => "Foil",
976                Holographic => "Holographic",
977                Polychrome => "Polychrome",
978            }
979        )
980    }
981}
982
983impl FromStr for Edition {
984    type Err = String;
985
986    fn from_str(s: &str) -> Result<Self, Self::Err> {
987        use Edition::*;
988
989        let value = match s {
990            "Foil" => Foil,
991            "Holographic" => Holographic,
992            "Polychrome" => Polychrome,
993            _ => return Err(format!("Invalid Edition: `{s}`")),
994        };
995
996        Ok(value)
997    }
998}
999
1000impl Debug for PokerHand {
1001    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1002        Display::fmt(&self, f)
1003    }
1004}
1005
1006impl Display for PokerHand {
1007    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1008        use PokerHand::*;
1009
1010        write!(
1011            f,
1012            "{}",
1013            match self {
1014                HighCard => "High Card",
1015                Pair => "Pair",
1016                TwoPair => "Two Pair",
1017                ThreeOfAKind => "Three Of A Kind",
1018                Straight => "Straight",
1019                Flush => "Flush",
1020                FullHouse => "Full House",
1021                FourOfAKind => "Four Of A Kind",
1022                StraightFlush => "Straight Flush",
1023                FiveOfAKind => "Five Of A Kind",
1024                FlushHouse => "Flush House",
1025                FlushFive => "Flush Five",
1026            }
1027        )
1028    }
1029}
1030
1031impl Rank {
1032    pub fn rank_value(&self) -> Chips {
1033        use Rank::*;
1034
1035        let value = match *self {
1036            Two => 2,
1037            Three => 3,
1038            Four => 4,
1039            Five => 5,
1040            Six => 6,
1041            Seven => 7,
1042            Eight => 8,
1043            Nine => 9,
1044            Ten | Jack | Queen | King => 10,
1045            Ace => 11,
1046        };
1047
1048        value.into()
1049    }
1050
1051    /// Returns true if the rank is a face card (Jack, Queen, or King).
1052    pub fn is_face(&self) -> bool {
1053        use Rank::*;
1054        matches!(self, Jack | Queen | King)
1055    }
1056}
1057
1058impl PokerHand {
1059    pub fn hand_value(&self) -> (Chips, Mult) {
1060        use PokerHand::*;
1061
1062        #[rustfmt::skip]
1063        let (chips, mult) = match *self {
1064            HighCard =>      (5,   1),
1065            Pair =>          (10,  2),
1066            TwoPair =>       (20,  2),
1067            ThreeOfAKind =>  (30,  3),
1068            Straight =>      (30,  4),
1069            Flush =>         (35,  4),
1070            FullHouse =>     (40,  4),
1071            FourOfAKind =>   (60,  7),
1072            StraightFlush => (100, 8),
1073            FiveOfAKind =>   (120, 12),
1074            FlushHouse =>    (140, 14),
1075            FlushFive =>     (160, 16),
1076        };
1077
1078        (chips.into(), mult.into())
1079    }
1080}
1081
1082impl Suit {
1083    pub fn color(&self) -> SuitColor {
1084        use Suit::*;
1085        use SuitColor::*;
1086
1087        match *self {
1088            Spades | Clubs => Black,
1089            Hearts | Diamonds => Red,
1090        }
1091    }
1092
1093    pub fn other_suit_of_same_color(&self) -> Suit {
1094        use Suit::*;
1095
1096        match *self {
1097            Spades => Clubs,
1098            Hearts => Diamonds,
1099            Clubs => Spades,
1100            Diamonds => Hearts,
1101        }
1102    }
1103}
1104
1105impl SuitColor {
1106    pub fn other_color(&self) -> SuitColor {
1107        use SuitColor::*;
1108
1109        match *self {
1110            Black => Red,
1111            Red => Black,
1112        }
1113    }
1114}
1115
1116#[cfg(test)]
1117mod tests {
1118    use crate::*;
1119    use enum_iterator::all;
1120
1121    #[test]
1122    fn jokers_to_string_parse() {
1123        for joker in all::<Joker>() {
1124            assert_eq!(Ok(joker), joker.to_string().parse())
1125        }
1126    }
1127
1128    #[test]
1129    fn suits_to_string_parse() {
1130        for suit in all::<Suit>() {
1131            assert_eq!(Ok(suit), suit.to_string().parse())
1132        }
1133    }
1134
1135    #[test]
1136    fn ranks_to_string_parse() {
1137        for rank in all::<Rank>() {
1138            assert_eq!(Ok(rank), rank.to_string().parse())
1139        }
1140    }
1141
1142    #[test]
1143    fn rank_is_face() {
1144        use Rank::*;
1145
1146        // Test face cards
1147        let face_cards = [Jack, Queen, King];
1148        for card in face_cards {
1149            assert!(card.is_face(), "{card:?} should be a face card");
1150        }
1151
1152        // Test non-face cards
1153        let non_face_cards = [Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten];
1154        for card in non_face_cards {
1155            assert!(!card.is_face(), "{card:?} should not be a face card");
1156        }
1157    }
1158
1159    #[test]
1160    fn enhancements_to_string_parse() {
1161        for enhancement in all::<Enhancement>() {
1162            assert_eq!(Ok(enhancement), enhancement.to_string().parse())
1163        }
1164    }
1165
1166    #[test]
1167    fn editions_to_string_parse() {
1168        for edition in all::<Edition>() {
1169            assert_eq!(Ok(edition), edition.to_string().parse())
1170        }
1171    }
1172
1173    #[test]
1174    fn serialize_round() {
1175        use crate::Joker::*;
1176        use Edition::*;
1177        use Enhancement::*;
1178        use Rank::*;
1179        use Suit::*;
1180
1181        #[rustfmt::skip]
1182        let round = Round {
1183            cards_played: vec![
1184                Card::new(Ace,   Hearts,   None,        None),
1185                Card::new(Queen, Clubs,    Some(Bonus), None),
1186                Card::new(Ten,   Spades,   None,        Some(Holographic)),
1187                Card::new(Seven, Diamonds, Some(Glass), Some(Polychrome)),
1188            ],
1189            cards_held_in_hand: vec![
1190                Card::new(Ace, Clubs, None, None),
1191            ],
1192            jokers: vec![
1193                JokerCard::new(Joker,         None),
1194                JokerCard::new(Blueprint,     Some(Foil)),
1195                JokerCard::new(Photograph,    Some(Holographic)),
1196                JokerCard::new(SockAndBuskin, Some(Polychrome)),
1197                JokerCard::new(FlowerPot,     Some(Polychrome)),
1198            ],
1199        };
1200
1201        let serialized = serde_yaml::to_string(&round);
1202        assert!(serialized.is_ok(), "{serialized:?}");
1203        let serialized = serialized.unwrap();
1204
1205        let expected = r#"
1206cards_played:
1207- A♥
1208- Q♣ Bonus
1209- 10♠ Holographic
1210- 7♦ Glass Polychrome
1211cards_held_in_hand:
1212- A♣
1213jokers:
1214- Joker
1215- Blueprint Foil
1216- Photograph Holographic
1217- Sock And Buskin Polychrome
1218- Flower Pot Polychrome
1219"#
1220        .trim_start();
1221
1222        assert_eq!(serialized, expected);
1223    }
1224
1225    #[test]
1226    fn deserialize_round() {
1227        use crate::Joker::*;
1228        use Edition::*;
1229        use Enhancement::*;
1230        use Rank::*;
1231        use Suit::*;
1232
1233        let serialized = r#"
1234cards_played:
1235- A♥
1236- Q♣ Bonus
1237- 10♠ Holographic
1238- 7♦ Glass Polychrome
1239cards_held_in_hand:
1240- A♣
1241jokers:
1242- Joker
1243- Blueprint Foil
1244- Photograph Holographic
1245- Sock And Buskin Polychrome
1246- Flower Pot Polychrome
1247"#
1248        .trim_start();
1249
1250        #[rustfmt::skip]
1251        #[allow(unused)]
1252        let expected = Round {
1253            cards_played: vec![
1254                Card::new(Ace,   Hearts,   None,        None),
1255                Card::new(Queen, Clubs,    Some(Bonus), None),
1256                Card::new(Ten,   Spades,   None,        Some(Holographic)),
1257                Card::new(Seven, Diamonds, Some(Glass), Some(Polychrome)),
1258            ],
1259            cards_held_in_hand: vec![
1260                Card::new(Ace, Clubs, None, None),
1261            ],
1262            jokers: vec![
1263                JokerCard::new(Joker,         None),
1264                JokerCard::new(Blueprint,     Some(Foil)),
1265                JokerCard::new(Photograph,    Some(Holographic)),
1266                JokerCard::new(SockAndBuskin, Some(Polychrome)),
1267                JokerCard::new(FlowerPot,     Some(Polychrome)),
1268            ],
1269        };
1270
1271        let deserialized = serde_yaml::from_str::<Round>(serialized);
1272        assert!(deserialized.is_ok(), "{deserialized:?}");
1273        let deserialized = deserialized.unwrap();
1274
1275        let reserialized = serde_yaml::to_string(&deserialized);
1276        assert!(reserialized.is_ok(), "{reserialized:?}");
1277        let reserialized = reserialized.unwrap();
1278
1279        assert_eq!(serialized, reserialized);
1280    }
1281
1282    #[test]
1283    fn test_card_serialization() {
1284        let card = Card::new(
1285            Rank::Ace,
1286            Suit::Hearts,
1287            Some(Enhancement::Bonus),
1288            Some(Edition::Foil),
1289        );
1290        let serialized = serde_yaml::to_string(&card).unwrap();
1291        assert_eq!(serialized, "A♥ Bonus Foil\n");
1292    }
1293
1294    #[test]
1295    fn test_card_deserialization() {
1296        let serialized = "A♥ Bonus Foil";
1297        let card: Card = serde_yaml::from_str(serialized).unwrap();
1298        let expected = Card::new(
1299            Rank::Ace,
1300            Suit::Hearts,
1301            Some(Enhancement::Bonus),
1302            Some(Edition::Foil),
1303        );
1304        assert_eq!(
1305            format!("{card:?}").trim(),
1306            format!("{expected:?}").trim(),
1307            "Deserialization failed: expected {:?}, got {:?}",
1308            expected,
1309            card
1310        );
1311    }
1312
1313    #[test]
1314    fn test_joker_card_serialization() {
1315        let joker_card = JokerCard::new(Joker::JollyJoker, Some(Edition::Holographic));
1316        let serialized = serde_yaml::to_string(&joker_card).unwrap();
1317        assert_eq!(serialized, "Jolly Joker Holographic\n");
1318    }
1319
1320    #[test]
1321    fn test_joker_card_deserialization() {
1322        let serialized = "Jolly Joker Holographic";
1323        let joker_card: JokerCard = serde_yaml::from_str(serialized).unwrap();
1324        let expected = JokerCard::new(Joker::JollyJoker, Some(Edition::Holographic));
1325        assert_eq!(
1326            format!("{joker_card:?}").trim(),
1327            format!("{expected:?}").trim(),
1328            "Deserialization failed: expected {:?}, got {:?}",
1329            expected,
1330            joker_card
1331        );
1332    }
1333
1334    #[test]
1335    fn test_round_trip_card() {
1336        let card = Card::new(Rank::King, Suit::Diamonds, None, None);
1337        let serialized = serde_yaml::to_string(&card).unwrap();
1338        let deserialized: Card = serde_yaml::from_str(&serialized).unwrap();
1339        assert_eq!(
1340            format!("{card:?}").trim(),
1341            format!("{deserialized:?}").trim(),
1342            "Round-trip failed: expected {:?}, got {:?}",
1343            card,
1344            deserialized
1345        );
1346    }
1347
1348    #[test]
1349    fn test_round_trip_joker_card() {
1350        let joker_card = JokerCard::new(Joker::CraftyJoker, None);
1351        let serialized = serde_yaml::to_string(&joker_card).unwrap();
1352        let deserialized: JokerCard = serde_yaml::from_str(&serialized).unwrap();
1353        assert_eq!(
1354            format!("{joker_card:?}").trim(),
1355            format!("{deserialized:?}").trim(),
1356            "Round-trip failed: expected {:?}, got {:?}",
1357            joker_card,
1358            deserialized
1359        );
1360    }
1361
1362    #[test]
1363    fn test_invalid_card_deserialization() {
1364        let serialized = "Invalid Card";
1365        let result: Result<Card, _> = serde_yaml::from_str(serialized);
1366        assert!(result.is_err());
1367    }
1368
1369    #[test]
1370    fn test_invalid_joker_card_deserialization() {
1371        let serialized = "Invalid JokerCard";
1372        let result: Result<JokerCard, _> = serde_yaml::from_str(serialized);
1373        assert!(result.is_err());
1374    }
1375
1376    #[test]
1377    fn test_card_serialization_various_cases() {
1378        let cases = vec![
1379            (Rank::Two, Suit::Hearts, None, None, "2♥\n"),
1380            (
1381                Rank::Three,
1382                Suit::Clubs,
1383                Some(Enhancement::Mult),
1384                None,
1385                "3♣ Mult\n",
1386            ),
1387            (
1388                Rank::Four,
1389                Suit::Diamonds,
1390                None,
1391                Some(Edition::Foil),
1392                "4♦ Foil\n",
1393            ),
1394            (
1395                Rank::Five,
1396                Suit::Spades,
1397                Some(Enhancement::Bonus),
1398                Some(Edition::Holographic),
1399                "5♠ Bonus Holographic\n",
1400            ),
1401            (
1402                Rank::Ace,
1403                Suit::Hearts,
1404                Some(Enhancement::Wild),
1405                Some(Edition::Polychrome),
1406                "A♥ Wild Polychrome\n",
1407            ),
1408        ];
1409
1410        for (rank, suit, enhancement, edition, expected) in cases {
1411            let card = Card::new(rank, suit, enhancement, edition);
1412            let serialized = serde_yaml::to_string(&card).unwrap();
1413            assert_eq!(serialized, expected);
1414        }
1415    }
1416
1417    #[test]
1418    fn test_card_deserialization_various_cases() {
1419        let cases = vec![
1420            ("2♥", Card::new(Rank::Two, Suit::Hearts, None, None)),
1421            (
1422                "3♣ Mult",
1423                Card::new(Rank::Three, Suit::Clubs, Some(Enhancement::Mult), None),
1424            ),
1425            (
1426                "4♦ Foil",
1427                Card::new(Rank::Four, Suit::Diamonds, None, Some(Edition::Foil)),
1428            ),
1429            (
1430                "5♠ Bonus Holographic",
1431                Card::new(
1432                    Rank::Five,
1433                    Suit::Spades,
1434                    Some(Enhancement::Bonus),
1435                    Some(Edition::Holographic),
1436                ),
1437            ),
1438            (
1439                "A♥ Steel Polychrome",
1440                Card::new(
1441                    Rank::Ace,
1442                    Suit::Hearts,
1443                    Some(Enhancement::Steel),
1444                    Some(Edition::Polychrome),
1445                ),
1446            ),
1447        ];
1448
1449        for (serialized, expected_card) in cases {
1450            let card: Card = serde_yaml::from_str(serialized).unwrap();
1451            assert_eq!(
1452                format!("{card:?}").trim(),
1453                format!("{expected_card:?}").trim(),
1454                "Deserialization failed for {}: expected {:?}, got {:?}",
1455                serialized,
1456                expected_card,
1457                card
1458            );
1459        }
1460    }
1461
1462    #[test]
1463    fn test_joker_card_serialization_various_cases() {
1464        let cases = vec![
1465            (Joker::Joker, None, "Joker\n"),
1466            (Joker::JollyJoker, Some(Edition::Foil), "Jolly Joker Foil\n"),
1467            (
1468                Joker::ZanyJoker,
1469                Some(Edition::Holographic),
1470                "Zany Joker Holographic\n",
1471            ),
1472            (
1473                Joker::MadJoker,
1474                Some(Edition::Polychrome),
1475                "Mad Joker Polychrome\n",
1476            ),
1477            (Joker::CrazyJoker, None, "Crazy Joker\n"),
1478        ];
1479
1480        for (joker, edition, expected) in cases {
1481            let joker_card = JokerCard::new(joker, edition);
1482            let serialized = serde_yaml::to_string(&joker_card).unwrap();
1483            assert_eq!(serialized, expected);
1484        }
1485    }
1486
1487    #[test]
1488    fn test_joker_card_deserialization_various_cases() {
1489        let cases = vec![
1490            ("Joker", JokerCard::new(Joker::Joker, None)),
1491            (
1492                "Jolly Joker Foil",
1493                JokerCard::new(Joker::JollyJoker, Some(Edition::Foil)),
1494            ),
1495            (
1496                "Zany Joker Holographic",
1497                JokerCard::new(Joker::ZanyJoker, Some(Edition::Holographic)),
1498            ),
1499            (
1500                "Mad Joker Polychrome",
1501                JokerCard::new(Joker::MadJoker, Some(Edition::Polychrome)),
1502            ),
1503            ("Crazy Joker", JokerCard::new(Joker::CrazyJoker, None)),
1504        ];
1505
1506        for (serialized, expected_joker_card) in cases {
1507            let joker_card: JokerCard = serde_yaml::from_str(serialized).unwrap();
1508            assert_eq!(
1509                format!("{joker_card:?}").trim(),
1510                format!("{expected_joker_card:?}").trim(),
1511                "Deserialization failed for {}: expected {:?}, got {:?}",
1512                serialized,
1513                expected_joker_card,
1514                joker_card
1515            );
1516        }
1517    }
1518
1519    #[test]
1520    fn test_invalid_card_deserialization_cases() {
1521        let invalid_cases = vec![
1522            "Invalid Card",
1523            "2X",
1524            "3♣ Unknown",
1525            "4♦ Mult Extra",
1526            "5♠ Bonus Holographic Extra",
1527        ];
1528
1529        for serialized in invalid_cases {
1530            let result: Result<Card, _> = serde_yaml::from_str(serialized);
1531            assert!(result.is_err());
1532        }
1533    }
1534
1535    #[test]
1536    fn test_invalid_joker_card_deserialization_cases() {
1537        let invalid_cases = vec![
1538            "Invalid JokerCard",
1539            "Jolly Joker Unknown",
1540            "Zany Joker Holographic Extra",
1541            "Mad Joker Polychrome Extra",
1542        ];
1543
1544        for serialized in invalid_cases {
1545            let result: Result<JokerCard, _> = serde_yaml::from_str(serialized);
1546            assert!(result.is_err());
1547        }
1548    }
1549}