use proptest::prelude::*;
use yahtzee_engine::{Category, Dice, Game, Keep, Phase, TurnAction};
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0 >> 33
}
fn below(&mut self, bound: usize) -> usize {
self.next() as usize % bound
}
}
fn random_roll(lcg: &mut Lcg, kept: Keep) -> Dice {
let mut counts = kept.counts();
for _ in kept.len()..5 {
counts[lcg.below(6)] += 1;
}
Dice::from_counts(counts).expect("kept dice plus rerolls make five")
}
proptest! {
#[test]
fn random_legal_play_reaches_a_consistent_full_card(
seed in any::<u64>(),
players in 1usize..=3,
) {
let mut lcg = Lcg(seed);
let mut game = Game::new(players);
let mut turns = 0u32;
while !game.is_over() {
match game.phase() {
Phase::AwaitingRoll => {
let roll = random_roll(&mut lcg, game.kept());
game.roll(roll).expect("a roll is due");
}
Phase::AwaitingAction => {
let view = game.view();
let dice = view.dice();
if view.rolls_left() > 0 && lcg.next().is_multiple_of(2) {
let keeps: Vec<Keep> = dice.keeps().collect();
let keep = keeps[lcg.below(keeps.len())];
game.act(TurnAction::Reroll(keep)).expect("keep is legal");
continue;
}
let legal: Vec<Category> = view.legal_categories().iter().collect();
prop_assert!(!legal.is_empty());
let category = legal[lcg.below(legal.len())];
let seat = view.seat();
let expected = view.state().apply(category, dice).expect("legal");
let delta = game
.act(TurnAction::Score(category))
.expect("category is legal")
.expect("scoring returns a delta");
prop_assert_eq!(delta, expected);
prop_assert_eq!(game.scorecard(seat).state(), delta.next);
turns += 1;
}
Phase::Finished => unreachable!("checked by the loop"),
}
}
prop_assert_eq!(turns as usize, 13 * players);
for card in game.scorecards() {
prop_assert!(card.is_full());
let boxes: u16 = Category::ALL
.iter()
.map(|&c| u16::from(card.get(c).expect("full card")))
.sum();
let rebuilt = boxes
+ u16::from(card.upper_bonus())
+ 100 * u16::from(card.yahtzee_bonuses());
prop_assert_eq!(card.total(), rebuilt);
}
}
#[cfg(feature = "rand")]
#[test]
fn the_heuristic_bot_plays_legal_games(seed in any::<u64>()) {
use rand::SeedableRng;
use yahtzee_engine::{HeuristicBot, Strategy, play_game};
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut game = Game::new(2);
let mut bots = [HeuristicBot::new(), HeuristicBot::new()];
let [a, b] = &mut bots;
let mut strategies: [&mut dyn Strategy; 2] = [a, b];
let totals = play_game(&mut game, &mut strategies, &mut rng)
.expect("the bot never proposes an illegal action");
prop_assert_eq!(totals.len(), 2);
prop_assert!(game.scorecards().iter().all(|card| card.is_full()));
}
}