use crate::match_log::MatchLogMessage; use super::match_context::{MatchCtx, RequestResult}; use futures::stream::futures_unordered::FuturesUnordered; use futures::{FutureExt, StreamExt}; use serde::{Deserialize, Serialize}; use tokio::time::Duration; use serde_json; use std::convert::TryInto; pub use planetwars_rules::config::{Config, Map}; use planetwars_rules::protocol::{self as proto, PlayerAction}; 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, } 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()); PwMatch { match_state, match_ctx, } } pub async fn run(&mut self) { while !self.match_state.is_finished() { let player_messages = self.prompt_players().await; for (player_id, turn) in player_messages { let res = self.execute_action(player_id, turn); if let Some(err) = action_errors(res) { let _info_str = serde_json::to_string(&err).unwrap(); // TODO // println!("player {}: {}", player_id, info_str); } } self.match_state.step(); // Log state let state = self.match_state.serialize_state(); self.match_ctx.log(MatchLogMessage::GameState(state)); } } async fn prompt_players(&mut self) -> Vec<(usize, RequestResult>)> { // borrow these outside closure to make the borrow checker happy let state = self.match_state.state(); let match_ctx = &mut self.match_ctx; // TODO: this numbering is really messy. // Get rid of the distinction between player_num // and player_id. self.match_state .state() .players .iter() .filter(|p| p.alive) .map(move |player| { let state_for_player = pw_serializer::serialize_rotated(state, player.id - 1); match_ctx .request( player.id.try_into().unwrap(), serde_json::to_vec(&state_for_player).unwrap(), Duration::from_millis(1000), ) .map(move |resp| (player.id, resp)) }) .collect::>() .collect::>() .await } fn execute_action( &mut self, player_num: usize, turn: RequestResult>, ) -> proto::PlayerAction { let turn = match turn { Err(_timeout) => return proto::PlayerAction::Timeout, Ok(data) => data, }; let action: proto::Action = match serde_json::from_slice(&turn) { Err(err) => return proto::PlayerAction::ParseError(err.to_string()), Ok(action) => action, }; let commands = action .commands .into_iter() .map(|command| { let res = self.match_state.execute_command(player_num, &command); proto::PlayerCommand { command, error: res.err(), } }) .collect(); proto::PlayerAction::Commands(commands) } } fn action_errors(action: PlayerAction) -> Option { match action { PlayerAction::Commands(commands) => { let failed = commands .into_iter() .filter(|cmd| cmd.error.is_some()) .collect::>(); if failed.is_empty() { None } else { Some(PlayerAction::Commands(failed)) } } e => Some(e), } }