modes:single:tests: major refactor
Better exhaustive testing, addition of messages on asserts as well.
This commit is contained in:
@@ -24,16 +24,13 @@ impl Hand for Single {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn footstool(&self, other: &Self) -> Footstool {
|
fn footstool(&self, other: &Self) -> Footstool {
|
||||||
let self_abs = self.0.deck_abs();
|
// We use deck_abs() to get an index in the overall deck ordering.
|
||||||
let other_abs = other.0.deck_abs();
|
match (self.0.deck_abs(), other.0.deck_abs()) {
|
||||||
|
// A full footstool only occurs when both are the same.
|
||||||
// Trivial implementation
|
(x, y) if x == y => Footstool::Full,
|
||||||
if self_abs == other_abs {
|
// Half footstools can only occur when x is consecutive to y.
|
||||||
Footstool::Full
|
(x, y) if x == y + 1 => Footstool::Half,
|
||||||
} else if self_abs == (other_abs + 1) % 52 {
|
_ => Footstool::None,
|
||||||
Footstool::Half
|
|
||||||
} else {
|
|
||||||
Footstool::None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,129 +44,219 @@ impl Display for Single {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
card::{make_decks, PlayingCard, Rank, Suit},
|
card::{PlayingCard, Rank, Suit},
|
||||||
modes::tests::test_footstool,
|
modes::tests::test_footstool,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new() {
|
fn new() {
|
||||||
// TEST: Jokers are not valid singles.
|
// TEST: Jokers are not valid singles.
|
||||||
assert!(Single::new(Card::make_joker()).is_none());
|
assert_eq!(
|
||||||
|
Single::new(Card::make_joker()),
|
||||||
|
None,
|
||||||
|
"Expected Jokers to never be valid singles"
|
||||||
|
);
|
||||||
|
|
||||||
let valid_singles =
|
let valid_singles = Card::iter_all(1)
|
||||||
make_decks(1).filter_map(Single::new).collect::<Vec<_>>();
|
.filter_map(Single::new)
|
||||||
let deck = make_decks(1).collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let deck = Card::iter_all(1).collect::<Vec<_>>();
|
||||||
|
|
||||||
// TEST: Only two cards in a single deck aren't valid singles.
|
// TEST: Only two cards in a single deck aren't valid singles.
|
||||||
assert!(valid_singles.len() == deck.len() - 2);
|
assert_eq!(valid_singles.len(), deck.len() - 2);
|
||||||
|
|
||||||
// TEST: All valid singles are playing cards.
|
|
||||||
assert!(valid_singles.iter().all(|Single(card)| !card.is_joker()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn footstool() {
|
fn footstool() {
|
||||||
// Make a deck with no jokers.
|
// Create a vector for all possible Singles in 1 deck of cards. Due to
|
||||||
let singles = PlayingCard::iter_deck(0)
|
// ordering of Card::iter_all, we expect this to be sorted as well.
|
||||||
.map(Card::PlayingCard)
|
let singles = Card::iter_all(1)
|
||||||
.filter_map(Single::new)
|
.filter_map(Single::new)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// TEST: Consecutive singles footstool testing.
|
||||||
singles.windows(3).for_each(|single_slice| {
|
singles.windows(3).for_each(|single_slice| {
|
||||||
let (s1, s2, s3) =
|
let (s1, s2, s3) =
|
||||||
(single_slice[0], single_slice[1], single_slice[2]);
|
(single_slice[0], single_slice[1], single_slice[2]);
|
||||||
|
|
||||||
// TEST: A single is always full footstooled by itself.
|
// TEST: Test footstool patterns and get some results back for
|
||||||
assert!(s1.footstool(&s1) == Footstool::Full);
|
// further testing.
|
||||||
|
|
||||||
// TEST: non-reflexivity of footstool on neighbours.
|
|
||||||
let (_, s2_on_s1) = test_footstool(&s1, &s2);
|
let (_, s2_on_s1) = test_footstool(&s1, &s2);
|
||||||
let (_, s3_on_s2) = test_footstool(&s2, &s3);
|
let (_, s3_on_s2) = test_footstool(&s2, &s3);
|
||||||
let (s1_on_s3, s3_on_s1) = test_footstool(&s1, &s3);
|
let (s1_on_s3, s3_on_s1) = test_footstool(&s1, &s3);
|
||||||
|
|
||||||
// TEST: s2 is half-footstooled by s3, and s1 is half footstooled by
|
// TEST: s2 is half-footstooled by s3, and s1 is half footstooled by
|
||||||
// s2.
|
// s2.
|
||||||
assert!(s3_on_s2 == Footstool::Half);
|
assert!(
|
||||||
assert!(s2_on_s1 == Footstool::Half);
|
s3_on_s2 == Footstool::Half,
|
||||||
|
"{s3} should half footstool {s2}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
s2_on_s1 == Footstool::Half,
|
||||||
|
"{s2} should half footstool {s1}"
|
||||||
|
);
|
||||||
|
|
||||||
// TEST: s1 does not footstool whatsoever with s3.
|
// TEST: s1 does not footstool whatsoever with s3.
|
||||||
assert!(s1_on_s3 == Footstool::None);
|
assert!(
|
||||||
assert!(s3_on_s1 == Footstool::None);
|
s1_on_s3 == Footstool::None,
|
||||||
|
"{s1} should not footstool {s3}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
s3_on_s1 == Footstool::None,
|
||||||
|
"{s3} should not footstool {s1}"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
for single in &singles {
|
// Exhaustive testing over every possible combinations of Singles.
|
||||||
let footstool_results = singles
|
|
||||||
.iter()
|
// Create an exhaustive map for all combinations (Single, Single) along
|
||||||
.map(|&other_single| {
|
// with the results of the first footstooling the second.
|
||||||
// TEST: All footstool results are non-reflexive.
|
let exhaustive_singles_footstool = singles
|
||||||
test_footstool(single, &other_single)
|
.iter()
|
||||||
|
.flat_map(|single| {
|
||||||
|
singles
|
||||||
|
.iter()
|
||||||
|
.map(move |other_single| (single, other_single))
|
||||||
|
})
|
||||||
|
.map(|(single, other_single)| {
|
||||||
|
// TEST: Expected generic pattern for footstooling of hands -
|
||||||
|
// see mod::tests::test_footstool for details.
|
||||||
|
|
||||||
|
// NOTE: Due to test_footstool impl, this automatically tests
|
||||||
|
// that single == other_single <=> single full footstools
|
||||||
|
// other_single (and vice versa)
|
||||||
|
(single, other_single, test_footstool(single, other_single).0)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// TEST: Half footstools.
|
||||||
|
{
|
||||||
|
// Maps Singles to a Vector of the Singles they half footstool.
|
||||||
|
let counter = {
|
||||||
|
let mut counter: HashMap<Single, Vec<Single>> = HashMap::new();
|
||||||
|
exhaustive_singles_footstool
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, _, res)| *res == Footstool::Half)
|
||||||
|
.for_each(|(c1, c2, _)| {
|
||||||
|
if let Some(val) = counter.get_mut(*c1) {
|
||||||
|
val.push(**c2);
|
||||||
|
} else {
|
||||||
|
counter.insert(**c1, vec![**c2]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
counter
|
||||||
|
};
|
||||||
|
|
||||||
|
// TEST: For any Single there is only 1 other Single that it
|
||||||
|
// half footstools.
|
||||||
|
counter.iter().for_each(|(c1, counter)| {
|
||||||
|
assert_eq!(
|
||||||
|
counter.len(),
|
||||||
|
1,
|
||||||
|
"Expected {c1} to only have 1 card that it half footstools"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TEST: For any Single, the Single that it half footstools
|
||||||
|
// is unique to it.
|
||||||
|
{
|
||||||
|
let mut unique_half_footstools = HashSet::<Single>::new();
|
||||||
|
counter.iter().for_each(|(c1, counter)| {
|
||||||
|
let c2 = counter[0];
|
||||||
|
assert_eq!(unique_half_footstools.get(&c2), None, "Expected {c2} to be unique to the half footstools of {c1}");
|
||||||
|
unique_half_footstools.insert(c2);
|
||||||
})
|
})
|
||||||
.map(|x| x.0)
|
}
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// TEST: A single is only full-footstooled by itself.
|
// TEST: The only Single that doesn't have a half footstool is 3[D]
|
||||||
let full_footstools = footstool_results
|
{
|
||||||
.iter()
|
let card = Single::new(Card::from(0)).unwrap();
|
||||||
.filter(|&&x| x == Footstool::Full)
|
assert_eq!(
|
||||||
.count();
|
counter.get(&card),
|
||||||
assert!(full_footstools == 1);
|
None,
|
||||||
|
"Expected {card} to not have any half footstools."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TEST: A single is half-footstooled by at most one single.
|
// TEST: Non-footstools
|
||||||
let half_footstools = footstool_results
|
{
|
||||||
.iter()
|
// A little combinatorial check. 3[D] has no half footstools and 1
|
||||||
.filter(|&&x| x == Footstool::Half)
|
// full footstool (itself). Every other card should have 1 unique
|
||||||
.count();
|
// half footstool and 1 full footstool.
|
||||||
assert!(half_footstools <= 1);
|
|
||||||
|
|
||||||
// TEST: A single is not footstooled by any other singles.
|
// 51 cards should satisfy the latter condition => 102 instances of
|
||||||
let non_footstools = footstool_results
|
// a half or full footstool. With 3[D], that's 103 instances of a
|
||||||
.iter()
|
// footstool.
|
||||||
.filter(|&&x| x == Footstool::None)
|
|
||||||
.count();
|
// If the above conditions hold, then we'd expect the number of non
|
||||||
assert!(
|
// footstools in our exhaustive set to be (52 ** 2) - 103.
|
||||||
non_footstools < singles.len()
|
|
||||||
&& non_footstools >= singles.len() - 3
|
assert_eq!(
|
||||||
|
exhaustive_singles_footstool
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, _, footstool)| *footstool == Footstool::None)
|
||||||
|
.count(),
|
||||||
|
(52 * 52) - 103
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn footstool_deck_irrelevance() {
|
fn footstool_deck_irrelevance() {
|
||||||
// For a fixed Single, comparing to another deck's cards doesn't change if
|
// For a fixed Single, comparing to another deck's cards doesn't change
|
||||||
// it gets footstooled.
|
// if it gets footstooled.
|
||||||
let pivot = PlayingCard::new(0, Rank::Three, Suit::Club);
|
let piv_card = Card::make_playing_card(Rank::Three, Suit::Club);
|
||||||
let pivot = Card::PlayingCard(pivot);
|
let pivot = Single::new(piv_card).unwrap();
|
||||||
let pivot = Single(pivot);
|
|
||||||
|
|
||||||
for i in 1..10 {
|
for i in 1..10 {
|
||||||
let piv_copy = Single(Card::PlayingCard(PlayingCard {
|
let piv_copy = Single(Card::PlayingCard(PlayingCard {
|
||||||
deck: i,
|
deck: i,
|
||||||
..pivot.0.playing_card().unwrap()
|
..piv_card.playing_card().unwrap()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let piv_before = Single(Card::from(i64::from(piv_copy.0) - 1));
|
let piv_before = Single(Card::from(i64::from(piv_copy.0) - 1));
|
||||||
let piv_after = Single(Card::from(i64::from(piv_copy.0) + 1));
|
let piv_after = Single(Card::from(i64::from(piv_copy.0) + 1));
|
||||||
let piv_way_after = Single(Card::from(i64::from(piv_copy.0) + 2));
|
let piv_way_after = Single(Card::from(i64::from(piv_copy.0) + 2));
|
||||||
|
|
||||||
// TEST: a single may be footstooled by a single from another deck with
|
// TEST: a single may be footstooled by a single from another deck
|
||||||
// the same rank and suit.
|
// with the same rank and suit.
|
||||||
let (piv_on_piv_copy, _) = test_footstool(&pivot, &piv_copy);
|
let (piv_on_piv_copy, _) = test_footstool(&pivot, &piv_copy);
|
||||||
assert!(piv_on_piv_copy == Footstool::Full);
|
assert_eq!(
|
||||||
|
piv_on_piv_copy,
|
||||||
|
Footstool::Full,
|
||||||
|
"Expected {pivot}, {piv_copy} to full footstool."
|
||||||
|
);
|
||||||
|
|
||||||
// TEST: A single may be half footstooled by singles from another deck.
|
// TEST: A single may be half footstooled by singles from another
|
||||||
|
// deck.
|
||||||
let (piv_on_piv_before, _) = test_footstool(&pivot, &piv_before);
|
let (piv_on_piv_before, _) = test_footstool(&pivot, &piv_before);
|
||||||
assert!(piv_on_piv_before == Footstool::Half);
|
assert_eq!(
|
||||||
|
piv_on_piv_before,
|
||||||
|
Footstool::Half,
|
||||||
|
"Expected {pivot}, {piv_before} to half footstool."
|
||||||
|
);
|
||||||
|
|
||||||
let (_, piv_after_on_piv) = test_footstool(&pivot, &piv_after);
|
let (_, piv_after_on_piv) = test_footstool(&pivot, &piv_after);
|
||||||
assert!(piv_after_on_piv == Footstool::Half);
|
assert_eq!(
|
||||||
|
piv_after_on_piv,
|
||||||
|
Footstool::Half,
|
||||||
|
"Expected {pivot}, {piv_after} to half footstool."
|
||||||
|
);
|
||||||
|
|
||||||
// TEST: A single is still not footstooled by singles from other
|
// TEST: A single is still not footstooled by singles from other
|
||||||
// decks that aren't adjacent.
|
// decks that aren't adjacent.
|
||||||
let (piv_on_piv_way_after, _) =
|
let (piv_on_piv_way_after, _) =
|
||||||
test_footstool(&pivot, &piv_way_after);
|
test_footstool(&pivot, &piv_way_after);
|
||||||
assert!(piv_on_piv_way_after == Footstool::None);
|
assert_eq!(
|
||||||
|
piv_on_piv_way_after,
|
||||||
|
Footstool::None,
|
||||||
|
"Expected {pivot}, {piv_way_after} to not footstool."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user