From 5543f0a6b6a1c1d4a2a68e1aaeac75b7131ba577 Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Fri, 1 Dec 2023 22:37:06 -0500 Subject: [PATCH] Add player matchup command --- src/main.rs | 129 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/sync.rs | 6 ++- src/util.rs | 20 ++++++++ 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index de083e2..85793a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,6 +105,8 @@ enum DatasetSC { enum PlayerSC { #[command(about = "Get info about a player")] Info { player: String }, + #[command(about = "Matchup data between two players")] + Matchup { player1: String, player2: String }, } fn main() { @@ -521,18 +523,125 @@ fn player_info(connection: &Connection, dataset: Option, player: String) println!("\x1b[1mVolatility:\x1b[0m {}", volatility); } +// TODO: Finish +fn player_matchup( + connection: &Connection, + dataset: Option, + player1: String, + player2: String, +) { + let dataset = dataset.unwrap_or_else(|| String::from("default")); + + let PlayerData { + id: player1, + name: name1, + prefix: prefix1, + discrim: discrim1, + } = get_player_from_input(connection, player1) + .unwrap_or_else(|_| error("Could not find player", 1)); + + let (deviation1, _, _) = get_player_rating_data(connection, &dataset, player1) + .unwrap_or_else(|_| error("Could not find player", 1)); + + let PlayerData { + id: player2, + name: name2, + prefix: prefix2, + discrim: discrim2, + } = get_player_from_input(connection, player2) + .unwrap_or_else(|_| error("Could not find player", 1)); + + let (deviation2, _, _) = get_player_rating_data(connection, &dataset, player2) + .unwrap_or_else(|_| error("Could not find player", 1)); + + let (hypothetical, advantage) = get_advantage(connection, &dataset, player1, player2) + .expect("Error communicating with SQLite") + .map(|x| (false, x)) + .unwrap_or_else(|| { + let metadata = get_metadata(connection, &dataset) + .expect("Error communicating with SQLite") + .unwrap_or_else(|| error("Dataset not found", 1)); + ( + true, + hypothetical_advantage( + connection, + &dataset, + player1, + player2, + metadata.set_limit, + metadata.decay_rate, + metadata.adj_decay_rate, + ) + .expect("Error communicating with SQLite"), + ) + }); + + let probability = 1.0 + / (1.0 + + f64::exp( + g_func((deviation1 * deviation1 + deviation2 * deviation2).sqrt()) * advantage, + )); + + let color = ansi_num_color(advantage, 0.2, 2.0); + let other_color = ansi_num_color(-advantage, 0.2, 2.0); + + let len1 = prefix1.as_deref().map(|s| s.len() + 1).unwrap_or(0) + name1.len(); + let len2 = prefix2.as_deref().map(|s| s.len() + 1).unwrap_or(0) + name2.len(); + + println!(); + if let Some(pre) = prefix1 { + print!("\x1b[2m{}\x1b[22m ", pre); + } + print!( + "\x1b[4m\x1b]8;;https://www.start.gg/user/{}\x1b\\\ +\x1b[1m{}\x1b[22m\x1b]8;;\x1b\\\x1b[0m - ", + discrim1, name1 + ); + if let Some(pre) = prefix2 { + print!("\x1b[2m{}\x1b[22m ", pre); + } + println!( + "\x1b[4m\x1b]8;;https://www.start.gg/user/{}\x1b\\\ +\x1b[1m{}\x1b[22m\x1b]8;;\x1b\\\x1b[0m", + discrim2, name2 + ); + + println!( + "\x1b[1m\x1b[{4}m{0:>2$}\x1b[0m - \x1b[1m\x1b[{5}m{1:<3$}\x1b[0m", + format!("{:.1}%", probability * 100.0), + format!("{:.1}%", (1.0 - probability) * 100.0), + len1, + len2, + other_color, + color + ); + + if hypothetical { + println!( + "\n\x1b[1mHypothetical Advantage: \x1b[{1}m{0:+.4}\x1b[0m", + advantage, color + ); + } else { + println!( + "\n\x1b[1mAdvantage: \x1b[{1}m{0:+.4}\x1b[0m", + advantage, color + ); + + let (a, b) = get_matchup_set_counts(connection, &dataset, player1, player2) + .expect("Error communicating with SQLite"); + + println!( + "\n\x1b[1mSet Count:\x1b[0m {} - {} ({:.3}% - {:.3}%)", + a, + b, + (a as f64 / (a + b) as f64) * 100.0, + (b as f64 / (a + b) as f64) * 100.0 + ); + } +} + // Sync -fn sync(datasets: Vec, all: bool, auth_token: Option) { - let config_dir = dirs::config_dir().unwrap(); - - let auth = auth_token - .or_else(|| get_auth_token(&config_dir)) - .unwrap_or_else(|| error("Access token not provided", 1)); - - let connection = - open_datasets(&config_dir).unwrap_or_else(|_| error("Could not open datasets file", 2)); - fn sync(connection: &Connection, auth: String, datasets: Vec, all: bool) { let all_datasets = list_dataset_names(connection).unwrap(); diff --git a/src/sync.rs b/src/sync.rs index 7d9e6e9..f5d8e1e 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -10,6 +10,10 @@ use sqlite::*; // Glicko-2 system calculation +pub fn g_func(dev: f64) -> f64 { + 1.0 / (1.0 + 3.0 * dev * dev / PI / PI).sqrt() +} + fn time_adjust(periods: f64, old_dev_sq: f64, volatility: f64) -> f64 { (old_dev_sq + periods * volatility * volatility).sqrt() } @@ -45,7 +49,7 @@ fn glicko_adjust( let period = metadata.period; let tau = metadata.tau; - let g_val = 1.0 / (1.0 + 3.0 * other_deviation * other_deviation / PI / PI).sqrt(); + let g_val = g_func(other_deviation); let exp_val = 1.0 / (1.0 + f64::exp(-g_val * advantage)); let variance = 1.0 / (g_val * g_val * exp_val * (1.0 - exp_val)); diff --git a/src/util.rs b/src/util.rs index 9729759..ceda0f1 100644 --- a/src/util.rs +++ b/src/util.rs @@ -40,6 +40,26 @@ pub fn read_string() -> String { line.trim().to_owned() } +pub fn ansi_num_color(num: f64, threshold1: f64, threshold2: f64) -> &'static str { + let sign = num > 0.0; + let num_abs = num.abs(); + let severity = if num_abs < threshold1 { + 0 + } else if num_abs < threshold2 { + 1 + } else { + 2 + }; + + match (sign, severity) { + (false, 1) => "31", + (true, 1) => "32", + (false, 2) => "91", + (true, 2) => "92", + _ => "39", + } +} + // Player Input pub enum PlayerInput {