use crate::card::{Card, PlayingCard}; use crate::helper::ordered; #[derive(Eq, Debug, Copy, Clone)] pub struct Pair(pub Card, pub Card); impl Pair { /** Create a new pair utilising two cards, `c1` and `c2`. Will return None if a Pair cannot be constructed out of the two cards. NOTE: By construction, if the Pair includes a Joker, that Joker will be the first member of the pair. In other words, Pair::1 will always be a valid playing card. */ fn new(c1: Card, c2: Card) -> Option { // Order the cards. This means if xor(c1 is joker, c2 is joker) c1 will // be that joker. let (c1, c2) = ordered(c1, c2); match (c1, c2) { // Can't be a pair if you got two jokers homie. (Card::Joker(_), Card::Joker(_)) => None, // NOTE: c2 cannot be a joker because of prev condition. If you've // got a joker you're automatically a pair. (Card::Joker(_), _) => Some(Pair(c1, c2)), // NOTE: c1 and c2 cannot be jokers. In which case, check their // ranks are equivalent. ( Card::PlayingCard(PlayingCard { rank: r1, .. }), Card::PlayingCard(PlayingCard { rank: r2, .. }), ) => (r1 == r2).then_some(Pair(c1, c2)), // Not necessary since the previous patterns technically cover all // cases. But I love the compiler too much to tell them... 💔 _ => None, } } } use crate::modes::single::Single; use crate::modes::{Footstool, Hand}; impl Hand for Pair { fn is_proper(&self) -> bool { matches!(self.0, Card::PlayingCard(_)) } fn footstool(&self, other: &Self) -> Footstool { // A pair footstools the other <=> the highest cards of both footstool // each other => we can rely on the footstool implementation of Single // for this. Single(self.1).footstool(&Single(other.1)) } } use std::fmt::{Display, Formatter, Result}; impl Display for Pair { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "Pair[{}, {}]", self.0, self.1) } } use std::cmp::Ordering; impl Ord for Pair { fn cmp(&self, other: &Self) -> Ordering { /* Comparison is slightly complicated by the inclusion of wild cards. Rules are as follows: 1) Two proper/improper pairs that have equivalent high cards are equivalent. 2) Two pairs with equivalent high cards but only one is proper; the pair that is proper wins. 3) Otherwise, comparison between high cards is the ordering. */ match (self.1.cmp(&other.1), self.is_proper(), other.is_proper()) { (Ordering::Equal, false, true) => Ordering::Less, (Ordering::Equal, true, false) => Ordering::Greater, (x, ..) => x, } } } impl PartialEq for Pair { fn eq(&self, other: &Self) -> bool { self.cmp(other) == Ordering::Equal } } impl PartialOrd for Pair { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } #[cfg(test)] mod tests { use super::*; use crate::card::{make_decks, Rank}; #[test] fn new() { // Two jokers can never be a pair. assert_eq!(Pair::new(Card::make_joker(), Card::make_joker()), None); for rank in Rank::iter_all() { for c1 in rank.cards() { for c2 in rank.cards() { // TEST: Pairs are composed of two similar rank cards. let pair = { let pair = Pair::new(c1, c2); assert_ne!(pair, None); pair.unwrap() }; // Test: Pairs of two playing cards are proper assert!(pair.is_proper()); // TEST: Pairs always sort their cards in strength. let (b1, b2) = ordered(c1, c2); assert_eq!(pair.0, b1); assert_eq!(pair.1, b2); } // TEST: Pairs may have one joker. let pair = { let p = Pair::new(c1, Card::make_joker()); assert_ne!(p, None); p.unwrap() }; // TEST: Pairs with a joker are improper. assert!(pair.is_improper()); // TEST: Improper pairs have a Joker in Pair::0. assert!(matches!(pair.0, Card::Joker(_))); assert!(matches!(pair.1, Card::PlayingCard(_))); } for opposing_rank in Rank::iter_all() { if rank == opposing_rank { continue; } assert!(rank != opposing_rank); // TEST: Two playing cards of differing rank can never be a pair. for r1 in rank.cards() { for r2 in opposing_rank.cards() { assert_eq!(Pair::new(r1, r2), None); } } } } } // A rank has 4 cards. There are a total of 10 proper pairs that can be // made out of 4 cards. With n jokers, add 4n improper pairs to the // total count of pairs for a single rank. fn exhaustive_pairs_deck() -> Vec { // A deck has 2 jokers => 18 pairs per rank. let mut pairs = Vec::with_capacity(13 * 18); for c1 in make_decks(1) { for c2 in make_decks(2) { if let Some(p) = Pair::new(c1, c2) { pairs.push(p); } } } pairs } fn exhaustive_pairs_rank(r: Rank) -> Vec { // We're only looking at one joker here, so 14 pairs in a rank. let mut pairs = Vec::with_capacity(14); for c1 in r.cards() { for c2 in r.cards() { pairs.push(Pair::new(c1, c2).unwrap()); } pairs.push(Pair::new(c1, Card::make_joker()).unwrap()); } pairs } #[test] fn ordering() { fn expected_ordering_relation(p1: &Pair, p2: &Pair) -> bool { let pair_ordering = p1.cmp(p2); let high_card_ordering = p1.1.cmp(&p2.1); match (pair_ordering, high_card_ordering) { // For any two pairs, we expect the high cards to dictate the // ordering of the pairs - the lower card should be irrelevant. (x, y) if x == y => true, // The only instances where the high card may not dictate card // ordering is if both pairs have equivalent high cards and // exclusively one of the pairs is improper - pairs that are // improper should be sorted less than the proper one. (Ordering::Less, Ordering::Equal) => { // p1 has a joker, p2 doesn't => p2 is better than p1 p1.is_improper() && p2.is_proper() } (Ordering::Greater, Ordering::Equal) => { // p2 has a joker, p1 doesn't => p1 is better than p2 p2.is_improper() && p1.is_proper() } _ => false, } } for p1 in &exhaustive_pairs_deck() { for p2 in &exhaustive_pairs_deck() { // TEST: For any two pair we expect them to have the // `expected_ordering_relation`. assert!(expected_ordering_relation(p1, p2)); } } } #[test] fn footstool() { todo!("Implement tests for Pair footstools"); } #[test] fn footstool_deck_irrelevance() { todo!("Implement tests for Pair footstool deck irrelevance"); } }