From 980c7ca58bf160b40a221860795fe587d08c0b29 Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Tue, 14 Apr 2026 21:25:05 +0100 Subject: [PATCH] modes:triple:tests: Implement ordering and footstool tests for triples Painful. I hate testing with a passion. I find it so difficult to not be exhaustive since single pattern's aren't enough, but at the level of complexity for triples it's actually getting computationally difficult (read: takes a few seconds) to do. --- src/modes/triple.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/modes/triple.rs b/src/modes/triple.rs index 3d019e5..0b42ead 100644 --- a/src/modes/triple.rs +++ b/src/modes/triple.rs @@ -117,6 +117,7 @@ impl Hash for Triple { mod tests { use crate::{ card::{PlayingCard, Rank}, + modes::tests::test_footstool, zipcartesian::ZipCartesianExt, }; @@ -258,5 +259,102 @@ mod tests { ); } } + + fn exhaustive_triples_deck() -> impl Iterator + Clone { + Card::iter_all(1) + .zip_cartesian(Card::iter_all(1).zip_cartesian(Card::iter_all(1))) + .filter_map(|(c1, (c2, c3))| Triple::new(c1, c2, c3)) + } + + fn exhaustive_triples_rank( + rank: Rank, + ) -> impl Iterator + Clone { + let choices = rank.cards().into_iter().chain([Card::make_joker()]); + let c1s = choices.clone(); + let c2s = choices.clone(); + let c3s = choices.clone(); + c1s.zip_cartesian(c2s.zip_cartesian(c3s)) + .filter_map(|(c1, (c2, c3))| Triple::new(c1, c2, c3)) + } + + #[test] + fn ordering() { + // Unlike pairs and singles, the complexity of triples ordering is too + // great to be exhaustive. As a compromise, we'll run through a bunch + // of cases that should demonstrate everything we want. + + let joker = Card::make_joker(); + + 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) < + // rank(t2) + .flat_map(|(r1, r2)| { + exhaustive_triples_rank(r1) + .zip_cartesian(exhaustive_triples_rank(r2)) + }) + { + // TEST: Any triple formed of some higher rank will be better than + // any triple formed of a lower rank. + assert!(t2 > t1, "Expected {t1} < {t2} as rank({t1}) < rank({t2})"); + assert!(t1 < t2, "Expected {t2} > {t1} as rank({t1}) < rank({t2})"); + } + + // So high card rank determines ordering between differing ranked + // triples. + + // Iterate through all ranks + for rank in Rank::iter_all() { + 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; + + // NOTE: By new test this should be safe to unwrap. + let minima = Triple::new(diamond, joker, joker).unwrap(); + let maxima = Triple::new(spade, spade, spade).unwrap(); + + for triple in exhaustive_triples_rank(rank) { + // TEST: The lowest possible triple in a rank is a diamond + 2 + // jokers + assert!(minima <= triple); + // TEST: The highest possible triple in a rank is 3 spades + assert!(maxima >= triple); + } + } + } + + #[test] + fn footstool() { + let triples = exhaustive_triples_deck().collect::>(); + for t1 in &triples { + for t2 in &triples { + // TEST: Expected footstool condition + test_footstool(t1, t2); + } + } } }