Skip to content

Commit

Permalink
Finish
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldoylecs committed Oct 6, 2019
1 parent 1b84573 commit 764db57
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 25 deletions.
54 changes: 54 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ edition = "2018"

[dependencies]
boxcars = "0.5.0"
csv = "1.1.1"
dirs = "2.0.2"
serde = "1.0.101"
serde_derive = "1.0.101"
serde_json = "1.0.40"
178 changes: 153 additions & 25 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,62 @@
extern crate boxcars;
extern crate csv;
extern crate dirs;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use boxcars::{ParseError, Replay, HeaderProp};
use serde_json;

use std::env;
use std::error;
use std::fs;
use std::io::{self, stdin, Read};
use std::io::{stdin, Read, Error, ErrorKind};
use std::path::*;
use std::string::*;

#[derive(Default)]
#[derive(Default, Debug, Clone, Serialize)]
struct PlayerStats {
name: String,
platform: String,
platform_id: String,
platform_id: u64,
team: i32,
games_players: i32,
mvp: bool,
points: u32,
goals: u16,
assists: u16,
saves: u16,
shots: u16
points: i32,
goals: i32,
assists: i32,
saves: i32,
shots: i32
}

impl PlayerStats {
fn default() -> PlayerStats {
PlayerStats {
name: "".to_string(),
platform: "".to_string(),
platform_id: 0,
team: -1,
games_players: 0,
mvp: false,
points: 0,
goals: 0,
assists: 0,
saves: 0,
shots: 0
}
}
}

fn prompt_for_user_input(prompt: &str) -> String {
println!("{}", prompt);
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
input
input.trim_start().trim_end().to_string()
}

