Add player matchup command

This commit is contained in:
Kiana Sheibani 2023-12-01 22:37:06 -05:00
parent c1604772a2
commit 5543f0a6b6
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
3 changed files with 144 additions and 11 deletions

View file

@ -105,6 +105,8 @@ enum DatasetSC {
enum PlayerSC { enum PlayerSC {
#[command(about = "Get info about a player")] #[command(about = "Get info about a player")]
Info { player: String }, Info { player: String },
#[command(about = "Matchup data between two players")]
Matchup { player1: String, player2: String },
} }
fn main() { fn main() {
@ -521,18 +523,125 @@ fn player_info(connection: &Connection, dataset: Option<String>, player: String)
println!("\x1b[1mVolatility:\x1b[0m {}", volatility); println!("\x1b[1mVolatility:\x1b[0m {}", volatility);
} }
// TODO: Finish
fn player_matchup(
connection: &Connection,
dataset: Option<String>,
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 // Sync
fn sync(datasets: Vec<String>, all: bool, auth_token: Option<String>) {
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<String>, all: bool) { fn sync(connection: &Connection, auth: String, datasets: Vec<String>, all: bool) {
let all_datasets = list_dataset_names(connection).unwrap(); let all_datasets = list_dataset_names(connection).unwrap();

View file

@ -10,6 +10,10 @@ use sqlite::*;
// Glicko-2 system calculation // 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 { fn time_adjust(periods: f64, old_dev_sq: f64, volatility: f64) -> f64 {
(old_dev_sq + periods * volatility * volatility).sqrt() (old_dev_sq + periods * volatility * volatility).sqrt()
} }
@ -45,7 +49,7 @@ fn glicko_adjust(
let period = metadata.period; let period = metadata.period;
let tau = metadata.tau; 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 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)); let variance = 1.0 / (g_val * g_val * exp_val * (1.0 - exp_val));

View file

@ -40,6 +40,26 @@ pub fn read_string() -> String {
line.trim().to_owned() 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 // Player Input
pub enum PlayerInput { pub enum PlayerInput {