From f785062d70d78a3bca4a5ba94211a078a16c94ed Mon Sep 17 00:00:00 2001 From: Simon Bernier St-Pierre Date: Thu, 7 Mar 2019 23:40:20 -0500 Subject: [PATCH] simulation works --- src/main.rs | 31 +++++++--- src/markdown.rs | 0 src/nhlapi.rs | 5 +- src/simulation.rs | 152 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/markdown.rs diff --git a/src/main.rs b/src/main.rs index 858dbe6..c6285a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,13 @@ impl Api { .expect("team abbrev not found") } + pub fn get_team_by_id(&self, team_id: u32) -> &Team { + self.teams + .iter() + .find(|t| t.id == team_id) + .expect("team id not found") + } + pub fn get_points(&self, team_id: u32, past: bool) -> u32 { if !past { self.standings @@ -163,8 +170,18 @@ impl<'m> MatchupPre<'m> { { home_team } else { - // TODO: simulation - home_team + const TIMES: u32 = 50_000; + if self.is_result { + simulation::pick_ideal_winner( + a.api, + a.my_team, + &a.api.past_standings, + self.game, + TIMES, + ) + } else { + simulation::pick_ideal_winner(a.api, a.my_team, &a.api.standings, self.game, TIMES) + } }; Matchup { @@ -177,11 +194,11 @@ impl<'m> MatchupPre<'m> { } fn main() -> reqwest::Result<()> { - let teams = nhlapi::teams::get()?; - let past_standings = nhlapi::standings::yesterday()?; - let standings = nhlapi::standings::today()?; - let results = nhlapi::schedule::yesterday()?; - let games = nhlapi::schedule::today()?; + let teams = nhlapi::teams::get().expect("error getting teams"); + let past_standings = nhlapi::standings::yesterday().expect("error getting past standings"); + let standings = nhlapi::standings::today().expect("error getting standings"); + let results = nhlapi::schedule::yesterday().expect("error getting results"); + let games = nhlapi::schedule::today().expect("error getting games"); let api = Api { teams, diff --git a/src/markdown.rs b/src/markdown.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/nhlapi.rs b/src/nhlapi.rs index 7449038..819805a 100644 --- a/src/nhlapi.rs +++ b/src/nhlapi.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] //! Docs: https://gitlab.com/dword4/nhlapi use std::fmt::Display; @@ -116,7 +117,7 @@ pub mod schedule { let client = reqwest::Client::new(); let mut root: Root = client - .get("https://statsapi.web.nhl.com/api/v1/schedule") + .get("https://statsapi.web.nhl.com/api/v1/schedule?expand=schedule.linescore") .query(&[("date", date)]) .send()? .json()?; @@ -208,8 +209,6 @@ pub mod standings { } pub mod teams { - use std::cmp; - use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/simulation.rs b/src/simulation.rs index 65afd73..01a8679 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -1,6 +1,13 @@ +use std::cmp::Reverse; +use std::collections::BTreeSet; + use rand::seq::SliceRandom; -use crate::nhlapi::LeagueRecord; +use crate::nhlapi; +use crate::nhlapi::schedule::Game; +use crate::nhlapi::standings::TeamRecord; +use crate::nhlapi::teams::Team; +use crate::Api; #[derive(Debug, Copy, Clone)] struct Entry { @@ -10,6 +17,7 @@ struct Entry { wins: u32, losses: u32, ot: u32, + games_played: u32, points: u32, } @@ -20,15 +28,147 @@ enum Event { Ot, } -fn random_event(rec: &LeagueRecord) -> Event { +impl Event { + fn points(&self) -> u32 { + match self { + Event::Win => 2, + Event::Loss => 0, + Event::Ot => 1, + } + } +} + +fn random_event(base: &Entry) -> Event { [ - (Event::Win, rec.wins), - (Event::Loss, rec.losses), - (Event::Ot, rec.ot), + (Event::Win, base.wins), + (Event::Loss, base.losses), + (Event::Ot, base.ot), ] .choose_weighted(&mut rand::thread_rng(), |x| x.1) .unwrap() .0 } -pub struct Simulation {} +pub fn pick_ideal_winner<'a>( + api: &'a Api, + my_team: &'a Team, + records: &'a [TeamRecord], + game: &'a Game, + times: u32, +) -> &'a nhlapi::Team { + let mut home_win_sim = Simulation::new(api, my_team, records); + home_win_sim.give_team_win(game.home_team().id); + home_win_sim.give_team_loss(game.away_team().id); + let mut home_win_x = 0; + for _ in 0..times { + if home_win_sim.run() { + home_win_x += 1; + } + } + + let mut away_win_sim = Simulation::new(api, my_team, records); + away_win_sim.give_team_win(game.away_team().id); + away_win_sim.give_team_loss(game.home_team().id); + let mut away_win_x = 0; + for _ in 0..times { + if away_win_sim.run() { + away_win_x += 1; + } + } + + eprintln!( + "{} ({}) at {} ({})", + game.away_team().name, + away_win_x, + game.home_team().name, + home_win_x + ); + + if home_win_x > away_win_x { + game.home_team() + } else { + game.away_team() + } +} + +struct Simulation<'a> { + my_team: &'a Team, + base: Vec, +} + +impl Simulation<'_> { + pub fn new<'a>(api: &'a Api, my_team: &'a Team, records: &'a [TeamRecord]) -> Simulation<'a> { + let mut base = Vec::new(); + for record in records { + let team = api.get_team_by_id(record.team.id); + if team.conference.id == my_team.conference.id { + base.push(Entry { + team_id: team.id, + division_id: team.division.id, + conference_id: team.conference.id, + wins: record.league_record.wins, + losses: record.league_record.losses, + ot: record.league_record.ot, + games_played: record.games_played, + points: record.points, + }); + } + } + Simulation { my_team, base } + } + + pub fn give_team_win(&mut self, team_id: u32) { + if let Some(entry) = self.base.iter_mut().find(|x| x.team_id == team_id) { + entry.wins += 1; + entry.points += 2; + entry.games_played += 1; + } + } + + pub fn give_team_loss(&mut self, team_id: u32) { + if let Some(entry) = self.base.iter_mut().find(|x| x.team_id == team_id) { + entry.losses += 1; + entry.games_played += 1; + } + } + + pub fn run(&self) -> bool { + let mut entries = self.base.clone(); + for (base, entry) in self.base.iter().zip(entries.iter_mut()) { + while entry.games_played < 82 { + let event = random_event(base); + entry.games_played += 1; + entry.points += event.points(); + match event { + Event::Win => entry.wins += 1, + Event::Loss => entry.losses += 1, + Event::Ot => entry.ot += 1, + } + } + } + entries.sort_unstable_by_key(|e| Reverse((e.points, e.wins))); + + let top_3_teams: BTreeSet = entries + .iter() + .filter(|x| x.division_id == self.my_team.division.id) + .take(3) + .map(|x| x.team_id) + .chain( + entries + .iter() + .filter(|x| x.division_id != self.my_team.division.id) + .take(3) + .map(|x| x.team_id), + ) + .collect(); + + let wildcard: BTreeSet = entries + .iter() + .filter(|x| !top_3_teams.contains(&x.team_id)) + .take(2) + .map(|x| x.team_id) + .collect(); + + top_3_teams.contains(&self.my_team.id) || wildcard.contains(&self.my_team.id) + } +}