fn read_path_input(prompt: &str) -> Result<PathBuf, String> {
let input = prompt_for_user_input(prompt);
let raw_path = PathBuf::from(input.trim_start().trim_end());
let raw_path = PathBuf::from(input);
let full_path = match fs::canonicalize(raw_path) {
Ok(path) => path,
Err(error) => return Err(error.to_string()),
Expand Down Expand Up @@ -62,53 +84,159 @@ fn parse_replay(path: &Path) -> Result<Replay, Box<dyn error::Error>> {
}
}

fn get_player_stats_property(replay: Replay) -> Option<HeaderProp> {
for property in replay.properties {
if property.0 == "PlayerStats" {
return Some(property.1);
fn set_mvp(players: Vec<PlayerStats>, winning_team: i32) -> Vec<PlayerStats> {
let mut highest_scoring_player: PlayerStats = PlayerStats::default();
for player in players.iter() {
if player.team == winning_team {
if player.points > highest_scoring_player.points {
highest_scoring_player = player.clone();
}
}
}
let mut new_player_list = players.clone();
for player in new_player_list.iter_mut() {
if player.platform_id == highest_scoring_player.platform_id {
player.mvp = true;
break;
}
}
None
new_player_list
}

fn make_player_stats(props: Vec<(String, HeaderProp)>) -> PlayerStats {
let player = PlayerStats::default();
let mut player = PlayerStats::default();
for prop in props {
let id = prop.0;
let val = prop.1;
match id.as_ref() {
"Name" => if let HeaderProp::Str(s) = val { player.name = s },
"Platform" => if let HeaderProp::Str(s) = val { player.platform = s },
"OnlineID" => if let HeaderProp::Str(s) = val { player.platform_id = s },
"Name" => if let HeaderProp::Str(s) = val { player.name = s; },
"Platform" => if let HeaderProp::Byte = val { player.platform = "N/A".to_string(); },
"OnlineID" => if let HeaderProp::QWord(n) = val { player.platform_id = n; },
"Team" => if let HeaderProp::Int(n) = val { player.team = n; },
"Score" => if let HeaderProp::Int(n) = val { player.points = n; },
"Goals" => if let HeaderProp::Int(n) = val { player.goals = n; },
"Assists" => if let HeaderProp::Int(n) = val { player.assists = n; },
"Saves" => if let HeaderProp::Int(n) = val { player.saves = n; },
"Shots" => if let HeaderProp::Int(n) = val { player.shots = n; },
_ => (),
}
}
player
}

fn get_stats_from_player_stats(replay_stats: HeaderProp) -> Vec<PlayerStats> {
let stats = Vec::new();
fn get_stats_from_player_stats(replay_stats: HeaderProp, winning_team: i32) -> Vec<PlayerStats> {
let mut stats = Vec::new();
if let HeaderProp::Array(values) = replay_stats {
for player_info in values {
let player_stats = make_player_stats(values);
let player_stats = make_player_stats(player_info);
stats.push(player_stats);
}
}
stats = set_mvp(stats, winning_team);
stats
}

fn get_player_stats_property(replay: &Replay) -> Result<HeaderProp, String> {
for property in &replay.properties {
if property.0 == "PlayerStats" {
return Ok(property.1.clone());
}
}
Err("PlayerStats Property not found!".to_string())
}

fn get_winning_team(replay: &Replay) -> Result<i32, String> {
let mut team_0_score: i32 = -1;
let mut team_1_score: i32 = -1;
for prop in &replay.properties {
match prop.0.as_ref() {
"Team0Score" => if let HeaderProp::Int(v) = prop.1 { team_0_score = v },
"Team1Score" => if let HeaderProp::Int(v) = prop.1 { team_1_score = v },
_ => (),
}
}

if team_0_score == -1 || team_1_score == -1 {
return Err("Team score failed to be found".to_string());
}

return if team_0_score < team_1_score { Ok(1) } else { Ok(0) }
}

fn write_csv(stats: Vec<PlayerStats>, output: PathBuf) -> Result<(), Box<dyn error::Error>> {
let mut csv_wtr = csv::WriterBuilder::new()
.has_headers(false)
.from_path(output)?;
csv_wtr.write_record(&[
"Name",
"Platform",
"OnlineID",
"Games Played",
"MVPs",
"Points",
"Goals",
"Assists",
"Saves",
"Shots"])?;
for stat in stats.iter() {
let name = stat.name.clone();
let platform = stat.platform.clone();
let online_id = stat.platform_id.to_string();
let games_played = 1.to_string();
let mvps = if stat.mvp { "1".to_string() } else { "0".to_string() };
let score = stat.points.to_string();
let goals = stat.goals.to_string();
let assists = stat.assists.to_string();
let saves = stat.saves.to_string();
let shots = stat.shots.to_string();
csv_wtr.write_record(&[name,
platform,
online_id,
games_played,
mvps,
score,
goals,
assists,
saves,
shots])?;
}
csv_wtr.flush()?;
Ok(())
}

fn run() -> Result<(), Box<dyn error::Error>> {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
let input_path: PathBuf = read_path_input("What is the path to the replay file directory")?;
let output_path: PathBuf = read_path_input("What is the path to the output directory")?;
let mut output_path: PathBuf = read_path_input("What is the path to the output directory")?;

let mut all_stats: Vec<PlayerStats> = vec![];
for entry in fs::read_dir(input_path)? {
let entry = entry?;
let file_path: PathBuf = entry.path();
if file_path.is_dir() { continue; }
if !file_path.to_string_lossy().ends_with(".replay") { continue; }

let replay: Replay = parse_replay(&file_path)?;
println!("{}", file_path.to_string_lossy());
let player_stats_prop = get_player_stats_property(&replay)?;
let winning_team = get_winning_team(&replay)?;
let stats = get_stats_from_player_stats(player_stats_prop, winning_team);
for stat in stats.iter() {
all_stats.push(stat.clone());
}
println!(
"Done reading {}",
file_path.file_name()
.ok_or_else(|| Error::new(
ErrorKind::Other,
"Failed to read file name"))?
.to_string_lossy());
}

let file_name: String = prompt_for_user_input("What do you want the output file to be called?");
output_path.push(file_name);
output_path.set_extension("csv".to_string());
write_csv(all_stats, output_path)?;
Ok(())
}

Expand Down

0 comments on commit 764db57

Please sign in to comment.