*: small refactors and test changes

This commit is contained in:
2026-04-16 11:45:34 +01:00
committed by oreodave
parent f1d26acb0b
commit 79366d60fe
5 changed files with 63 additions and 54 deletions

View File

@@ -17,7 +17,7 @@ impl Iterator for CardIterator {
suit: Suit::Spade, suit: Suit::Spade,
deck, deck,
}) => { }) => {
self.0 = Card::Joker((deck + 1) * -1); self.0 = Card::Joker(-(deck + 1));
None None
} }
Card::PlayingCard(pc) => { Card::PlayingCard(pc) => {
@@ -40,7 +40,7 @@ impl DoubleEndedIterator for CardIterator {
suit: Suit::Diamond, suit: Suit::Diamond,
deck, deck,
}) => { }) => {
self.0 = Card::Joker((deck + 1) * -1); self.0 = Card::Joker(-(deck + 1));
None None
} }
Card::PlayingCard(pc) => { Card::PlayingCard(pc) => {

View File

@@ -273,11 +273,18 @@ mod test_impls {
#[test] #[test]
fn rank() { fn rank() {
let ranks = Rank::iter_all().into_iter().collect::<HashSet<_>>(); let ranks = Rank::all().into_iter().collect::<HashSet<_>>();
// TEST: Rank::iter_all produces all 13 unique ranks. // TEST: Rank::iter_all produces all 13 unique ranks.
assert_eq!(ranks.len(), 13); assert_eq!(ranks.len(), 13);
for rank in Rank::iter_all() { for (ind, rank) in Rank::all().iter().enumerate() {
let rest = rank.iter_rest().collect::<HashSet<_>>();
// TEST: Rank::iter_rest should generate the remaining ranks after
// the current rank in order.
assert_eq!(rest.len(), 13 - ind - 1);
}
for rank in Rank::all() {
let cards = rank.cards().into_iter().collect::<HashSet<_>>(); let cards = rank.cards().into_iter().collect::<HashSet<_>>();
assert_eq!(cards.len(), 4, "Expected 4 cards per rank"); assert_eq!(cards.len(), 4, "Expected 4 cards per rank");
for c in cards { for c in cards {
@@ -293,8 +300,8 @@ mod test_impls {
// Since there are 4 unique cards generated by rank.cards(), and // Since there are 4 unique cards generated by rank.cards(), and
// they all have the same deck (0) and rank (rank), they must differ // they all have the same deck (0) and rank (rank), they must differ
// by Suit by virtue of ord. 4 suits suggests (by pigeonhole // by Suit. 4 suits suggests (by pigeonhole principle) that all
// principle) that all suits must have been used. // suits must have been used.
// So rank.cards() generates all cards of deck 0 that have that rank // So rank.cards() generates all cards of deck 0 that have that rank
// (which is precisely 4 cards). QED. // (which is precisely 4 cards). QED.
@@ -408,7 +415,7 @@ mod test_impls {
counter counter
}; };
for (rank, suit) in Rank::iter_all() for (rank, suit) in Rank::all()
.into_iter() .into_iter()
.zip_cartesian(Suit::iter_all().into_iter()) .zip_cartesian(Suit::iter_all().into_iter())
{ {

View File

@@ -113,11 +113,11 @@ mod tests {
assert_eq!(Pair::new(Card::make_joker(), Card::make_joker()), None); assert_eq!(Pair::new(Card::make_joker(), Card::make_joker()), None);
// TEST: Non pair tests. // TEST: Non pair tests.
for (c1, c2) in Rank::iter_all() for (c1, c2) in Rank::all()
.into_iter() .into_iter()
// Generate tuples (r1, r2) where r1 != r2 // Generate tuples (r1, r2) where r1 != r2
.flat_map(|r1| { .flat_map(|r1| {
Rank::iter_all() Rank::all()
.into_iter() .into_iter()
.filter(move |&r2| r2 != r1) .filter(move |&r2| r2 != r1)
.map(move |r2| (r1, r2)) .map(move |r2| (r1, r2))

View File

@@ -150,18 +150,22 @@ mod tests {
// Triple::2. // Triple::2.
assert_eq!( assert_eq!(
trip.2, card, trip.2, card,
"Expected the highest card of the triple ({}) to be the sole PlayingCard ({card})", "Expected Triple::2 to be the sole PlayingCard"
trip.2 );
assert_eq!(
trip.high_card(),
card,
"Expected Triple::HighCard = card"
); );
} }
// Iterate over all pairs of cards with similar ranks // Iterate over all pairs of cards with similar ranks
for (c1, c2) in Rank::iter_all().into_iter().flat_map(|r| { for (c1, c2) in Rank::all().into_iter().flat_map(|r| {
r.cards().into_iter().zip_cartesian(r.cards().into_iter()) r.cards().into_iter().zip_cartesian(r.cards().into_iter())
}) { }) {
let trip = Triple::new(c1, c2, joker); let trip = Triple::new(c1, c2, joker);
// TEST: Any two similar rank cards with 1 joker are a // TEST: Any two similar rank cards with 1 joker is a Triple.
// Triple.
assert_ne!( assert_ne!(
trip, None, trip, None,
"Expected ({c1}, {c2}, Joker) to make a Triple" "Expected ({c1}, {c2}, Joker) to make a Triple"
@@ -190,13 +194,16 @@ mod tests {
trip.1, trip.1,
trip.2, trip.2,
); );
// TEST: Expect triple high card to be the highest card of c1,c2.
assert_eq!(trip.high_card(), c2);
} }
// Iterate over all pairs of cards with differing ranks // Iterate over all pairs of cards with differing ranks
for (c1, c2) in Rank::iter_all() for (c1, c2) in Rank::all()
.into_iter() .into_iter()
.flat_map(|r1| { .flat_map(|r1| {
Rank::iter_all() Rank::all()
.into_iter() .into_iter()
.filter(move |&r2| r2 != r1) .filter(move |&r2| r2 != r1)
.map(move |r2| (r1, r2)) .map(move |r2| (r1, r2))
@@ -246,6 +253,10 @@ mod tests {
let [c1, c2, c3] = ordered([c1, c2, c3]); let [c1, c2, c3] = ordered([c1, c2, c3]);
// TEST: Triple::high_card should be c3 (the highest card of the
// ordered set).
assert_eq!(trip.high_card(), c3);
// TEST: If a triple is formed of 3 playing cards, they are // TEST: If a triple is formed of 3 playing cards, they are
// ordered s.t. Triple::2 > Triple::1 > Triple::0. // ordered s.t. Triple::2 > Triple::1 > Triple::0.
assert_eq!( assert_eq!(
@@ -286,18 +297,11 @@ mod tests {
let joker = Card::make_joker(); let joker = Card::make_joker();
for (t1, t2) in for (t1, t2) in
// Iterate through all pairs of differing ranks (r1, r2) where r2 >
// r1
Rank::iter_all()
.into_iter()
.flat_map(|r1| {
Rank::iter_all()
.into_iter()
.filter(move |&r2| r2 > r1)
.map(move |r2| (r1, r2))
})
// Generate an exhaustive set of triples where rank(t1) < // Generate an exhaustive set of triples where rank(t1) <
// rank(t2) // rank(t2)
Rank::all()
.into_iter()
.flat_map(move |r1| r1.iter_rest().map(move |r2| (r1, r2)))
.flat_map(|(r1, r2)| { .flat_map(|(r1, r2)| {
exhaustive_triples_rank(r1) exhaustive_triples_rank(r1)
.zip_cartesian(exhaustive_triples_rank(r2)) .zip_cartesian(exhaustive_triples_rank(r2))
@@ -310,39 +314,38 @@ mod tests {
} }
// So high card rank determines ordering between differing ranked // So high card rank determines ordering between differing ranked
// triples. // triples. Let's test what happens within triples of the same high
// card rank.
// Iterate through all ranks // Iterate through all ranks
for rank in Rank::iter_all() { for rank in Rank::all() {
let cards = rank.cards(); let cards = rank.cards();
// All possible 2 joker triples for this rank.
let two_joker_triples = cards
.map(|c| Triple::new(c, joker, joker))
.map(Option::unwrap);
for triple in
exhaustive_triples_rank(rank).filter(|x| x.count_jokers() < 2)
{
for two_joker_trip in two_joker_triples {
// TEST: A two joker triple is always worse than any triples
// in the same rank that have at most 1 joker.
assert!(two_joker_trip < triple);
}
}
let [diamond, _, _, spade] = cards; let [diamond, _, _, spade] = cards;
// NOTE: By new test this should be safe to unwrap. // NOTE: By new test this should be safe to unwrap.
let minima = Triple::new(diamond, joker, joker).unwrap(); let minima = Triple::new(diamond, joker, joker).unwrap();
let maxima = Triple::new(spade, spade, spade).unwrap(); let maxima = Triple::new(spade, spade, spade).unwrap();
// All possible 2 joker triples for this rank.
let two_joker_triples = cards
.map(|c| Triple::new(c, joker, joker))
.map(Option::unwrap);
for triple in exhaustive_triples_rank(rank) { for triple in exhaustive_triples_rank(rank) {
// TEST: The lowest possible triple in a rank is a diamond + 2 // TEST: The lowest possible triple in a rank is a diamond + 2
// jokers // jokers
assert!(minima <= triple); assert!(minima <= triple);
// TEST: The highest possible triple in a rank is 3 spades // TEST: The highest possible triple in a rank is 3 spades
assert!(maxima >= triple); assert!(maxima >= triple);
if triple.count_jokers() < 2 {
for two_joker_trip in two_joker_triples {
// TEST: A two joker triple is always worse than any
// triples in the same rank that have at most 1 joker.
assert!(two_joker_trip < triple);
}
}
} }
} }
} }
@@ -350,8 +353,8 @@ mod tests {
#[test] #[test]
fn footstool() { fn footstool() {
let triples = exhaustive_triples_deck().collect::<Vec<_>>(); let triples = exhaustive_triples_deck().collect::<Vec<_>>();
for t1 in &triples { for (ind, t1) in triples.iter().enumerate() {
for t2 in &triples { for t2 in &triples[ind..] {
// TEST: Expected footstool condition // TEST: Expected footstool condition
test_footstool(t1, t2); test_footstool(t1, t2);
} }

View File

@@ -19,18 +19,17 @@ where
I::Item: Copy, I::Item: Copy,
{ {
/// Exhaustive coupling of two iterators. /// Exhaustive coupling of two iterators.
/// For each x in `self`: for each y in `b`: yield (x, y). /// For each x in `self`: for each y in `other`: yield (x, y).
/// b: B must implement `Clone`. fn zip_cartesian<Other>(
fn zip_cartesian<B>(
self, self,
b: B, other: Other,
) -> impl Iterator<Item = (Self::Item, B::Item)> + Clone ) -> impl Iterator<Item = (Self::Item, Other::Item)> + Clone
where where
B::Item: Copy, Other::Item: Copy,
B: Iterator + Clone, Other: Iterator + Clone,
{ {
self.flat_map(move |a_item| { self.flat_map(move |a_item| {
b.clone().map(move |b_item| (a_item, b_item)) other.clone().map(move |b_item| (a_item, b_item))
}) })
} }
} }