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"
|
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)",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
203
src/main.rs
203
src/main.rs
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
143
src/nhlapi.rs
143
src/nhlapi.rs
@@ -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
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