continue work on simulation

This commit is contained in:
2019-03-06 23:31:03 -05:00
parent 7357fe7df0
commit cd548eaa01
6 changed files with 357 additions and 27 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
**/*.rs.bk

1
Cargo.lock generated
View File

@@ -579,6 +579,7 @@ name = "playoffsbot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@@ -8,3 +8,4 @@ edition = "2018"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
reqwest = "0.9" reqwest = "0.9"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
rand = "0.6"

View File

@@ -1,7 +1,200 @@
mod nhlapi; use std::collections::BTreeSet;
fn main() { use nhlapi::schedule::Game;
println!("Hello, world!"); use nhlapi::standings::TeamRecord;
let s = nhlapi::schedule::today().unwrap(); use nhlapi::teams::Team;
println!("{:#?}", s);
mod nhlapi;
mod simulation;
pub struct Api {
teams: Vec<Team>,
past_standings: Vec<TeamRecord>,
standings: Vec<TeamRecord>,
results: nhlapi::schedule::Date,
games: nhlapi::schedule::Date,
}
impl Api {
pub fn get_team_by_abbrev(&self, abbrev: &str) -> &Team {
let abbrev = abbrev.to_ascii_uppercase();
self.teams
.iter()
.find(|t| t.abbrev == abbrev)
.expect("team abbrev not found")
}
pub fn get_points(&self, team_id: u32, past: bool) -> u32 {
if !past {
self.standings
.iter()
.find(|t| t.team.id == team_id)
.expect("team id not found")
.points
} else {
self.past_standings
.iter()
.find(|t| t.team.id == team_id)
.expect("team id not found")
.points
}
}
}
struct Analyzer<'a> {
api: &'a Api,
my_team: &'a Team,
own_conference_team_ids: BTreeSet<u32>,
}
impl Analyzer<'_> {
pub fn new<'a>(api: &'a Api, my_team: &'a Team) -> Analyzer<'a> {
let mut own_conference_team_ids = BTreeSet::new();
for team in &api.teams {
if team.conference.id == my_team.conference.id {
own_conference_team_ids.insert(team.id);
}
}
Analyzer {
api,
my_team,
own_conference_team_ids,
}
}
pub fn perform(&self) -> Analysis {
let mut my_game = None;
let mut games = vec![];
let mut my_result = None;
let mut results = vec![];
for game in &self.api.games.games {
let m = MatchupPre::create(self, game, false);
if m.is_relevant(self) {
if m.is_my_team_involed {
my_game = Some(m.pick_winner(self));
} else {
games.push(m.pick_winner(self));
}
}
}
for game in &self.api.results.games {
let m = MatchupPre::create(self, game, true);
if m.is_relevant(self) {
if m.is_my_team_involed {
my_result = Some(m.pick_winner(self));
} else {
results.push(m.pick_winner(self));
}
}
}
Analysis {
my_team: self.my_team,
my_game: my_game,
games: games,
my_result: my_result,
results: results,
}
}
}
#[derive(Debug)]
struct Analysis<'a> {
pub my_team: &'a Team,
pub my_result: Option<Matchup<'a>>,
pub results: Vec<Matchup<'a>>,
pub my_game: Option<Matchup<'a>>,
pub games: Vec<Matchup<'a>>,
}
#[derive(Debug)]
struct Matchup<'a> {
pub game: &'a Game,
pub is_result: bool,
pub is_my_team_involed: bool,
pub ideal_winner: &'a nhlapi::Team,
}
struct MatchupPre<'a> {
pub game: &'a Game,
pub is_result: bool,
pub is_my_team_involed: bool,
}
impl<'m> MatchupPre<'m> {
pub fn create<'a>(a: &'a Analyzer, game: &'a Game, is_result: bool) -> MatchupPre<'a> {
let is_my_team_involed =
game.teams.away.team.id == a.my_team.id || game.teams.home.team.id == a.my_team.id;
MatchupPre {
game,
is_result,
is_my_team_involed,
}
}
pub fn is_relevant(&self, a: &Analyzer) -> bool {
self.is_my_team_involed
|| a.own_conference_team_ids
.contains(&self.game.home_team().id)
|| a.own_conference_team_ids
.contains(&self.game.away_team().id)
}
pub fn pick_winner(self, a: &'m Analyzer) -> Matchup<'m> {
let home_team = self.game.home_team();
let away_team = self.game.away_team();
let ideal_winner = if self.is_my_team_involed {
if a.my_team.id == home_team.id {
home_team
} else if a.my_team.id == away_team.id {
away_team
} else {
panic!("unexpected case in pick_winner");
}
} else if a.own_conference_team_ids.contains(&home_team.id)
&& !a.own_conference_team_ids.contains(&away_team.id)
{
away_team
} else if a.own_conference_team_ids.contains(&away_team.id)
&& !a.own_conference_team_ids.contains(&home_team.id)
{
home_team
} else {
// TODO: simulation
home_team
};
Matchup {
game: self.game,
is_result: self.is_result,
is_my_team_involed: self.is_my_team_involed,
ideal_winner: ideal_winner,
}
}
}
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 api = Api {
teams,
past_standings,
standings,
results,
games,
};
let a = Analyzer::new(&api, api.get_team_by_abbrev("mtl"));
let xd = a.perform();
println!("{:#?}", xd);
Ok(())
} }

View File

@@ -48,19 +48,19 @@ impl Serialize for Season {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LeagueRecord { pub struct LeagueRecord {
wins: u32, pub wins: u32,
losses: u32, pub losses: u32,
ot: u32, pub ot: u32,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Team { pub struct Team {
id: u32, pub id: u32,
name: String, pub name: String,
} }
pub mod schedule { pub mod schedule {
use chrono::{DateTime, NaiveDate, Utc}; use chrono::{DateTime, Local, NaiveDate, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{LeagueRecord, Season, Team}; use super::{LeagueRecord, Season, Team};
@@ -88,6 +88,15 @@ pub mod schedule {
pub teams: Teams, pub teams: Teams,
} }
impl Game {
pub fn home_team(&self) -> &Team {
&self.teams.home.team
}
pub fn away_team(&self) -> &Team {
&self.teams.away.team
}
}
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Teams { pub struct Teams {
pub away: TeamRecord, pub away: TeamRecord,
@@ -99,15 +108,45 @@ pub mod schedule {
pub team: Team, pub team: Team,
#[serde(rename = "leagueRecord")] #[serde(rename = "leagueRecord")]
pub league_record: LeagueRecord, pub league_record: LeagueRecord,
pub score: u32,
} }
pub fn today() -> reqwest::Result<Vec<Date>> { pub fn get(date: &NaiveDate) -> reqwest::Result<Date> {
let root: Root = reqwest::get("https://statsapi.web.nhl.com/api/v1/schedule")?.json()?; let date = format!("{}", date.format("%Y-%m-%d"));
let client = reqwest::Client::new();
let mut root: Root = client
.get("https://statsapi.web.nhl.com/api/v1/schedule")
.query(&[("date", date)])
.send()?
.json()?;
Ok(root.dates.remove(0))
}
pub fn get_range(begin: &NaiveDate, end: &NaiveDate) -> reqwest::Result<Vec<Date>> {
let begin = format!("{}", begin.format("%Y-%m-%d"));
let end = format!("{}", end.format("%Y-%m-%d"));
let client = reqwest::Client::new();
let root: Root = client
.get("https://statsapi.web.nhl.com/api/v1/schedule")
.query(&[("startDate", begin), ("endDate", end)])
.send()?
.json()?;
Ok(root.dates) Ok(root.dates)
} }
pub fn today() -> reqwest::Result<Date> {
get(&Local::today().naive_local())
}
pub fn yesterday() -> reqwest::Result<Date> {
get(&Local::today().naive_local().pred())
}
} }
pub mod standings { pub mod standings {
use chrono::{Local, NaiveDate};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{from_str, LeagueRecord, Team}; use super::{from_str, LeagueRecord, Team};
@@ -125,32 +164,92 @@ pub mod standings {
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TeamRecord { pub struct TeamRecord {
team: Team, pub team: Team,
#[serde(rename = "leagueRecord")] #[serde(rename = "leagueRecord")]
league_record: LeagueRecord, pub league_record: LeagueRecord,
#[serde(rename = "goalsAgainst")] #[serde(rename = "goalsAgainst")]
goals_against: u32, pub goals_against: u32,
#[serde(rename = "goalsScored")] #[serde(rename = "goalsScored")]
goals_scored: u32, pub goals_scored: u32,
points: u32, pub points: u32,
row: u32, pub row: u32,
#[serde(rename = "gamesPlayed")] #[serde(rename = "gamesPlayed")]
games_played: u32, pub games_played: u32,
#[serde(rename = "divisionRank", deserialize_with = "from_str")] #[serde(rename = "divisionRank", deserialize_with = "from_str")]
division_rank: u32, pub division_rank: u32,
#[serde(rename = "conferenceRank", deserialize_with = "from_str")] #[serde(rename = "conferenceRank", deserialize_with = "from_str")]
conference_rank: u32, pub conference_rank: u32,
#[serde(rename = "leagueRank", deserialize_with = "from_str")] #[serde(rename = "leagueRank", deserialize_with = "from_str")]
league_rank: u32, pub league_rank: u32,
#[serde(rename = "wildCardRank", deserialize_with = "from_str")] #[serde(rename = "wildCardRank", deserialize_with = "from_str")]
wildcard_rank: u32, pub wildcard_rank: u32,
}
pub fn get(date: &NaiveDate) -> reqwest::Result<Vec<TeamRecord>> {
let date = format!("{}", date.format("%Y-%m-%d"));
let client = reqwest::Client::new();
let mut root: Root = client
.get("https://statsapi.web.nhl.com/api/v1/standings/byLeague")
.query(&[("date", date)])
.send()?
.json()?;
Ok(root.records.remove(0).team_records)
} }
pub fn today() -> reqwest::Result<Vec<TeamRecord>> { pub fn today() -> reqwest::Result<Vec<TeamRecord>> {
let mut root: Root = get(&Local::today().naive_local())
reqwest::get("https://statsapi.web.nhl.com/api/v1/standings/byLeague")?.json()?; }
Ok(root.records.remove(0).team_records)
pub fn yesterday() -> reqwest::Result<Vec<TeamRecord>> {
get(&Local::today().naive_local().pred())
}
}
pub mod teams {
use std::cmp;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
struct Root {
teams: Vec<Team>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Team {
pub id: u32,
#[serde(rename = "name")]
pub full_name: String,
#[serde(rename = "abbreviation")]
pub abbrev: String,
#[serde(rename = "teamName")]
pub name: String,
#[serde(rename = "locationName")]
pub location: String,
pub division: Division,
pub conference: Conference,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Division {
pub id: u32,
pub name: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Conference {
pub id: u32,
pub name: String,
}
pub fn get() -> reqwest::Result<Vec<Team>> {
let client = reqwest::Client::new();
let root: Root = client
.get("https://statsapi.web.nhl.com/api/v1/teams")
.send()?
.json()?;
Ok(root.teams)
} }
} }

34
src/simulation.rs Normal file
View File

@@ -0,0 +1,34 @@
use rand::seq::SliceRandom;
use crate::nhlapi::LeagueRecord;
#[derive(Debug, Copy, Clone)]
struct Entry {
team_id: u32,
division_id: u32,
conference_id: u32,
wins: u32,
losses: u32,
ot: u32,
points: u32,
}
#[derive(Debug, Copy, Clone)]
enum Event {
Win,
Loss,
Ot,
}
fn random_event(rec: &LeagueRecord) -> Event {
[
(Event::Win, rec.wins),
(Event::Loss, rec.losses),
(Event::Ot, rec.ot),
]
.choose_weighted(&mut rand::thread_rng(), |x| x.1)
.unwrap()
.0
}
pub struct Simulation {}