continue work on simulation
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -579,6 +579,7 @@ name = "playoffsbot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"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)",
|
||||
"serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -8,3 +8,4 @@ edition = "2018"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
reqwest = "0.9"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
rand = "0.6"
|
||||
|
||||
203
src/main.rs
203
src/main.rs
@@ -1,7 +1,200 @@
|
||||
mod nhlapi;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let s = nhlapi::schedule::today().unwrap();
|
||||
println!("{:#?}", s);
|
||||
use nhlapi::schedule::Game;
|
||||
use nhlapi::standings::TeamRecord;
|
||||
use nhlapi::teams::Team;
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
143
src/nhlapi.rs
143
src/nhlapi.rs
@@ -48,19 +48,19 @@ impl Serialize for Season {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct LeagueRecord {
|
||||
wins: u32,
|
||||
losses: u32,
|
||||
ot: u32,
|
||||
pub wins: u32,
|
||||
pub losses: u32,
|
||||
pub ot: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Team {
|
||||
id: u32,
|
||||
name: String,
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub mod schedule {
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use chrono::{DateTime, Local, NaiveDate, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{LeagueRecord, Season, Team};
|
||||
@@ -88,6 +88,15 @@ pub mod schedule {
|
||||
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)]
|
||||
pub struct Teams {
|
||||
pub away: TeamRecord,
|
||||
@@ -99,15 +108,45 @@ pub mod schedule {
|
||||
pub team: Team,
|
||||
#[serde(rename = "leagueRecord")]
|
||||
pub league_record: LeagueRecord,
|
||||
pub score: u32,
|
||||
}
|
||||
|
||||
pub fn today() -> reqwest::Result<Vec<Date>> {
|
||||
let root: Root = reqwest::get("https://statsapi.web.nhl.com/api/v1/schedule")?.json()?;
|
||||
pub fn get(date: &NaiveDate) -> reqwest::Result<Date> {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
use chrono::{Local, NaiveDate};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{from_str, LeagueRecord, Team};
|
||||
@@ -125,32 +164,92 @@ pub mod standings {
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct TeamRecord {
|
||||
team: Team,
|
||||
pub team: Team,
|
||||
#[serde(rename = "leagueRecord")]
|
||||
league_record: LeagueRecord,
|
||||
pub league_record: LeagueRecord,
|
||||
|
||||
#[serde(rename = "goalsAgainst")]
|
||||
goals_against: u32,
|
||||
pub goals_against: u32,
|
||||
#[serde(rename = "goalsScored")]
|
||||
goals_scored: u32,
|
||||
points: u32,
|
||||
row: u32,
|
||||
pub goals_scored: u32,
|
||||
pub points: u32,
|
||||
pub row: u32,
|
||||
#[serde(rename = "gamesPlayed")]
|
||||
games_played: u32,
|
||||
pub games_played: u32,
|
||||
|
||||
#[serde(rename = "divisionRank", deserialize_with = "from_str")]
|
||||
division_rank: u32,
|
||||
pub division_rank: u32,
|
||||
#[serde(rename = "conferenceRank", deserialize_with = "from_str")]
|
||||
conference_rank: u32,
|
||||
pub conference_rank: u32,
|
||||
#[serde(rename = "leagueRank", deserialize_with = "from_str")]
|
||||
league_rank: u32,
|
||||
pub league_rank: u32,
|
||||
#[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>> {
|
||||
let mut root: Root =
|
||||
reqwest::get("https://statsapi.web.nhl.com/api/v1/standings/byLeague")?.json()?;
|
||||
Ok(root.records.remove(0).team_records)
|
||||
get(&Local::today().naive_local())
|
||||
}
|
||||
|
||||
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
34
src/simulation.rs
Normal 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 {}
|
||||
Reference in New Issue
Block a user