// Demo module for generating test data and running simulations use crate::glicko::{GlickoRating, Glicko2Calculator, calculate_weighted_score}; use fake::faker::name::en::Name; use fake::Fake; extern crate rand; use rand::Rng; use rand::seq::SliceRandom; #[derive(Debug, Clone)] pub struct Player { pub name: String, pub email: String, pub singles: GlickoRating, pub doubles: GlickoRating, pub true_skill: f64, // Hidden skill level for simulation } impl Player { pub fn new_random() -> Self { let mut rng = rand::thread_rng(); let name: String = Name().fake(); let email = format!("{}@example.com", name.to_lowercase().replace(' ', ".")); // Random true skill between 1200-1800 let true_skill = rng.gen_range(1200.0..1800.0); Self { name, email, singles: GlickoRating::new_player(), doubles: GlickoRating::new_player(), true_skill, } } } #[derive(Debug)] pub struct Match { pub match_type: MatchType, pub team1: Vec, // Player indices pub team2: Vec, pub team1_score: i32, pub team2_score: i32, } #[derive(Debug, Clone, Copy)] pub enum MatchType { Singles, Doubles, } /// Simulate a match outcome based on true skill levels pub fn simulate_match( team1_skills: &[f64], team2_skills: &[f64], ) -> (i32, i32) { let mut rng = rand::thread_rng(); // Team skill is average let team1_avg: f64 = team1_skills.iter().sum::() / team1_skills.len() as f64; let team2_avg: f64 = team2_skills.iter().sum::() / team2_skills.len() as f64; // Win probability based on skill difference let skill_diff = team1_avg - team2_avg; let win_prob = 1.0 / (1.0 + 10_f64.powf(-skill_diff / 400.0)); // Determine winner let team1_wins = rng.gen::() < win_prob; // Generate score (pickleball to 11) if team1_wins { let margin = rng.gen_range(1..10); // 1-9 point margin let team2_score = 11 - margin; (11, team2_score) } else { let margin = rng.gen_range(1..10); let team1_score = 11 - margin; (team1_score, 11) } } /// Generate a session of matches pub fn generate_session(players: &mut [Player], num_matches: usize) -> Vec { let mut rng = rand::thread_rng(); let mut matches = Vec::new(); let calc = Glicko2Calculator::new(); for _ in 0..num_matches { // Randomly choose singles or doubles let match_type = if rng.gen_bool(0.5) { MatchType::Singles } else { MatchType::Doubles }; let match_result = match match_type { MatchType::Singles => { // Pick 2 random players let p1_idx = rng.gen_range(0..players.len()); let mut p2_idx = rng.gen_range(0..players.len()); while p2_idx == p1_idx { p2_idx = rng.gen_range(0..players.len()); } let (team1_score, team2_score) = simulate_match( &[players[p1_idx].true_skill], &[players[p2_idx].true_skill], ); // Update ratings let p1_outcome = if team1_score > team2_score { calculate_weighted_score(1.0, team1_score, team2_score) } else { calculate_weighted_score(0.0, team2_score, team1_score) }; let p2_outcome = if team2_score > team1_score { calculate_weighted_score(1.0, team2_score, team1_score) } else { calculate_weighted_score(0.0, team1_score, team2_score) }; players[p1_idx].singles = calc.update_rating( &players[p1_idx].singles, &[(players[p2_idx].singles, p1_outcome)], ); players[p2_idx].singles = calc.update_rating( &players[p2_idx].singles, &[(players[p1_idx].singles, p2_outcome)], ); Match { match_type, team1: vec![p1_idx], team2: vec![p2_idx], team1_score, team2_score, } } MatchType::Doubles => { // Pick 4 random players let mut indices: Vec = (0..players.len()).collect(); indices.shuffle(&mut rng); let team1_indices = vec![indices[0], indices[1]]; let team2_indices = vec![indices[2], indices[3]]; let team1_skills: Vec = team1_indices.iter() .map(|&i| players[i].true_skill) .collect(); let team2_skills: Vec = team2_indices.iter() .map(|&i| players[i].true_skill) .collect(); let (team1_score, team2_score) = simulate_match(&team1_skills, &team2_skills); // Update doubles ratings (simplified - each player vs team average) let team1_won = team1_score > team2_score; for &idx in &team1_indices { let outcome = if team1_won { calculate_weighted_score(1.0, team1_score, team2_score) } else { calculate_weighted_score(0.0, team2_score, team1_score) }; // Simulate vs average opponent let avg_opponent = GlickoRating { rating: team2_indices.iter().map(|&i| players[i].doubles.rating).sum::() / 2.0, rd: team2_indices.iter().map(|&i| players[i].doubles.rd).sum::() / 2.0, volatility: 0.06, }; players[idx].doubles = calc.update_rating( &players[idx].doubles, &[(avg_opponent, outcome)], ); } for &idx in &team2_indices { let outcome = if !team1_won { calculate_weighted_score(1.0, team2_score, team1_score) } else { calculate_weighted_score(0.0, team1_score, team2_score) }; let avg_opponent = GlickoRating { rating: team1_indices.iter().map(|&i| players[i].doubles.rating).sum::() / 2.0, rd: team1_indices.iter().map(|&i| players[i].doubles.rd).sum::() / 2.0, volatility: 0.06, }; players[idx].doubles = calc.update_rating( &players[idx].doubles, &[(avg_opponent, outcome)], ); } Match { match_type, team1: team1_indices, team2: team2_indices, team1_score, team2_score, } } }; matches.push(match_result); } matches } /// Print session summary pub fn print_summary(players: &[Player], matches: &[Match]) { println!("\nšŸ“ Session Summary"); println!("=================="); println!("\n{} matches played\n", matches.len()); println!("Match Results:"); for (i, m) in matches.iter().enumerate() { match m.match_type { MatchType::Singles => { let p1 = &players[m.team1[0]]; let p2 = &players[m.team2[0]]; let winner = if m.team1_score > m.team2_score { &p1.name } else { &p2.name }; println!( " {}. Singles: {} vs {} → {} wins {}-{}", i + 1, p1.name, p2.name, winner, m.team1_score.max(m.team2_score), m.team1_score.min(m.team2_score) ); } MatchType::Doubles => { let t1_names: Vec<&str> = m.team1.iter().map(|&i| players[i].name.as_str()).collect(); let t2_names: Vec<&str> = m.team2.iter().map(|&i| players[i].name.as_str()).collect(); let winner = if m.team1_score > m.team2_score { "Team 1" } else { "Team 2" }; println!( " {}. Doubles: {} vs {} → {} wins {}-{}", i + 1, t1_names.join(" & "), t2_names.join(" & "), winner, m.team1_score.max(m.team2_score), m.team1_score.min(m.team2_score) ); } } } println!("\nšŸ“Š Singles Leaderboard:"); let mut singles_sorted = players.to_vec(); singles_sorted.sort_by(|a, b| b.singles.rating.partial_cmp(&a.singles.rating).unwrap()); for (i, p) in singles_sorted.iter().take(10).enumerate() { println!( " {}. {} - {:.1} (RD: {:.1}, σ: {:.3})", i + 1, p.name, p.singles.rating, p.singles.rd, p.singles.volatility ); } println!("\nšŸ“Š Doubles Leaderboard:"); let mut doubles_sorted = players.to_vec(); doubles_sorted.sort_by(|a, b| b.doubles.rating.partial_cmp(&a.doubles.rating).unwrap()); for (i, p) in doubles_sorted.iter().take(10).enumerate() { println!( " {}. {} - {:.1} (RD: {:.1}, σ: {:.3})", i + 1, p.name, p.doubles.rating, p.doubles.rd, p.doubles.volatility ); } }