434 lines
15 KiB
Rust
434 lines
15 KiB
Rust
#[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::<HashSet<_>>();
|
|
|
|
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::<HashSet<_>>();
|
|
|
|
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<PlayingCard> {
|
|
((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::<HashSet<_>>()
|
|
}
|
|
|
|
// 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::<Vec<_>>();
|
|
|
|
let playing_cards = range
|
|
.clone()
|
|
.map(|x| PlayingCard::try_from(x).unwrap())
|
|
.collect::<Vec<_>>();
|
|
|
|
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::ZipCatersianExt,
|
|
};
|
|
|
|
#[test]
|
|
fn rank() {
|
|
let ranks = Rank::iter_all().collect::<HashSet<_>>();
|
|
// 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::<HashSet<_>>();
|
|
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::<HashSet<_>>();
|
|
// 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::<HashSet<_>>();
|
|
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::<HashSet<_>>();
|
|
|
|
// 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::<Vec<_>>();
|
|
|
|
// 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
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|