diff --git a/planetwars-matchrunner/src/lib.rs b/planetwars-matchrunner/src/lib.rs index 50cff70..b0e8e49 100644 --- a/planetwars-matchrunner/src/lib.rs +++ b/planetwars-matchrunner/src/lib.rs @@ -14,7 +14,6 @@ use futures::{stream::FuturesOrdered, StreamExt}; use match_context::MatchCtx; use match_log::{create_log_sink, MatchLogger}; use planetwars_rules::PwConfig; -use serde::{Deserialize, Serialize}; pub use self::match_context::{EventBus, PlayerHandle}; @@ -25,18 +24,6 @@ pub struct MatchConfig { pub players: Vec, } -#[derive(Serialize, Deserialize)] -pub struct MatchMeta { - pub map_name: String, - pub timestamp: chrono::DateTime, - pub players: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct PlayerInfo { - pub name: String, -} - pub struct MatchPlayer { pub bot_spec: Box, } @@ -53,6 +40,12 @@ pub trait BotSpec: Send + Sync { pub struct MatchOutcome { pub winner: Option, + pub player_outcomes: Vec, +} + +pub struct PlayerOutcome { + pub had_errors: bool, + pub crashed: bool, } pub async fn run_match(config: MatchConfig) -> MatchOutcome { @@ -86,36 +79,31 @@ pub async fn run_match(config: MatchConfig) -> MatchOutcome { let match_ctx = MatchCtx::new(event_bus, players, match_logger); - // TODO: is this still needed? - // assemble the math meta struct - // let match_meta = MatchMeta { - // map_name: config.map_name.clone(), - // timestamp: chrono::Local::now(), - // players: config - // .players - // .iter() - // .map(|bot| PlayerInfo { - // name: bot.name.clone(), - // }) - // .collect(), - // }; - // write!( - // log_file, - // "{}\n", - // serde_json::to_string(&match_meta).unwrap() - // ) - // .unwrap(); + let mut match_instance = pw_match::PwMatch::create(match_ctx, pw_config); + match_instance.run().await; + match_instance.match_ctx.shutdown().await; - let final_state = pw_match::PwMatch::create(match_ctx, pw_config).run().await; - - let survivors = final_state.state().living_players(); + let survivors = match_instance.match_state.state().living_players(); let winner = if survivors.len() == 1 { Some(survivors[0]) } else { None }; - MatchOutcome { winner } + let player_outcomes = (1..=config.players.len()) + .map(|player_id| { + let player_status = &match_instance.player_status[&player_id]; + PlayerOutcome { + had_errors: player_status.had_command_errors, + crashed: player_status.terminated, + } + }) + .collect(); + + MatchOutcome { + winner, + player_outcomes, + } } // writing this as a closure causes lifetime inference errors diff --git a/planetwars-matchrunner/src/pw_match.rs b/planetwars-matchrunner/src/pw_match.rs index 62a07e5..01da9b4 100644 --- a/planetwars-matchrunner/src/pw_match.rs +++ b/planetwars-matchrunner/src/pw_match.rs @@ -9,6 +9,7 @@ use tokio::time::Duration; use serde_json; +use std::collections::HashMap; use std::convert::TryInto; pub use planetwars_rules::config::{Config, Map}; @@ -17,30 +18,43 @@ use planetwars_rules::protocol as proto; use planetwars_rules::serializer as pw_serializer; use planetwars_rules::{PlanetWars, PwConfig}; -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MatchConfig { - pub map_name: String, - pub max_turns: usize, -} - pub struct PwMatch { pub match_ctx: MatchCtx, pub match_state: PlanetWars, + pub player_status: HashMap, +} + +pub struct PlayerStatus { + pub had_command_errors: bool, + pub terminated: bool, } impl PwMatch { pub fn create(match_ctx: MatchCtx, config: PwConfig) -> Self { // TODO: this is kind of hacked together at the moment let match_state = PlanetWars::create(config, match_ctx.players().len()); + let player_status = match_ctx + .players() + .into_iter() + .map(|player_id| { + ( + player_id as usize, + PlayerStatus { + had_command_errors: false, + terminated: false, + }, + ) + }) + .collect(); PwMatch { match_state, match_ctx, + player_status, } } - pub async fn run(mut self) -> PlanetWars { + pub async fn run(&mut self) { // log initial state self.log_game_state(); @@ -49,14 +63,12 @@ impl PwMatch { for (player_id, turn) in player_messages { let player_action = self.execute_action(player_id, turn); + self.update_player_status(player_id, &player_action); self.log_player_action(player_id, player_action); } self.match_state.step(); self.log_game_state(); } - - self.match_ctx.shutdown().await; - self.match_state } async fn prompt_players(&mut self) -> Vec<(usize, RequestResult>)> { @@ -145,6 +157,26 @@ impl PwMatch { } } } + + fn update_player_status(&mut self, player_id: usize, player_action: &PlayerAction) { + let player_status = self.player_status.get_mut(&player_id).unwrap(); + match player_action { + PlayerAction::Commands(dispatches) => { + if dispatches.iter().any(|d| d.error.is_some()) { + player_status.had_command_errors = true; + } + } + PlayerAction::ParseError { .. } => { + player_status.had_command_errors = true; + } + PlayerAction::Timeout => { + player_status.had_command_errors = true; + } + PlayerAction::Terminated => { + player_status.terminated = true; + } + } + } } #[derive(Debug, Clone, Serialize, Deserialize)]