diff --git a/bin/bot/src/config.rs b/bin/bot/src/config.rs index b1cf969..0ef758c 100644 --- a/bin/bot/src/config.rs +++ b/bin/bot/src/config.rs @@ -1,3 +1,4 @@ +use crate::evaluator::MatchStatsEvaluator; use serde::Deserialize; use std::path::{Path, PathBuf}; use tokio::fs::read_to_string; @@ -8,6 +9,8 @@ pub struct Config { pub discord_token: String, pub rgapi_key: String, pub message_templates_path: PathBuf, + // TODO: Consider making this also a path + pub match_stats_evaluator: MatchStatsEvaluator, } impl Config { diff --git a/bin/bot/src/evaluator.rs b/bin/bot/src/evaluator.rs deleted file mode 100644 index ef7ff3d..0000000 --- a/bin/bot/src/evaluator.rs +++ /dev/null @@ -1,103 +0,0 @@ -use chrono::TimeDelta; -use std::collections::HashMap; -use the_collector_db::model::{Match, SummonerMatch}; - -// TODO: Move to a configuration file -// TODO: Research values to use here -lazy_static::lazy_static! { - static ref THRESHOLDS: HashMap = { - HashMap::from([ - (Role::Top, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - (Role::Jungle, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - (Role::Mid, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - (Role::Bot, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - (Role::Support, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - (Role::Other, Threshold { weighted_kda: WeightedKda(1.5), minutes_per_death: MinutesPerDeath(3.5) }), - ]) - }; -} - -#[derive(Debug, PartialEq, Eq, Hash)] -enum Role { - Top, - Jungle, - Mid, - Bot, - Support, - Other, -} - -#[derive(Debug, PartialEq, PartialOrd)] -struct WeightedKda(f32); - -impl From<&SummonerMatch> for WeightedKda { - fn from(stats: &SummonerMatch) -> Self { - let inner = (stats.kills as f32 + (stats.assists as f32 * 0.5)) / stats.deaths as f32; - Self(inner) - } -} - -#[derive(Debug, PartialEq, PartialOrd)] -struct MinutesPerDeath(f32); - -impl From<(&SummonerMatch, &Match)> for MinutesPerDeath { - fn from(data: (&SummonerMatch, &Match)) -> Self { - let minutes = TimeDelta::milliseconds(data.1.duration).num_minutes(); - let inner = minutes as f32 / data.0.deaths as f32; - Self(inner) - } -} - -/// A threshold of what is considered an int and what is not. The values -/// in a threshold are inclusive to something being evaluated positively -/// as an int. -/// -/// For example, if the threshold has a deaths value of 10 and the stat -/// block being tested has a deaths value of 10, then it is evaluated -/// positively as an int. -#[derive(Debug)] -struct Threshold { - weighted_kda: WeightedKda, - minutes_per_death: MinutesPerDeath, -} - -impl Threshold { - fn is_int(&self, stats: &SummonerMatch, match_data: &Match) -> bool { - if self.weighted_kda >= stats.into() { - return true; - } - if self.minutes_per_death >= (stats, match_data).into() { - return true; - } - false - } -} - -#[derive(Debug)] -pub struct MatchStatsEvaluator { - thresholds: &'static HashMap, -} - -impl MatchStatsEvaluator { - pub fn new() -> Self { - Self { - thresholds: &THRESHOLDS, - } - } - - pub fn is_int(&self, match_stats: &SummonerMatch, match_data: &Match) -> bool { - match match_stats - .position - .as_ref() - .unwrap_or(&String::from("Other")) - { - val if val == "TOP" => &self.thresholds[&Role::Top], - val if val == "JUNGLE" => &self.thresholds[&Role::Jungle], - val if val == "MIDDLE" => &self.thresholds[&Role::Mid], - val if val == "BOTTOM" => &self.thresholds[&Role::Bot], - val if val == "UTILITY" => &self.thresholds[&Role::Support], - _ => &self.thresholds[&Role::Other], - } - .is_int(match_stats, match_data) - } -} diff --git a/bin/bot/src/evaluator/mod.rs b/bin/bot/src/evaluator/mod.rs new file mode 100644 index 0000000..881b3fb --- /dev/null +++ b/bin/bot/src/evaluator/mod.rs @@ -0,0 +1,55 @@ +use serde::Deserialize; +use std::collections::HashMap; +use the_collector_db::model::{Match, SummonerMatch}; +use weight::{WeightedKda, Weights}; + +pub mod weight; + +#[derive(Debug, PartialEq, Eq, Hash, Deserialize)] +#[serde(rename_all = "snake_case")] +enum Role { + Top, + Jungle, + Mid, + Bot, + Support, + Other, +} + +impl From<&str> for Role { + fn from(value: &str) -> Self { + // Match to values from Riot API + match value.to_lowercase().as_str() { + "top" => Role::Top, + "jungle" => Role::Jungle, + "middle" => Role::Mid, + "bottom" => Role::Bot, + "utility" => Role::Support, + _ => Role::Other, + } + } +} + +#[derive(Debug, Deserialize, Default)] +pub struct MatchStatsEvaluator { + kda_weights: HashMap, + kda_limit: WeightedKda, +} + +impl MatchStatsEvaluator { + pub fn is_int(&self, match_stats: &SummonerMatch, _match_data: &Match) -> bool { + // Don't send a message for insignificant scoreline + if match_stats.deaths <= 4 && match_stats.kills >= 1 { + return false; + } + + // If their weighted KDA is less than <= 0, evaluate as an int + let role = match_stats + .position + .as_ref() + .map(String::as_str) + .unwrap_or("other") + .into(); + self.kda_weights[&role].calculate_weighted_kda(match_stats) <= self.kda_limit + } +} diff --git a/bin/bot/src/evaluator/weight.rs b/bin/bot/src/evaluator/weight.rs new file mode 100644 index 0000000..b351535 --- /dev/null +++ b/bin/bot/src/evaluator/weight.rs @@ -0,0 +1,21 @@ +use serde::Deserialize; +use the_collector_db::model::SummonerMatch; + +#[derive(Debug, PartialEq, PartialOrd, Deserialize, Default)] +pub struct WeightedKda(pub f32); + +#[derive(Debug, PartialEq, PartialOrd, Deserialize)] +pub struct Weights { + kill_weight: f32, + death_weight: f32, + assist_weight: f32, +} + +impl Weights { + pub fn calculate_weighted_kda(&self, stats: &SummonerMatch) -> WeightedKda { + let inner = (self.kill_weight * stats.kills as f32) + + (self.death_weight * stats.deaths as f32) + + (self.assist_weight * stats.assists as f32); + WeightedKda(inner) + } +} diff --git a/bin/bot/src/main.rs b/bin/bot/src/main.rs index 355154b..548f396 100644 --- a/bin/bot/src/main.rs +++ b/bin/bot/src/main.rs @@ -2,7 +2,6 @@ use anyhow::Context as _; use command::Data; use config::Config; use ddragon::DataDragon; -use evaluator::MatchStatsEvaluator; use handler::bot::BotHandler; use handler::message::MessageHandler; use message::MessageBuilder; @@ -81,7 +80,7 @@ async fn main() -> anyhow::Result<()> { let summoner_match_handler = MessageHandler { db_handler: db_handler.clone(), subscriber: IpcSubscriber::new(IPC_SUMMONER_MATCH_PATH)?, - evaluator: MatchStatsEvaluator::new(), + evaluator: config.match_stats_evaluator, message_builder: MessageBuilder::new(config.message_templates_path).await?, http: client.http.clone(), };