planetwars.dev/planetwars-matchrunner/src/pw_match.rs
2022-04-27 20:08:48 +02:00

138 lines
4.1 KiB
Rust

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<Vec<u8>>)> {
// 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::<FuturesUnordered<_>>()
.collect::<Vec<_>>()
.await
}
fn execute_action(
&mut self,
player_num: usize,
turn: RequestResult<Vec<u8>>,
) -> 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<PlayerAction> {
match action {
PlayerAction::Commands(commands) => {
let failed = commands
.into_iter()
.filter(|cmd| cmd.error.is_some())
.collect::<Vec<_>>();
if failed.is_empty() {
None
} else {
Some(PlayerAction::Commands(failed))
}
}
e => Some(e),
}
}