#[cfg(test)] mod test_numerics { use std::collections::HashSet; use crate::card::{Card, PlayingCard, Rank, Suit}; #[test] fn rank() { // TEST: Negative numbers cannot be ranks assert!(Rank::try_from(-1).is_err()); // TEST: Numbers >= 13 cannot be a rank. for i in 13..1000 { assert!( Rank::try_from(i).is_err(), "Expected {i} not to match a rank" ); } // TEST: Numbers in [0, 12] are mapped to ranks let set = (0..13) .map(|i| { let rank = Rank::try_from(i); assert!(rank.is_ok(), "Expected {i} to map to a valid rank"); rank.unwrap() }) .collect::>(); assert_eq!( set.len(), 13, "Expected 13 unique ranks from Rank::try_from(0..13)" ); } #[test] fn suit() { // TEST: Negative numbers cannot be suits assert!(Suit::try_from(-1).is_err()); // TEST: Numbers >= 4 cannot be a suit. for i in 4..1000 { assert!( Suit::try_from(i).is_err(), "Expected {i} not to match a suit" ); } // TEST: Numbers in [0, 3] are mapped to suits let set = (0..4) .map(|i| { let suit = Suit::try_from(i); assert!(suit.is_ok(), "Expected {i} to map to a valid suit"); suit.unwrap() }) .collect::>(); assert_eq!( set.len(), 4, "Expected 4 unique Suits from Suit::try_from(0..4)" ); } #[test] fn playing_card() { // TEST: Negative numbers cannot be playing cards assert!(PlayingCard::try_from(-1).is_err()); // TEST: PlayingCard derived from 0 should be the 3 of Diamonds of the // 0th deck. { let expected = PlayingCard::new(0, Rank::Three, Suit::Diamond); let card = PlayingCard::try_from(0); assert!(card.is_ok()); let card = card.unwrap(); assert_eq!(card.rank, expected.rank); assert_eq!(card.suit, expected.suit); } fn range_to_playing_card(deck: i64) -> HashSet { ((deck * 52)..((deck + 1) * 52)) .map(|n| { let res = PlayingCard::try_from(n); assert!( res.is_ok(), "Expected {n} to map to a PlayingCard" ); res.unwrap() }) .map(|card| { // TEST: Playing cards derived from 52d..52(d + 1) all are // in deck d. assert_eq!(card.deck, deck); card }) .collect::>() } // 0..51 should map to the 0th deck of playing cards. let first_deck = range_to_playing_card(0); // 52..103 should map to the 1st deck of playing cards. let second_deck = range_to_playing_card(1); // TEST: Expect 52 unique playing cards from // PlayingCard::try_from(0..52) or PlayingCard::try_from(52..104). assert_eq!(first_deck.len(), 52, "Expected 52 unique cards"); assert_eq!(second_deck.len(), 52, "Expected 52 unique cards"); // There are 52 unique cards in PlayingCard::try_from(0..52). With deck // being fixed to one number, pigeonhole principle suggests all Playing // Cards really must be there (13 ranks, 4 suits => 52 combinations). // To really prove it we can check all combinations of rank and suit and // observe that they are present. for rank in (0..13i64).map(|n| Rank::try_from(n).unwrap()) { for suit in (0..4i64).map(|n| Suit::try_from(n).unwrap()) { let playing_card = PlayingCard::new(0, rank, suit); // TEST: Expected combination of rank and suit to be present in // first deck. assert!( first_deck.contains(&playing_card), "Expected {playing_card} to be present in the map of 0th deck" ); // TEST: Expected combination of rank and suit to be present in // second deck. let playing_card = PlayingCard::new(1, rank, suit); assert!( second_deck.contains(&playing_card), "Expected {playing_card} to be present in the map of 1st deck" ); } } // To test i64::from on PlayingCard, we can just do a map over a big // range and see that PlayingCard::try_from is the inverse. This range // should cover an inordinate number of Cards for a single game so for // our purposes it's just fine. for i in 0..1000 { let pc = PlayingCard::try_from(i).unwrap(); let ret = i64::from(pc); // TEST: i64::from is the exact inverse of PlayingCard::try_from. assert_eq!( i, ret, "Expected i64::from(PlayingCard::try_from(x)) = x" ); } } #[test] fn card() { // You can make Cards from negative numbers. for i in -100..0 { let card = Card::from(i); // TEST: Card::from(negative number) makes jokers. assert!(card.is_joker(), "Expected Card::from({i}) makes jokers"); } // Card::from should defer to PlayingCard::try_from for positive // integers. We may test this with a ridiculous range. let range = 0..1000; let cards = range.clone().map(Card::from).collect::>(); let playing_cards = range .clone() .map(|x| PlayingCard::try_from(x).unwrap()) .collect::>(); assert_eq!(cards.len(), playing_cards.len()); assert_eq!(cards.len(), 1000); for (card, pc) in cards.iter().zip(playing_cards.iter()) { if let Card::PlayingCard(c) = card { assert_eq!(c.deck, pc.deck); assert_eq!(c.rank, pc.rank); assert_eq!(c.suit, pc.suit); } else { unreachable!("All of cards should be playing cards"); } } // Thus the test on PlayingCard::from applies here. Great! // To test i64::from on Card, we'll do what the test on PlayingCard did // and use a massive range. for i in -1000..1000 { let pc = Card::from(i); let ret = i64::from(pc); // TEST: i64::from is the exact inverse of Card::from. assert_eq!(i, ret, "Expected i64::from(Card::from({i})) = {i}"); } } } #[cfg(test)] mod test_ord { use crate::card::Card; #[test] fn jokers() { // All jokers are equivalent, regardless of the i64 they originate from. let joker = Card::from(-1); for i in -1000..-1 { let other = Card::from(i); assert_eq!(joker, other); } // All playing cards are "better" than Jokers. for i in 0..1000 { let other = Card::from(i); assert!(other > joker, "{other} > {joker}"); assert!(joker < other, "{joker} < {other}"); } } #[test] fn natural_ordering() { // The cards Card::from(0..52) preserve the ordering relationship of the // underlying numbers. In other words, Cards and 0..52 are order // isomorphic. for i in 0..52 { let card = Card::from(i); // We don't need to do a lower numbers check because of this. for hi in (i + 1)..52 { let hi_card = Card::from(hi); // TEST: Higher numbers lead to higher cards. assert!(card < hi_card, "{card} < {hi_card}"); assert!(hi_card > card, "{hi_card} > {card}"); } } } #[test] fn deck_irrelevance() { // We'll do a similar check as natural_ordering, but we'll be using // cards from the 1st deck rather than the 0th deck. for i in 0..52 { let j = i + 52; let card_0 = Card::from(i); let card_1 = Card::from(j); // TEST: Two numbers equivalent mod 52 are "equivalent" cards under // Card::from. assert_eq!(card_0, card_1); // We don't need to do a lower numbers check because of this. for hi in (i + 1)..52 { let hi_card_0 = Card::from(hi); // deck 0 let hi_card_1 = Card::from(hi + 52); // deck 1 // TEST: Higher cards are still ordered better than another // deck's cards. assert!(card_1 < hi_card_0, "{card_1} < {hi_card_0}"); assert!(card_0 < hi_card_1, "{card_0} < {hi_card_1}"); assert!(hi_card_0 > card_1, "{hi_card_0} > {card_1}"); assert!(hi_card_1 > card_0, "{hi_card_1} > {card_0}"); } } } } #[cfg(test)] mod test_impls { use std::collections::{HashMap, HashSet}; use crate::{ card::{Card, PlayingCard, Rank, Suit}, exactsizearr::ExactSizedArr, zipcartesian::ZipCartesianExt, }; #[test] fn rank() { let ranks = Rank::iter_all().collect::>(); // TEST: Rank::iter_all produces all 13 unique ranks. assert_eq!(ranks.len(), 13); for rank in Rank::iter_all() { let cards = rank.cards().collect::>(); assert_eq!(cards.len(), 4, "Expected 4 cards per rank"); for c in cards { // TEST: rank.cards() generates Playing Cards of the same rank // as the input in deck 0. assert!(!c.is_joker()); let Card::PlayingCard(c) = c else { unreachable!("See above"); }; assert_eq!(c.deck, 0); assert_eq!(c.rank, rank); } // Since there are 4 unique cards generated by rank.cards(), and // they all have the same deck (0) and rank (rank), they must differ // by Suit by virtue of ord. 4 suits suggests (by pigeonhole // principle) that all suits must have been used. // So rank.cards() generates all cards of deck 0 that have that rank // (which is precisely 4 cards). QED. } } #[test] fn suit() { let suits = Suit::iter_all().collect::>(); // TEST: Suit::iter_all produces all 4 unique suits. assert_eq!(suits.len(), 4); for suit in Suit::iter_all() { let cards = suit.cards().collect::>(); assert_eq!(cards.len(), 13, "Expected 13 cards per suit"); for c in cards { // TEST: suit.cards() generates Playing Cards of the same suit // as the input in deck 0. assert!(!c.is_joker()); let Card::PlayingCard(c) = c else { unreachable!("See above"); }; assert_eq!(c.deck, 0); assert_eq!(c.suit, suit); } // Similar to rank, pigeonhole principle suggests we must have all // 13 ranks of cards in the suit expected for suit.cards(). } } #[test] fn playing_card() { for deck in 0..10 { let playing_cards = PlayingCard::iter_all(deck).collect::>(); // TEST: Expected 52 cards to be generated by PlayingCard::iter_all. assert_eq!( playing_cards.len(), 52, "Expected 52 cards in a playing card deck" ); for card in PlayingCard::iter_all(deck) .into_array::<52>() .unwrap_or_else(|_| { unreachable!( "Look at previous assertion; there must be 52 cards in the iterator." ) }) { // TEST: card.deck must match the input deck assert_eq!(card.deck, deck); let numeral = i64::from(card); // TEST: Expect the card from playing_cards to be bounded by the // limits generated by i64::from. assert!( numeral >= 52 * deck, "Expected i64::from({}) to be bounded below by {}", card, 52 * deck ); assert!( numeral <= (52 * deck) + 51, "Expected i64::from({}) to be bounded above by {}", card, (52 * deck) + 51 ); } } } #[test] fn card() { for decks in 1..10 { let cards = Card::iter_all(decks).collect::>(); // TEST: Expected there to be `decks` number of decks of cards (2 // jokers + 52 playing cards) present in Card::iter_all(decks). assert_eq!( cards.len(), (54 * decks) as usize, "Expected {} cards in a playing card deck", 54 * decks, ); // TEST: Expect there to be 2 jokers per deck of cards input. assert_eq!( cards.iter().filter(|n| n.is_joker()).count(), (2 * decks) as usize, "Expected there to be {} jokers in Card::iter_all({})", 2 * decks, decks, ); // Construct a counter which maps each unique (rank, suit) // combination to the count of that combination in cards. let counter = { let mut counter: HashMap<(Rank, Suit), i64> = HashMap::new(); for card in &cards { let Card::PlayingCard(card) = card else { continue; }; if let Some(count) = counter.get_mut(&(card.rank, card.suit)) { *count += 1; } else { counter.insert((card.rank, card.suit), 1); } } counter }; for (rank, suit) in Rank::iter_all().zip_cartesian(Suit::iter_all()) { // TEST: We expect `decks` instances of a (rank, suit) // combination in Card::iter_all(decks). assert_eq!( counter[&(rank, suit)], decks, "{} of {} doesn't have {} copies in Card::iter_all({})", rank, suit, decks, decks ); } } } }