use std::fs::File; use std::collections::{HashMap}; use uuid::Uuid; use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; // timekeeping use chrono::prelude::*; use chrono::Duration; use account::Account; use account; use player::{Player, Score, player_create}; use mob::{bot_player, instance_mobs}; use game::{Game, Phase, game_get, game_write, game_update}; use item::{Item}; use rpc::{RpcMessage}; use img; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum InstancePhase { Lobby, InProgress, Finished, } pub type ChatState = HashMap; #[derive(Debug,Clone,Serialize,Deserialize)] struct Round { game_id: Option, finished: bool, } impl Round { fn new() -> Round { Round { game_id: None, finished: false } } } #[derive(Debug,Clone,Copy,Serialize,Deserialize)] pub enum TimeControl { Standard, Slow, Practice, } impl TimeControl { fn vbox_time_seconds(&self) -> i64 { match self { TimeControl::Standard => 180, TimeControl::Slow => 240, TimeControl::Practice => panic!("practice vbox seconds called"), } } fn game_time_seconds(&self) -> i64 { match self { TimeControl::Standard => 60, TimeControl::Slow => 120, TimeControl::Practice => panic!("practice game seconds called"), } } pub fn vbox_phase_end(&self) -> Option> { match self { TimeControl::Practice => None, _ => Some(Utc::now() .checked_add_signed(Duration::seconds(self.vbox_time_seconds())) .expect("could not set vbox phase end")), } } pub fn lobby_timeout(&self) -> DateTime { Utc::now() .checked_add_signed(Duration::seconds(15)) .expect("could not set phase end") } pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option> { match self { TimeControl::Practice => None, _ => Some(Utc::now() .checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms)) .expect("could not set game phase end")), } } } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Instance { pub id: Uuid, pub name: String, players: Vec, rounds: Vec, max_players: usize, time_control: TimeControl, phase: InstancePhase, phase_end: Option>, phase_start: DateTime, winner: Option, } impl Instance { fn new() -> Instance { Instance { id: Uuid::new_v4(), players: vec![], rounds: vec![], phase: InstancePhase::Lobby, max_players: 2, name: String::new(), time_control: TimeControl::Standard, phase_start: Utc::now(), phase_end: Some(TimeControl::Standard.lobby_timeout()), winner: None, } } pub fn redact(mut self, account: Uuid) -> Instance { self.players = self.players.into_iter() .map(|p| p.redact(account)) .collect(); self } fn phase_timed_out(&self) -> bool { match self.phase_end { Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, None => false, } } fn timed_out_players(&self) -> Vec { self.players .iter() .filter(|p| !p.ready) .map(|p| p.id) .collect::>() } pub fn upkeep(mut self) -> (Instance, Option) { // time out lobbies that have been open too long if self.phase == InstancePhase::Lobby && self.phase_timed_out() { self.finish(); return (self, None); } if self.phase != InstancePhase::InProgress { return (self, None); } if !self.phase_timed_out() { return (self, None); } let new_game = self .timed_out_players() .iter() .filter_map(|p| self.player_ready(*p).unwrap()) .collect::>() .into_iter() .next(); (self, new_game) } fn set_name(mut self, name: String) -> Result { if name.len() == 0 { return Err(err_msg("name must have a length")); } self.name = name; Ok(self) } fn set_time_control(mut self, tc: TimeControl) -> Instance { self.time_control = tc; self } fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { if self.players.len() >= self.max_players { return Err(err_msg("game full")) } match self.players.iter().find(|p| p.id == player.id) { Some(_p) => return Err(err_msg("already joined")), None => (), }; self.players.push(player); Ok(self) } fn player_ready(&mut self, player_id: Uuid) -> Result, Error> { if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { return Err(err_msg("instance not in start or vbox phase")); } // LOBBY CHECKS if self.phase == InstancePhase::Lobby { let i = self.players .iter_mut() .position(|p| p.id == player_id) .ok_or(err_msg("player_id not found"))?; let v = !self.players[i].ready; self.players[i].set_ready(v); match self.can_start() { true => { self.start(); return Ok(None); } false => return Ok(None), }; } // GAME PHASE READY let i = self.players .iter_mut() .position(|p| p.id == player_id) .ok_or(err_msg("player_id not found"))?; let v = !self.players[i].ready; self.players[i].set_ready(v); // start the game even if afk noobs have no skills if !self.phase_timed_out() && self.players[i].constructs.iter().all(|c| c.skills.len() == 0) { return Err(err_msg("your constructs have no skills")); } // create a game object if both players are ready // this should only happen once let all_ready = self.round_ready_check(); if !all_ready { return Ok(None); } let game = self.create_round_game(); let current_round = self.rounds .last_mut() .expect("instance does not have any rounds"); current_round.game_id = Some(game.id); return Ok(Some(game)); } fn round_ready_check(&mut self) -> bool { self.players .iter() .all(|p| p.ready) } // maybe just embed the games in the instance // but seems hella inefficient fn create_round_game(&mut self) -> Game { let current_round = self.rounds .last_mut() .expect("instance does not have any rounds"); let mut game = Game::new(); current_round.game_id = Some(game.id); // disable upkeep until players finish their game self.phase_end = None; game .set_player_num(2) .set_player_constructs(3) .set_time_control(self.time_control) .set_instance(self.id); for player in self.players.clone().into_iter() { game.player_add(player).unwrap(); } assert!(game.can_start()); return game.start(); } fn can_start(&self) -> bool { self.players.len() == self.max_players && self.all_ready() } fn start(&mut self) -> &mut Instance { // self.players.sort_unstable_by_key(|p| p.id); self.next_round() } fn next_round(&mut self) -> &mut Instance { if self.finish_condition() { return self.finish(); } self.phase = InstancePhase::InProgress; self.phase_start = Utc::now(); self.phase_end = self.time_control.vbox_phase_end(); let bits = match self.rounds.len() > 0 { true => 12 + 6 * self.rounds.len(), false => 0, }; self.players.iter_mut().for_each(|p| { p.vbox.balance_add(bits.into()); p.set_ready(false); p.vbox.fill(); }); self.rounds.push(Round::new()); self.bot_round_actions(); self } fn finish_condition(&mut self) -> bool { self.players.iter().any(|p| p.score == Score::Win) } pub fn finish(&mut self) -> &mut Instance { self.phase = InstancePhase::Finished; for player in self.players.iter() { if player.score == Score::Win { self.winner = Some(player.id); } } self } fn finished(&self) -> bool { self.phase == InstancePhase::Finished } fn bot_round_actions(&mut self) -> &mut Instance { for bot in self.players.iter_mut().filter(|p| p.bot) { bot.vbox.fill(); bot.autobuy(); } let games = self.players .clone() .iter() .filter(|b| b.bot) .filter_map(|b| self.player_ready(b.id).unwrap()) .collect::>(); for game in games { if game.finished() { self.game_finished(&game).unwrap(); } else { info!("{:?} unfishededes", game); } } self } fn current_game_id(&self) -> Option { if self.phase != InstancePhase::InProgress { return None; } let current_round = self.rounds .last() .expect("instance does not have any rounds"); if current_round.finished || current_round.game_id.is_none() { return None; } return current_round.game_id; } fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { { let current_round = self.rounds .iter_mut() .filter(|r| r.game_id.is_some()) .find(|r| r.game_id.unwrap() == game.id); match current_round { Some(c) => c.finished = true, None => return Err(err_msg("instance does not have a round for this game")), }; } // if you don't win, you lose // ties can happen if both players agree to a draw // or ticks fire and knock everybody out if let Some(winner) = game.winner() { let winner = self.players.iter_mut() .find(|p| p.id == winner.id) .unwrap(); winner.score = winner.score.add_win(&Score::Zero); }; if self.all_games_finished() { self.next_round(); } Ok(self) } fn all_ready(&self) -> bool { self.players.iter().all(|p| p.ready) } fn all_games_finished(&self) -> bool { match self.rounds.last() { Some(r) => r.finished, None => true, } } // PLAYER ACTIONS fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() .find(|p| p.id == account) .ok_or(err_msg("account not in instance")) } fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() .find(|p| p.id != account) .ok_or(err_msg("opponent not in instance")) } pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> { if self.players.iter().find(|p| p.id == account).is_none() { return Err(err_msg("player not in this instance")); } if self.phase == InstancePhase::Lobby { return Err(err_msg("game not yet started")); } if self.current_game_id().is_some() { return Err(err_msg("you cannot perform vbox actions while in a game")); } Ok(()) } pub fn vbox_discard(mut self, account: Uuid) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_discard()?; Ok(self) } pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize, construct_id: Option) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_accept(group, index, construct_id)?; Ok(self) } pub fn vbox_combine(mut self, account: Uuid, indices: Vec) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_combine(indices)?; Ok(self) } pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_reclaim(index)?; Ok(self) } pub fn vbox_apply(mut self, account: Uuid, index: usize, construct_id: Uuid) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_apply(index, construct_id)?; Ok(self) } pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_unequip(target, construct_id, target_construct_id)?; Ok(self) } } pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result { let instance_bytes = to_vec(&instance)?; let query = " INSERT INTO instances (id, data, upkeep) VALUES ($1, $2, $3) RETURNING id; "; let result = tx .query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?; result.iter().next().ok_or(format_err!("no instances written"))?; return Ok(instance); } pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result { let instance_bytes = to_vec(&instance)?; let query = " UPDATE instances SET data = $1, finished = $2, upkeep = $3, updated_at = now() WHERE id = $4 RETURNING id, data; "; let result = tx .query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?; result.iter().next().ok_or(err_msg("no instance row returned"))?; trace!("{:?} wrote instance", instance.id); if instance.finished() { info!("finished id={:?}", instance.id); match instance_json_file_write(&instance) { Ok(dest) => info!("wrote dest={:?}", dest), Err(e) => error!("json write error={:?}", e), }; } return Ok(instance); } fn instance_json_file_write(g: &Instance) -> Result { let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id); serde_json::to_writer(File::create(&dest)?, g)?; Ok(dest) } pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result { let query = " SELECT * FROM instances WHERE id = $1 FOR UPDATE; "; let result = tx .query(query, &[&instance_id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("instance not found")), }; let instance_bytes: Vec = returned.get("data"); let instance = from_slice::(&instance_bytes)?; return Ok(instance); } pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE FROM instances WHERE id = $1; "; let result = tx .execute(query, &[&id])?; if result != 1 { return Err(format_err!("unable to delete instance {:?}", id)); } info!("instance deleted {:?}", id); return Ok(()); } pub fn _instance_list(tx: &mut Transaction) -> Result, Error> { let query = " SELECT data, id FROM instances AND finished = false; "; let result = tx .query(query, &[])?; let mut list = vec![]; for row in result.into_iter() { let bytes: Vec = row.get(0); let id = row.get(1); match from_slice::(&bytes) { Ok(i) => list.push(i), Err(_e) => { instance_delete(tx, id)?; } }; } return Ok(list); } pub fn instances_need_upkeep(tx: &mut Transaction) -> Result, Error> { let query = " SELECT data, id FROM instances WHERE finished = false AND upkeep < now() FOR UPDATE; "; let result = tx .query(query, &[])?; let mut list = vec![]; for row in result.into_iter() { let bytes: Vec = row.get(0); let id = row.get(1); match from_slice::(&bytes) { Ok(i) => list.push(i), Err(_e) => { instance_delete(tx, id)?; } }; } return Ok(list); } // timed out instances with no time control pub fn instances_idle(tx: &mut Transaction) -> Result, Error> { let query = " SELECT data, id FROM instances WHERE finished = false AND updated_at < now() - interval '1 hour' FOR UPDATE; "; let result = tx .query(query, &[])?; let mut list = vec![]; for row in result.into_iter() { let bytes: Vec = row.get(0); let id = row.get(1); match from_slice::(&bytes) { Ok(i) => list.push(i), Err(_e) => { instance_delete(tx, id)?; } }; } return Ok(list); } pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result { let bot = bot_player(); let bot_id = bot.id; // generate bot imgs for the client to see for c in bot.constructs.iter() { img::shapes_write(c.img)?; } let mut instance = Instance::new() .set_time_control(TimeControl::Practice) .set_name(bot.name.clone())?; let player = Player::from_account(tx, account)?; instance.add_player(player.clone())?; instance.add_player(bot)?; instance.player_ready(bot_id)?; instance = instance_create(tx, instance)?; player_create(tx, player, instance.id, account)?; Ok(instance) } pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result { let mut instance = Instance::new() // TODO generate nice game names .set_name("PVP".to_string())?; instance = instance_create(tx, instance)?; for account in [a, b].iter() { let acc_p = Player::from_account(tx, &account)?; let player = player_create(tx, acc_p, instance.id, account)?; instance.add_player(player)?; } instance_update(tx, instance) } pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { let mut instance = instance_get(tx, instance_id)?; if let Some(game_id) = instance.current_game_id() { let mut game = game_get(tx, game_id)?; game.player_by_id(account.id)?.forfeit(); game = game.start(); // actually finishes it... game_update(tx, &game)?; } instance.account_player(account.id)?.set_lose(); instance.account_opponent(account.id)?.set_win(); instance.next_round(); Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) } pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { let mut instance = instance_get(tx, instance_id)?; let player_id = instance.account_player(account.id)?.id; if let Some(game) = instance.player_ready(player_id)? { game_write(tx, &game)?; // ensures cleanup for warden etc is done game_update(tx, &game)?; instance_update(tx, instance)?; return Ok(RpcMessage::GameState(game)); } Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) } pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result { let instance = instance_get(tx, instance_id)?; if let Some(game_id) = instance.current_game_id() { let game = game_get(tx, game_id)?; // return the game until it's finished if game.phase != Phase::Finished { return Ok(RpcMessage::GameState(game)) } } Ok(RpcMessage::InstanceState(instance)) } pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> { let mut instance = instance_get(tx, instance_id)?; instance.game_finished(game)?; // info!("{:?}", instance_get(tx, instance_id)?); instance_update(tx, instance)?; Ok(()) } pub fn bot_instance() -> Instance { let mut instance = Instance::new(); let bot_player = bot_player(); let bot = bot_player.id; instance.add_player(bot_player).unwrap(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); instance.add_player(player).expect("could not add player"); instance.player_ready(player_account).unwrap(); instance.player_ready(bot).unwrap(); return instance; } pub fn demo() -> Result, Error> { let bot = bot_player(); // generate bot imgs for the client to see for c in bot.constructs.iter() { img::shapes_write(c.img)?; }; let bot2 = bot_player(); // generate bot imgs for the client to see for c in bot2.constructs.iter() { img::shapes_write(c.img)?; }; Ok(vec![bot, bot2]) } #[cfg(test)] mod tests { use super::*; #[test] fn instance_pve_test() { let mut instance = Instance::new(); let bot_player = bot_player(); let bot = bot_player.id; instance.add_player(bot_player).unwrap(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); instance.add_player(player).expect("could not add player"); assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(player_account).unwrap(); instance.player_ready(bot).unwrap(); assert_eq!(instance.phase, InstancePhase::Finished); } #[test] fn instance_bot_vbox_test() { let _instance = Instance::new(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); } #[test] fn instance_start_test() { let mut instance = Instance::new(); assert_eq!(instance.max_players, 2); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"a".to_string(), constructs); let a_id = player.id; instance.add_player(player).expect("could not add player"); assert!(!instance.can_start()); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"b".to_string(), constructs); let b_id = player.id; instance.add_player(player).expect("could not add player"); assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(a_id).expect("a ready"); assert!(!instance.can_start()); instance.player_ready(b_id).expect("b ready"); assert_eq!(instance.phase, InstancePhase::InProgress); assert!(!instance.can_start()); instance.players[0].autobuy(); instance.players[1].autobuy(); instance.player_ready(a_id).expect("a ready"); let game = instance.player_ready(b_id).expect("b ready"); assert!(game.is_some()); } #[test] fn instance_upkeep_test() { let mut instance = Instance::new(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"a".to_string(), constructs); let a_id = player.id; instance.add_player(player).expect("could not add player"); assert!(!instance.can_start()); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"b".to_string(), constructs); let b_id = player.id; instance.add_player(player).expect("could not add player"); instance.players[0].autobuy(); instance.player_ready(a_id).expect("a ready"); instance.player_ready(b_id).expect("b ready"); instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap()); let (mut instance, new_games) = instance.upkeep(); assert!(new_games.is_some()); let game = new_games.unwrap(); assert!(game.finished()); instance.game_finished(&game).unwrap(); assert_eq!(instance.rounds.len(), 2); assert!(instance.players.iter().all(|p| !p.ready)); // info!("{:#?}", instance); } #[test] fn instance_upkeep_idle_lobby_test() { let mut instance = Instance::new(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let player = Player::new(player_account, &"a".to_string(), constructs); let _a_id = player.id; instance.add_player(player).expect("could not add player"); assert!(!instance.can_start()); instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::minutes(61)).unwrap()); let (instance, _new_games) = instance.upkeep(); assert!(instance.finished()); } }