From ff454e3cae410bc1ace64efaa7eb7081bd1cd241 Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Fri, 3 Nov 2023 22:39:18 -0400 Subject: [PATCH] Add set limit to algorithm --- src/datasets.rs | 124 +++++++++++++++++++++++++++++++----------------- src/main.rs | 60 ++++++++++++++++++++--- src/sync.rs | 14 +++--- 3 files changed, 140 insertions(+), 58 deletions(-) diff --git a/src/datasets.rs b/src/datasets.rs index 7f41443..ee13458 100644 --- a/src/datasets.rs +++ b/src/datasets.rs @@ -14,7 +14,9 @@ pub struct DatasetMetadata { pub country: Option, pub state: Option, + pub set_limit: u64, pub decay_rate: f64, + pub adj_decay_rate: f64, pub period: f64, pub tau: f64, } @@ -47,7 +49,9 @@ CREATE TABLE IF NOT EXISTS datasets ( game_name TEXT NOT NULL, country TEXT, state TEXT, + set_limit INTEGER NOT NULL, decay_rate REAL NOT NULL, + adj_decay_rate REAL NOT NULL, period REAL NOT NULL, tau REAL NOT NULL ) STRICT;"; @@ -85,7 +89,9 @@ pub fn list_datasets(connection: &Connection) -> sqlite::Result("game_name").to_owned(), country: r_.read::, _>("country").map(String::from), state: r_.read::, _>("state").map(String::from), + set_limit: r_.read::("set_limit") as u64, decay_rate: r_.read::("decay_rate"), + adj_decay_rate: r_.read::("adj_decay_rate"), period: r_.read::("period"), tau: r_.read::("tau"), }, @@ -111,25 +117,35 @@ pub fn new_dataset( dataset: &str, metadata: DatasetMetadata, ) -> sqlite::Result<()> { - let query1 = r#"INSERT INTO datasets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"#; + let query1 = r#"INSERT INTO datasets VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"#; let query2 = format!( r#"CREATE TABLE "{0}_players" ( id INTEGER PRIMARY KEY, name TEXT, prefix TEXT, + last_played INTEGER NOT NULL, deviation REAL NOT NULL, volatility REAL NOT NULL, + sets_won TEXT NOT NULL, + sets_count_won INTEGER AS (length(sets_won) - length(replace(sets_won, ',', ''))), sets_lost TEXT NOT NULL, - last_played INTEGER NOT NULL -); + sets_count_lost INTEGER AS (length(sets_lost) - length(replace(sets_lost, ',', ''))), + sets TEXT AS (sets_won || sets_lost), + sets_count INTEGER AS (sets_count_won + sets_count_lost) +) STRICT; CREATE TABLE "{0}_network" ( player_A INTEGER NOT NULL, player_B INTEGER NOT NULL, advantage REAL NOT NULL, + sets_A TEXT NOT NULL, + sets_count_A INTEGER AS (length(sets_A) - length(replace(sets_A, ',', ''))), sets_B TEXT NOT NULL, + sets_count_B INTEGER AS (length(sets_B) - length(replace(sets_B, ',', ''))), + sets TEXT AS (sets_A || sets_B), + sets_count INTEGER AS (sets_count_A + sets_count_B), UNIQUE (player_A, player_B), CHECK (player_A < player_B), @@ -144,9 +160,10 @@ CREATE INDEX "{0}_network_B" ON "{0}_network" (player_B); CREATE VIEW "{0}_view" - (player_A_id, player_B_id, player_A_name, player_B_name, advantage, sets_A, sets_B, sets) AS + (player_A_id, player_B_id, player_A_name, player_B_name, advantage, + sets_A, sets_count_A, sets_B, sets_count_B, sets, sets_count) AS SELECT players_A.id, players_B.id, players_A.name, players_B.name, advantage, - sets_A, sets_B, sets_A || sets_B FROM "{0}_network" + sets_A, sets_count_A, sets_B, sets_count_B, network.sets, network.sets_count FROM "{0}_network" network INNER JOIN "{0}_players" players_A ON player_A = players_A.id INNER JOIN "{0}_players" players_B ON player_B = players_B.id;"#, dataset @@ -161,9 +178,11 @@ CREATE VIEW "{0}_view" .bind((4, &metadata.game_name[..]))? .bind((5, metadata.country.as_deref()))? .bind((6, metadata.state.as_deref()))? - .bind((7, metadata.decay_rate))? - .bind((8, metadata.period))? - .bind((9, metadata.tau))? + .bind((7, metadata.set_limit as i64))? + .bind((8, metadata.decay_rate))? + .bind((9, metadata.adj_decay_rate))? + .bind((10, metadata.period))? + .bind((11, metadata.tau))? .try_for_each(|x| x.map(|_| ()))?; connection.execute(query2) @@ -188,7 +207,9 @@ pub fn get_metadata( game_name: r_.read::<&str, _>("game_name").to_owned(), country: r_.read::, _>("country").map(String::from), state: r_.read::, _>("state").map(String::from), + set_limit: r_.read::("set_limit") as u64, decay_rate: r_.read::("decay_rate"), + adj_decay_rate: r_.read::("adj_decay_rate"), period: r_.read::("period"), tau: r_.read::("tau"), }) @@ -222,7 +243,8 @@ pub fn add_players( ) -> sqlite::Result<()> { let query = format!( r#"INSERT OR IGNORE INTO "{}_players" - VALUES (?, ?, ?, 2.01, 0.06, '', '', ?)"#, + (id, name, prefix, last_played, deviation, volatility, sets_won, sets_lost) + VALUES (?, ?, ?, ?, 2.01, 0.06, '', '')"#, dataset ); @@ -318,6 +340,7 @@ pub fn insert_advantage( ) -> sqlite::Result<()> { let query = format!( r#"INSERT INTO "{}_network" + (player_A, player_B, advantage, sets_A, sets_B) VALUES (min(:a, :b), max(:a, :b), iif(:a > :b, -:v, :v), '', '')"#, dataset ); @@ -329,49 +352,60 @@ pub fn insert_advantage( statement.into_iter().try_for_each(|x| x.map(|_| ())) } -pub fn adjust_advantage( - connection: &Connection, - dataset: &str, - player1: PlayerId, - player2: PlayerId, - adjust: f64, - winner: usize, - set: SetId, -) -> sqlite::Result<()> { - let query = format!( - r#"UPDATE "{}_network" - SET advantage = advantage + iif(:a > :b, -:v, :v), - sets_A = iif(:w = (:a > :b), sets_A || :set || ',', sets_A), - sets_B = iif(:w = (:b > :a), sets_B || :set || ',', sets_B) - WHERE player_A = min(:a, :b) AND player_B = max(:a, :b)"#, - dataset - ); - - let mut statement = connection.prepare(&query)?; - statement.bind((":a", player1.0 as i64))?; - statement.bind((":b", player2.0 as i64))?; - statement.bind((":v", adjust))?; - statement.bind((":w", winner as i64))?; - statement.bind((":set", set.0 as i64))?; - statement.into_iter().try_for_each(|x| x.map(|_| ())) -} - pub fn adjust_advantages( connection: &Connection, dataset: &str, - player: PlayerId, - adjust: f64, + set: SetId, + player1: PlayerId, + player2: PlayerId, + winner: usize, + adjust1: f64, + adjust2: f64, + decay_rate: f64, + adj_decay_rate: f64, + set_limit: u64, ) -> sqlite::Result<()> { - let query = format!( + let query1 = format!( r#"UPDATE "{}_network" - SET advantage = advantage + iif(:pl = player_A, -:v, :v) - WHERE player_A = :pl OR player_B = :pl"#, +SET advantage = advantage + iif(:pl = player_A, -:v, :v) + * iif(sets_count >= :sl, :d, :da) +WHERE (player_A = :pl AND player_B != :plo) + OR (player_B = :pl AND player_A != :plo)"#, + dataset + ); + let query2 = format!( + r#"UPDATE "{}_network" +SET advantage = advantage + iif(:a > :b, -:v, :v), + sets_A = iif(:w = (:a > :b), sets_A || :set || ',', sets_A), + sets_B = iif(:w = (:b > :a), sets_B || :set || ',', sets_B) +WHERE player_A = min(:a, :b) AND player_B = max(:a, :b)"#, dataset ); - let mut statement = connection.prepare(&query)?; - statement.bind((":pl", player.0 as i64))?; - statement.bind((":v", adjust))?; + let mut statement = connection.prepare(&query1)?; + statement.bind((":pl", player1.0 as i64))?; + statement.bind((":plo", player2.0 as i64))?; + statement.bind((":v", adjust1))?; + statement.bind((":sl", set_limit as i64))?; + statement.bind((":d", decay_rate))?; + statement.bind((":da", adj_decay_rate))?; + statement.into_iter().try_for_each(|x| x.map(|_| ()))?; + + statement = connection.prepare(&query1)?; + statement.bind((":pl", player2.0 as i64))?; + statement.bind((":plo", player1.0 as i64))?; + statement.bind((":v", adjust2))?; + statement.bind((":sl", set_limit as i64))?; + statement.bind((":d", decay_rate))?; + statement.bind((":da", adj_decay_rate))?; + statement.into_iter().try_for_each(|x| x.map(|_| ()))?; + + statement = connection.prepare(&query2)?; + statement.bind((":a", player1.0 as i64))?; + statement.bind((":b", player2.0 as i64))?; + statement.bind((":v", adjust2 - adjust1))?; + statement.bind((":w", winner as i64))?; + statement.bind((":set", set.0 as i64))?; statement.into_iter().try_for_each(|x| x.map(|_| ())) } @@ -526,7 +560,9 @@ CREATE TABLE IF NOT EXISTS datasets ( game_name: String::from("Test Game"), country: None, state: None, + set_limit: 0, decay_rate: 0.5, + adj_decay_rate: 0.5, period: (3600 * 24 * 30) as f64, tau: 0.2, } diff --git a/src/main.rs b/src/main.rs index 3e18354..de68091 100644 --- a/src/main.rs +++ b/src/main.rs @@ -247,10 +247,29 @@ State/province to track ratings for (leave empty for none): " None }; + // Set Limit + + let mut set_limit = 0; + print!( + " +\x1b[4mSet Limit\x1b[0m + +TODO + +Set limit (default 0): " + ); + let set_limit_input = read_string(); + if !set_limit_input.is_empty() { + set_limit = set_limit_input + .parse::() + .unwrap_or_else(|_| error("Input is not an integer", 1)); + } + // Advanced Options // Defaults - let mut decay_rate = 0.5; + let mut decay_rate = 0.8; + let mut adj_decay_rate = 0.5; let mut period_days = 40.0; let mut tau = 0.4; @@ -268,13 +287,38 @@ then it is assumed that a player's skill against one opponent always carries over to all other opponents. If the decay rate is 0, then all player match-ups are assumed to be independent of each other. -Network decay rate (default 0.5): " +Network decay rate (default 0.8): " ); let decay_rate_input = read_string(); if !decay_rate_input.is_empty() { decay_rate = decay_rate_input .parse::() - .unwrap_or_else(|_| error("Not a number", 1)); + .unwrap_or_else(|_| error("Input is not a number", 1)); + if decay_rate < 0.0 || decay_rate > 1.0 { + error("Input is not between 0 and 1", 1); + } + } + + // Decay Rate + + print!( + " +\x1b[4mAdjusted Network Decay Rate\x1b[0m + +TODO + +This should be lower than the regular network decay rate. + +Adjusted network decay rate (default 0.5): " + ); + let adj_decay_rate_input = read_string(); + if !adj_decay_rate_input.is_empty() { + adj_decay_rate = adj_decay_rate_input + .parse::() + .unwrap_or_else(|_| error("Input is not a number", 1)); + if decay_rate < 0.0 || decay_rate > 1.0 { + error("Input is not between 0 and 1", 1); + } } // Rating Period @@ -294,7 +338,7 @@ Rating period (in days, default 40): " if !period_input.is_empty() { period_days = period_input .parse::() - .unwrap_or_else(|_| error("Not a number", 1)); + .unwrap_or_else(|_| error("Input is not a number", 1)); } // Tau coefficient @@ -319,7 +363,7 @@ Tau constant (default 0.4): " if !tau_input.is_empty() { tau = tau_input .parse::() - .unwrap_or_else(|_| error("Not a number", 1)); + .unwrap_or_else(|_| error("Input is not a number", 1)); } } @@ -336,7 +380,9 @@ Tau constant (default 0.4): " game_name, country, state, + set_limit, decay_rate, + adj_decay_rate, period: (3600 * 24) as f64 * period_days, tau, }, @@ -371,7 +417,6 @@ fn sync(datasets: Vec, all: bool, auth_token: Option) { let all_datasets = list_dataset_names(&connection).unwrap(); - #[allow(unused_must_use)] let datasets = if all { all_datasets } else if datasets.is_empty() { @@ -398,7 +443,8 @@ fn sync(datasets: Vec, all: bool, auth_token: Option) { .unwrap_or_else(|| error(&format!("Dataset {} does not exist!", dataset), 1)); sync_dataset(&connection, &dataset, dataset_config, &auth) - .unwrap_or_else(|_| error("Error communicating with SQLite", 2)); + .expect("Error communicating with SQLite"); + // .unwrap_or_else(|_| error("Error communicating with SQLite", 2)); update_last_sync(&connection, &dataset).expect("Error communicating with SQLite"); } diff --git a/src/sync.rs b/src/sync.rs index 38b5d0d..8a6632b 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -41,7 +41,6 @@ fn glicko_adjust( time: u64, metadata: &DatasetMetadata, ) -> (f64, f64, f64) { - // TODO: Turn this into dataset metadata let period = metadata.period; let tau = metadata.tau; @@ -253,17 +252,18 @@ fn update_from_set( results.id, )?; - let decay_rate = metadata.decay_rate; - adjust_advantages(connection, dataset, player1, decay_rate * adjust1)?; - adjust_advantages(connection, dataset, player2, decay_rate * adjust2)?; - adjust_advantage( + adjust_advantages( connection, dataset, + results.id, player1, player2, - (1.0 - decay_rate) * (adjust2 - adjust1), results.winner, - results.id, + adjust1, + adjust2, + metadata.decay_rate, + metadata.adj_decay_rate, + metadata.set_limit, ) }