use trueskill_tt::{ ConstantDrift, ConvergenceOptions, Game, GameOptions, Gaussian, InferenceError, Outcome, Rating, }; type R = Rating; fn default_rating() -> R { R::new( Gaussian::from_ms(25.0, 25.0 / 3.0), 25.0 / 6.0, ConstantDrift(25.0 / 300.0), ) } #[test] fn game_ranked_1v1_golden() { let a = default_rating(); let b = default_rating(); let g = Game::::ranked( &[&[a], &[b]], Outcome::winner(0, 2), &GameOptions::default(), ) .unwrap(); let p = g.posteriors(); assert!(p[0][0].mu() > 25.0); assert!(p[1][0].mu() < 25.0); assert!((p[0][0].sigma() - p[1][0].sigma()).abs() < 1e-6); } #[test] fn game_one_v_one_shortcut() { let a = default_rating(); let b = default_rating(); let (a_post, b_post) = Game::::one_v_one(&a, &b, Outcome::winner(0, 2), &GameOptions::default()).unwrap(); assert!(a_post.mu() > 25.0); assert!(b_post.mu() < 25.0); } #[test] fn game_ranked_rejects_bad_p_draw() { let a = R::new(Gaussian::default(), 1.0, ConstantDrift(0.0)); let err = Game::::ranked( &[&[a], &[a]], Outcome::winner(0, 2), &GameOptions { p_draw: 1.5, score_sigma: 1.0, convergence: ConvergenceOptions::default(), }, ) .unwrap_err(); assert!(matches!(err, InferenceError::InvalidProbability { .. })); } #[test] fn game_ranked_rejects_mismatched_ranks() { let a = R::new(Gaussian::default(), 1.0, ConstantDrift(0.0)); let err = Game::::ranked( &[&[a], &[a]], Outcome::ranking([0, 1, 2]), &GameOptions::default(), ) .unwrap_err(); assert!(matches!(err, InferenceError::MismatchedShape { .. })); } #[test] fn game_free_for_all_three_players() { let a = default_rating(); let b = default_rating(); let c = default_rating(); let g = Game::::free_for_all( &[&a, &b, &c], Outcome::ranking([0, 1, 2]), &GameOptions::default(), ) .unwrap(); let p = g.posteriors(); assert_eq!(p.len(), 3); assert!(p[0][0].mu() > p[1][0].mu()); assert!(p[1][0].mu() > p[2][0].mu()); } #[test] fn game_log_evidence_is_finite() { let a = default_rating(); let b = default_rating(); let g = Game::::ranked( &[&[a], &[b]], Outcome::winner(0, 2), &GameOptions::default(), ) .unwrap(); assert!(g.log_evidence().is_finite()); assert!(g.log_evidence() < 0.0); } /// `one_v_one` used to hardcode `GameOptions::default()`, so a 1v1 could /// never set `p_draw` and a drawn 1v1 was unreachable through it. #[test] fn one_v_one_honours_the_draw_probability_it_is_given() { let a = default_rating(); let b = default_rating(); // Default options still reject a draw, because the default p_draw is zero. let err = Game::::one_v_one(&a, &b, Outcome::draw(2), &GameOptions::default()) .expect_err("a draw needs a positive p_draw"); assert!(matches!( err, InferenceError::TieWithoutDrawProbability { .. } )); // With a draw probability supplied it succeeds — which was impossible // before the signature took options. let options = GameOptions { p_draw: 0.25, ..GameOptions::default() }; let (a_post, b_post) = Game::::one_v_one(&a, &b, Outcome::draw(2), &options) .expect("a draw is representable once p_draw is positive"); // A symmetric draw leaves the means alone and sharpens both sides. assert!((a_post.mu() - b_post.mu()).abs() < 1e-9); assert!(a_post.sigma() < 25.0 / 3.0); } /// Convergence options reach the 1v1 path too, not just `p_draw`. #[test] fn one_v_one_honours_convergence_options() { let a = default_rating(); let b = default_rating(); let options = GameOptions { convergence: ConvergenceOptions::default(), ..GameOptions::default() }; let (a_post, _) = Game::::one_v_one(&a, &b, Outcome::winner(0, 2), &options).unwrap(); assert!(a_post.mu() > 25.0); }