fudd/types/
playing_cards.rs

1use crate::analysis::eval::Eval;
2use crate::types::arrays::five_card::FiveCard;
3use crate::types::arrays::seven_card::SevenCard;
4use crate::types::arrays::two_card::TwoCard;
5use crate::types::arrays::{Evaluable, Vectorable};
6use crate::types::playing_card::PlayingCard;
7use crate::types::poker_cards::PokerCards;
8use crate::types::poker_deck::PokerDeck;
9use crate::types::U32Card;
10use crate::util::random_ordering::RandomOrdering;
11use cardpack::Pile;
12use ckc_rs::{HandError, PokerCard};
13use core::fmt;
14use indexmap::set::Iter;
15use indexmap::IndexSet;
16use itertools::{Combinations, Itertools};
17use rayon::prelude::*;
18use std::fmt::Formatter;
19
20const NUMBER_OF_SHUFFLES: u8 = 5;
21
22#[derive(Clone, Debug, Default, PartialEq)]
23pub struct PlayingCards(IndexSet<PlayingCard>);
24
25impl PlayingCards {
26    #[must_use]
27    pub fn deck() -> PlayingCards {
28        let set: Vec<PlayingCard> = PokerDeck::par_iter().map(PlayingCard::from).collect();
29        PlayingCards::from(set)
30    }
31
32    /// Returns all the cards in a `PokerDeck` minus the ones passed in.
33    ///
34    /// TODO: :-P
35    #[must_use]
36    pub fn deck_minus(playing_cards: &PlayingCards) -> PlayingCards {
37        let mut cards = PlayingCards::default();
38        let deck = PlayingCards::deck();
39        for card in deck.iter() {
40            if playing_cards.get(card).is_none() {
41                cards.insert(*card);
42            }
43        }
44        cards
45    }
46
47    #[must_use]
48    pub fn deck_shuffled() -> PlayingCards {
49        let mut deck = PlayingCards(PokerDeck::iter().map(PlayingCard::from).collect());
50        deck.shuffle_in_place();
51        deck
52    }
53
54    pub fn append(&mut self, playing_cards: &PlayingCards) {
55        for card in playing_cards.iter() {
56            self.insert(*card);
57        }
58    }
59
60    pub fn combinations(&self, k: usize) -> Combinations<Iter<'_, PlayingCard>> {
61        self.iter().combinations(k)
62    }
63
64    #[must_use]
65    pub fn combine(&self, playing_cards: &PlayingCards) -> PlayingCards {
66        let mut cards = self.clone();
67        cards.append(playing_cards);
68        cards
69    }
70
71    #[must_use]
72    pub fn contains(&self, playing_card: &PlayingCard) -> bool {
73        self.0.contains(playing_card)
74    }
75
76    pub fn deal_from_the_bottom(&mut self) -> Option<PlayingCard> {
77        self.0.pop()
78    }
79
80    #[must_use]
81    pub fn draw(&mut self, number: usize) -> PlayingCards {
82        PlayingCards(self.0.drain(0..number).collect())
83    }
84
85    #[must_use]
86    pub fn draw_from_the_bottom(&mut self, number: usize) -> PlayingCards {
87        let l = self.len();
88        PlayingCards(self.0.drain(l - number..l).collect())
89    }
90
91    /// # Panics
92    ///
93    /// Will panic if the entity is empty.
94    pub fn draw_one(&mut self) -> PlayingCard {
95        self.draw(1).deal_from_the_bottom().unwrap()
96    }
97
98    /// # Errors
99    ///
100    /// Will throw a `HandError` if there are not exactly 7 cards.
101    #[allow(clippy::unnecessary_unwrap)]
102    pub fn eval_7cards(&self) -> Result<Eval, HandError> {
103        match self.to_seven_array() {
104            Ok(array) => Ok(array.eval()),
105            Err(e) => Err(e),
106        }
107    }
108
109    #[must_use]
110    pub fn get(&self, playing_card: &PlayingCard) -> Option<&PlayingCard> {
111        self.0.get(playing_card)
112    }
113
114    #[must_use]
115    pub fn get_index(&self, index: usize) -> Option<&PlayingCard> {
116        self.0.get_index(index)
117    }
118
119    /// Allows you to insert a `PlayingCard` provided it isn't blank.
120    pub fn insert(&mut self, playing_card: PlayingCard) -> bool {
121        if playing_card.is_blank() {
122            false
123        } else {
124            self.0.insert(playing_card)
125        }
126    }
127
128    #[must_use]
129    pub fn is_disjoint(&self, other: &PlayingCards) -> bool {
130        self.0.is_disjoint(&other.0)
131    }
132
133    #[must_use]
134    pub fn is_empty(&self) -> bool {
135        self.len() == 0
136    }
137
138    #[must_use]
139    pub fn is_subset(&self, other: &PlayingCards) -> bool {
140        self.0.is_subset(&other.0)
141    }
142
143    #[must_use]
144    pub fn is_superset(&self, other: &PlayingCards) -> bool {
145        self.0.is_superset(&other.0)
146    }
147
148    #[must_use]
149    pub fn iter(&self) -> indexmap::set::Iter<'_, PlayingCard> {
150        self.0.iter()
151    }
152
153    #[must_use]
154    pub fn len(&self) -> usize {
155        self.0.len()
156    }
157
158    #[must_use]
159    pub fn peak(&self) -> Option<&PlayingCard> {
160        self.0.first()
161    }
162
163    pub fn reverse(&mut self) {
164        self.0.reverse();
165    }
166
167    #[must_use]
168    pub fn shuffle(&self) -> PlayingCards {
169        let mut shuffled = self.clone();
170        shuffled.shuffle_in_place();
171        shuffled
172    }
173
174    pub fn shuffle_in_place(&mut self) {
175        for _ in 0..NUMBER_OF_SHUFFLES {
176            self.0
177                .sort_by(|_, _| rand::random::<RandomOrdering>().into());
178        }
179    }
180
181    #[must_use]
182    pub fn sort(&self) -> PlayingCards {
183        let mut c = self.clone();
184        c.sort_in_place();
185        c
186    }
187
188    pub fn sort_in_place(&mut self) {
189        self.0.sort();
190        self.0.reverse();
191    }
192
193    /// # Errors
194    ///
195    /// Throws `HandError` if not exactly 5 cards.
196    #[allow(clippy::missing_panics_doc)]
197    pub fn to_five_array(&self) -> Result<[PlayingCard; 5], HandError> {
198        match self.len() {
199            0..=4 => Err(HandError::NotEnoughCards),
200            5 => Ok([
201                *self.get_index(0).unwrap(),
202                *self.get_index(1).unwrap(),
203                *self.get_index(2).unwrap(),
204                *self.get_index(3).unwrap(),
205                *self.get_index(4).unwrap(),
206            ]),
207            _ => Err(HandError::TooManyCards),
208        }
209    }
210
211    /// # Errors
212    ///
213    /// Will throw a `HandError` if there are not exactly 5 cards.
214    pub fn to_five_cards(&self) -> Result<FiveCard, HandError> {
215        match self.to_five_array() {
216            Ok(hand) => Ok(FiveCard::from(hand)),
217            Err(e) => Err(e),
218        }
219    }
220
221    /// # Errors
222    ///
223    /// Will throw a `HandError` if there are not exactly 7 cards.
224    #[allow(clippy::missing_panics_doc)]
225    pub fn to_seven_array(&self) -> Result<SevenCard, HandError> {
226        SevenCard::try_from(self)
227    }
228
229    #[must_use]
230    pub fn to_vec(&self) -> Vec<PlayingCard> {
231        self.iter().copied().collect::<Vec<PlayingCard>>()
232    }
233
234    #[must_use]
235    pub fn two_cards(&self) -> Vec<TwoCard> {
236        self.combinations(2)
237            .map(TwoCard::from)
238            .collect::<Vec<TwoCard>>()
239    }
240}
241
242impl fmt::Display for PlayingCards {
243    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244        let s = self
245            .iter()
246            .map(PlayingCard::to_string)
247            .collect::<Vec<String>>()
248            .join(" ");
249
250        write!(f, "{s}")
251    }
252}
253
254impl indexmap::Equivalent<PlayingCard> for IndexSet<PlayingCard> {
255    fn equivalent(&self, key: &PlayingCard) -> bool {
256        self.get(key).is_some()
257    }
258}
259
260impl From<&Pile> for PlayingCards {
261    /// Returns a `PlayingCardSet` from a `Pile` filtering out any `cardpack::Card` that
262    /// isn't a valid, non blank `PlayingCard`.
263    ///
264    /// Idea from: <https://stackoverflow.com/a/63146008/1245251/>
265    fn from(pile: &Pile) -> Self {
266        let filtered = pile.clone().into_iter().filter_map(|c| {
267            let pc = PlayingCard::from(&c);
268            if pc.is_blank() {
269                None
270            } else {
271                Some(pc)
272            }
273        });
274        PlayingCards(filtered.collect())
275    }
276}
277
278impl From<&PokerCards> for PlayingCards {
279    fn from(poker_cards: &PokerCards) -> Self {
280        PlayingCards::from(&poker_cards.to_vec())
281    }
282}
283
284impl From<&Vec<U32Card>> for PlayingCards {
285    fn from(v: &Vec<U32Card>) -> Self {
286        let filtered = v.iter().filter_map(|c| {
287            let pc = PlayingCard::from(*c);
288            if pc.is_blank() {
289                None
290            } else {
291                Some(pc)
292            }
293        });
294        PlayingCards(filtered.collect())
295    }
296}
297
298impl From<Vec<&PlayingCard>> for PlayingCards {
299    fn from(v: Vec<&PlayingCard>) -> Self {
300        let filtered = v.iter().filter_map(|c| {
301            let pc = **c;
302            if pc.is_blank() {
303                None
304            } else {
305                Some(pc)
306            }
307        });
308        PlayingCards(filtered.collect())
309    }
310}
311
312impl From<PlayingCard> for PlayingCards {
313    fn from(playing_card: PlayingCard) -> Self {
314        PlayingCards::from(vec![playing_card])
315    }
316}
317
318impl From<Vec<PlayingCard>> for PlayingCards {
319    fn from(value: Vec<PlayingCard>) -> Self {
320        PlayingCards(value.into_iter().collect::<IndexSet<_>>())
321    }
322}
323
324impl TryFrom<&'static str> for PlayingCards {
325    type Error = HandError;
326
327    /// # Errors
328    ///
329    /// Will return `CardError::InvalidCard` for an invalid index.
330    #[allow(clippy::missing_panics_doc)]
331    fn try_from(value: &'static str) -> Result<Self, Self::Error> {
332        let mut cards = PlayingCards::default();
333
334        for s in value.split_whitespace() {
335            let card = PlayingCard::from(s);
336            if card.is_blank() {
337                return Err(HandError::InvalidCard);
338            }
339            cards.insert(card);
340        }
341        Ok(cards)
342    }
343}
344
345impl TryFrom<TwoCard> for PlayingCards {
346    type Error = HandError;
347
348    fn try_from(value: TwoCard) -> Result<Self, Self::Error> {
349        if value.is_dealt() {
350            let mut cards = PlayingCards::default();
351            cards.insert(PlayingCard::from(value.first()));
352            cards.insert(PlayingCard::from(value.second()));
353            Ok(cards)
354        } else {
355            Err(HandError::Incomplete)
356        }
357    }
358}
359
360#[cfg(test)]
361#[allow(non_snake_case)]
362mod playing_cards_tests {
363    use super::*;
364
365    fn royal_flush() -> PlayingCards {
366        PlayingCards::deck().draw(5)
367    }
368
369    fn is_royal_flush(cards: &PlayingCards) -> bool {
370        cards.contains(&PlayingCard::from("AS"))
371            && cards.contains(&PlayingCard::from("KS"))
372            && cards.contains(&PlayingCard::from("QS"))
373            && cards.contains(&PlayingCard::from("JS"))
374            && cards.contains(&PlayingCard::from("TS"))
375            && cards.len() == 5
376    }
377
378    #[test]
379    fn is_royal_flush__test() {
380        let mut cards = royal_flush();
381
382        assert!(is_royal_flush(&cards));
383
384        cards.insert(PlayingCard::from("KD"));
385
386        assert!(!is_royal_flush(&cards));
387    }
388
389    #[test]
390    fn combinations() {
391        let aces = PlayingCards::try_from("AS AH AD AC").unwrap();
392        assert_eq!(6, aces.combinations(2).count());
393        assert_eq!(2_598_960, PlayingCards::deck().combinations(5).count());
394    }
395
396    #[test]
397    fn two_cards() {
398        let aces = PlayingCards::try_from("AS AH AD AC").unwrap().two_cards();
399        assert_eq!(6, aces.len());
400    }
401
402    #[test]
403    fn contains() {
404        let cards = royal_flush();
405        assert!(cards.contains(&PlayingCard::from("AS")));
406        assert!(!cards.contains(&PlayingCard::from("AD")));
407    }
408
409    #[test]
410    fn deal_from_the_bottom() {
411        let mut cards = PlayingCards::deck();
412
413        let card = cards.deal_from_the_bottom().unwrap();
414
415        assert_eq!(card, PlayingCard::from("2C"));
416    }
417
418    #[test]
419    fn draw() {
420        let mut cards = PlayingCards::deck();
421
422        let drawn = cards.draw(5);
423
424        assert!(is_royal_flush(&drawn));
425        assert_eq!(cards.len(), 47);
426    }
427
428    #[test]
429    fn draw_one() {
430        let mut cards = PlayingCards::deck();
431
432        let drawn = cards.draw_one();
433
434        assert_eq!(drawn, PlayingCard::from("AS"));
435        assert_eq!(cards.len(), 51);
436    }
437
438    #[test]
439    fn get() {
440        let cards = royal_flush();
441        let ace_spades = PlayingCard::from("AS");
442
443        assert_eq!(cards.get(&ace_spades).unwrap(), &ace_spades);
444        assert!(cards.get(&PlayingCard::from("AD")).is_none());
445    }
446
447    #[test]
448    fn get_index() {
449        let cards = royal_flush();
450
451        assert_eq!(cards.get_index(0).unwrap(), &PlayingCard::from("AS"));
452        assert_eq!(cards.get_index(1).unwrap(), &PlayingCard::from("KS"));
453        assert_eq!(cards.get_index(2).unwrap(), &PlayingCard::from("QS"));
454        assert_eq!(cards.get_index(3).unwrap(), &PlayingCard::from("JS"));
455        assert_eq!(cards.get_index(4).unwrap(), &PlayingCard::from("TS"));
456        assert!(cards.get_index(5).is_none());
457    }
458
459    #[test]
460    fn insert() {
461        let mut cards = royal_flush();
462
463        let result = cards.insert(PlayingCard::from("AS"));
464
465        assert!(!result);
466        assert!(is_royal_flush(&cards));
467    }
468
469    #[test]
470    fn is_disjoint() {
471        let mut deck = PlayingCards::deck();
472        let royal_flush = deck.draw(5);
473        let straight_flush = deck.draw(5);
474
475        assert!(royal_flush.is_disjoint(&straight_flush));
476        assert!(straight_flush.is_disjoint(&royal_flush));
477        assert!(straight_flush.is_disjoint(&deck));
478        assert!(royal_flush.is_disjoint(&deck));
479        assert!(deck.is_disjoint(&royal_flush));
480        assert!(deck.is_disjoint(&straight_flush));
481        assert!(!royal_flush.is_disjoint(&PlayingCards::deck().draw(2)));
482    }
483
484    #[test]
485    fn is_empty() {
486        assert!(PlayingCards::default().is_empty())
487    }
488
489    #[test]
490    fn is_subset() {
491        let cards = royal_flush();
492        let other = PlayingCards::deck_shuffled();
493
494        assert!(cards.is_subset(&other));
495        assert!(!other.is_subset(&cards));
496    }
497
498    #[test]
499    fn is_superset() {
500        let cards = royal_flush();
501        let other = PlayingCards::deck_shuffled();
502
503        assert!(other.is_superset(&cards));
504        assert!(!cards.is_superset(&other));
505    }
506
507    #[test]
508    fn len() {
509        assert_eq!(5, royal_flush().len())
510    }
511
512    #[test]
513    fn reverse() {
514        let mut cards = royal_flush();
515        cards.reverse();
516
517        assert_eq!("T♠ J♠ Q♠ K♠ A♠", cards.to_string());
518    }
519
520    #[test]
521    fn shuffle_in_place() {
522        let mut cards = PlayingCards::deck().draw(5);
523
524        cards.shuffle_in_place();
525
526        assert!(is_royal_flush(&cards));
527        cards.sort_in_place();
528        assert_eq!("A♠ K♠ Q♠ J♠ T♠", cards.to_string());
529    }
530
531    #[test]
532    fn to_vec() {
533        let v = vec![
534            PlayingCard::from("AS"),
535            PlayingCard::from("KS"),
536            PlayingCard::from("QS"),
537            PlayingCard::from("JS"),
538            PlayingCard::from("TS"),
539        ];
540
541        assert_eq!(royal_flush().to_vec(), v);
542    }
543
544    #[test]
545    fn display() {
546        let cards = royal_flush();
547
548        assert_eq!("A♠ K♠ Q♠ J♠ T♠", cards.to_string());
549    }
550
551    #[test]
552    fn from__pile() {
553        let expected = PlayingCards::from(&Pile::french_deck());
554        let actual = PlayingCards::from(&Pile::french_deck_with_jokers());
555
556        assert_eq!(expected, actual);
557        assert_ne!(royal_flush(), actual);
558        assert!(PlayingCards::from(&Pile::skat_deck()).is_empty());
559    }
560
561    #[test]
562    fn try_from__static_str() {
563        let actual = PlayingCards::try_from("A♠ K♠ Q♠ J♠ T♠").unwrap();
564
565        assert_eq!(royal_flush(), actual);
566    }
567
568    #[test]
569    fn try_from__two_cards() {
570        let actual = PlayingCards::try_from(TwoCard::try_from("Q♠ J♠").unwrap()).unwrap();
571
572        assert_eq!("Q♠ J♠", actual.to_string());
573    }
574
575    #[test]
576    fn try_from__two_cards__invalid() {
577        let actual = PlayingCards::try_from(TwoCard::default());
578
579        assert!(actual.is_err());
580    }
581}