use uuid::Uuid; use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use std::iter; use rpc::{InstanceLobbyParams, InstanceJoinParams, InstanceReadyParams, InstanceStateParams}; use account::Account; use player::{Player, Score, player_create, player_get, player_update}; use cryp::{Cryp, cryp_get}; use mob::{instance_mobs}; use game::{Game, Phase, Team, game_get, game_write, game_instance_new, game_instance_join, game_global_get, game_global_set}; use vbox::{Var}; use rpc::{RpcResult}; use names::{name}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum InstancePhase { Lobby, InProgress, Finished, } #[derive(Debug,Clone,Serialize,Deserialize)] struct Round { player_ids: Vec, game_id: Uuid, finished: bool, } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Instance { id: Uuid, players: Vec, phase: InstancePhase, rounds: Vec>, open: bool, max_players: usize, password: Option, pub name: String, } impl Instance { fn new() -> Instance { Instance { id: Uuid::new_v4(), players: vec![], rounds: vec![], phase: InstancePhase::Lobby, open: true, max_players: 2, name: String::new(), password: None, } } pub fn global(player: Player) -> Instance { Instance { id: Uuid::nil(), players: vec![player], rounds: vec![], phase: InstancePhase::InProgress, open: false, max_players: 0, name: "Global Matchmaking".to_string(), password: None, } } fn set_max_players(mut self, max: usize) -> Result { if max > 16 || max % 2 != 0 { return Err(err_msg("max players must be divisible by 2 and less than 16")); } self.max_players = max; Ok(self) } 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 add_bots(mut self) -> Instance { self.open = false; self.players = iter::repeat_with(|| { let bot_id = Uuid::new_v4(); let cryps = instance_mobs(bot_id); let mut p = Player::new(bot_id, self.id, &name(), cryps).set_bot(true); p.set_ready(true); p }) .take(15) .collect::>(); self } fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { 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) } pub fn player_update(mut self, player: Player, ignore_phase: bool) -> Result { if !ignore_phase && self.phase != InstancePhase::InProgress { return Err(format_err!("instance not in vbox phase ({:?})", self.phase)); } let i = self.players .iter() .position(|p| p.id == player.id) .ok_or(err_msg("player_id not found"))?; self.players[i] = player; Ok(self) } fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Instance, Error> { if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { return Err(err_msg("instance not in start or vbox phase")); } let i = self.players .iter_mut() .position(|p| p.id == player_id) .ok_or(err_msg("player_id not found"))?; if self.phase != InstancePhase::Lobby && self.players[i].cryps.iter().all(|c| c.skills.len() == 0) { return Err(err_msg("your cryps have no skills")); } let v = !self.players[i].ready; self.players[i].set_ready(v); if self.phase == InstancePhase::Lobby && self.can_start() { self.start(); } Ok(self) } fn player_has_pve_game(&self, player_id: Uuid) -> bool { let opponent_id = self.current_round(player_id).player_ids .iter() .find(|p| **p != player_id) .expect("unable to find opponent"); return self.players .iter() .find(|p| p.id == *opponent_id) .expect("unable to find opponent") .bot; } fn bot_vs_player_game(&self, player_id: Uuid) -> Result { let current_round = self.current_round(player_id); let bot_id = current_round.player_ids.iter().find(|id| **id != player_id).unwrap(); let plr = self.players.clone().into_iter().find(|p| p.id == player_id).unwrap(); let bot = self.players.clone().into_iter().find(|p| p.id == *bot_id).unwrap(); let mut game = Game::new(); game.id = current_round.game_id; game .set_team_num(2) .set_team_size(3) .set_instance(self.id); // add the players let mut plr_team = Team::new(plr.id); plr_team.set_cryps(plr.cryps); let mut bot_team = Team::new(bot.id); bot_team.set_cryps(bot.cryps); bot_team.set_bot(); game .team_add(plr_team)? .team_add(bot_team)?; game = game.start(); Ok(game) } 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.open = false; self.next_round() } fn next_round(&mut self) -> &mut Instance { self.phase = InstancePhase::InProgress; self.players.iter_mut().for_each(|p| { p.ready = false; p.vbox.fill(); }); self.generate_rounds(); self.bot_vbox_phase(); self.bot_games_phase(); self } fn all_ready(&self) -> bool { self.players.iter().all(|p| p.ready) } fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { let round_num = self.rounds.len() - 1; self.rounds[round_num] .iter_mut() .find(|r| r.game_id == game.id) .ok_or(err_msg("could not find matchup in current round"))? .finished = true; let winner = game.winner().ok_or(err_msg("game not finished"))?; for team in game.teams.iter() { let mut player = self.account_player(team.id)?; match team.id == winner.id { true => player.add_win(), false => player.add_loss(), }; } if self.all_games_finished() { self.next_round(); } Ok(self) } fn all_games_finished(&self) -> bool { match self.rounds.last() { Some(r) => r.iter().all(|g| g.finished), None => true, } } fn bot_vbox_phase(&mut self) -> &mut Instance { for bot in self.players.iter_mut().filter(|p| p.bot) { bot.vbox.fill(); bot.autobuy(); bot.set_ready(true); } self } fn bot_games_phase(&mut self) -> &mut Instance { if self.phase != InstancePhase::InProgress { panic!("instance not in progress phase"); } let r = self.rounds.len() - 1; // println!("round num {:?}", r); // println!("{:?}", self.rounds[r]); for mut round in self.rounds[r].iter_mut() { if self.players .iter() .filter(|p| round.player_ids.contains(&p.id) && p.bot && p.ready) .count() == 2 { // println!("should play a game between {:?}", round.player_ids); let a = self.players.clone().into_iter().find(|p| p.id == round.player_ids[0]).unwrap(); let b = self.players.clone().into_iter().find(|p| p.id == round.player_ids[1]).unwrap(); // println!("{:?} vs {:?}", a.name, b.name); let mut game = Game::new(); game .set_team_num(2) .set_team_size(3); // add the players let mut a_team = Team::new(a.id); a_team.set_cryps(a.cryps); a_team.set_bot(); let mut b_team = Team::new(b.id); b_team.set_cryps(b.cryps); b_team.set_bot(); game .team_add(a_team).unwrap() .team_add(b_team).unwrap(); game = game.start(); assert!(game.finished()); let winner = match game.winner() { Some(w) => w, None => panic!("game has no winner {:?}", game), }; round.finished = true; for team in game.teams.iter() { let mut player = self.players.iter_mut().find(|p| p.id == team.id).unwrap(); match team.id == winner.id { true => player.add_win(), false => player.add_loss(), }; } } } self } fn generate_rounds(&mut self) -> &mut Instance { let round_num = self.rounds.len(); let mut matched_players = self.players .iter() .map(|p| p.id) .collect::>(); let np = matched_players.len(); if round_num > 0 { matched_players.rotate_right(round_num % np); matched_players.swap(0,1); } // only set up for even player numbers atm // no byes let current_round = matched_players[0..(np / 2)] .iter() .enumerate() .map(|(i, id)| Round { player_ids: vec![*id, matched_players[np - (i + 1)]], game_id: Uuid::new_v4(), finished: false, }) .collect::>(); self.rounds.push(current_round); self } fn current_round(&self, player_id: Uuid) -> &Round { let round_num = self.rounds.len() - 1; let current_round = self.rounds[round_num] .iter() .find(|g| g.player_ids.contains(&player_id)) .unwrap(); current_round } fn current_game(&mut self, player_id: Uuid) -> Option { if self.phase == InstancePhase::Lobby { return None; } let current_round = self.current_round(player_id); let can_start = self.players .iter() .filter(|p| current_round.player_ids.contains(&p.id)) .all(|p| p.ready); match can_start { true => Some(current_round.game_id), false => None, } } // 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")) } pub fn vbox_discard(mut self, account: Uuid) -> Result { self.account_player(account)? .vbox_discard()?; Ok(self) } pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize) -> Result { self.account_player(account)? .vbox_accept(group, index)?; Ok(self) } pub fn vbox_combine(mut self, account: Uuid, indices: Vec) -> Result { self.account_player(account)? .vbox_combine(indices)?; Ok(self) } pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result { self.account_player(account)? .vbox_reclaim(index)?; Ok(self) } pub fn vbox_apply(mut self, account: Uuid, index: usize, cryp_id: Uuid) -> Result { self.account_player(account)? .vbox_apply(index, cryp_id)?; Ok(self) } pub fn vbox_unequip(mut self, account: Uuid, target: Var, cryp_id: Uuid) -> Result { self.account_player(account)? .vbox_unequip(target, cryp_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, open) VALUES ($1, $2, $3) RETURNING id; "; let result = tx .query(query, &[&instance.id, &instance_bytes, &instance.open])?; 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, open = $2, updated_at = now() WHERE id = $3 RETURNING id, data; "; let result = tx .query(query, &[&instance_bytes, &instance.open, &instance.id])?; result.iter().next().ok_or(err_msg("no instance row returned"))?; // println!("{:?} wrote instance", instance.id); return Ok(instance); } 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)); } println!("instance deleted {:?}", id); return Ok(()); } pub fn instance_get_open(tx: &mut Transaction) -> Result { let query = " SELECT * FROM instances WHERE open = true; "; let result = tx .query(query, &[])?; 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 instances_afk(tx: &mut Transaction) -> Result, Error> { let query = " SELECT data, id FROM instances WHERE updated_at < now() - interval '5 seconds'; "; 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_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = match params.players { 1 => Instance::new() .set_max_players(16)? .set_name(params.name)? .add_bots(), _ => Instance::new() .set_max_players(params.players)? .set_name(params.name)?, }; instance = instance_create(tx, instance)?; let join_params = InstanceJoinParams { instance_id: instance.id, cryp_ids: params.cryp_ids }; instance_join(join_params, tx, account) } pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = instance_get(tx, params.instance_id)?; let cryps = params.cryp_ids .iter() .map(|id| cryp_get(tx, *id, account.id)) .collect::, Error>>()?; if cryps.len() != 3 { return Err(format_err!("incorrect team size. ({:})", 3)); } let player = player_create(tx, Player::new(account.id, instance.id, &account.name, cryps), account)?; instance.add_player(player)?; instance_update(tx, instance) } pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: Player) -> Result { // get the game let game = match game_global_get(tx) { Ok(g) => { println!("received global game {:?}", g.id); // if there is one try to join match game_instance_join(tx, player.clone(), g.id) { Ok(g) => g, // if fails make a new one Err(_e) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, } }, // if not found make a new one Err(_) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, }; // set the current game game_global_set(tx, &game)?; Ok(game) } pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = instance_get(tx, params.instance_id)?; let player_id = instance.account_player(account.id)?.id; instance.player_ready(player_id)?; if let Some(game_id) = instance.current_game(player_id) { match instance.player_has_pve_game(player_id) { true => match game_get(tx, game_id) { Ok(g) => g, Err(_) => { let game = instance.bot_vs_player_game(player_id)?; game_write(tx, &game)?; game }, }, false => { let opponent_id = *instance .current_round(account.id) .player_ids .iter() .find(|p| **p != account.id) .expect("could not find opponent"); let a = instance.account_player(account.id)?.clone(); let b = instance.account_player(opponent_id)?.clone(); let teams = vec![a, b]; game_instance_new(tx, teams, game_id, instance.id)? } }; } instance_update(tx, instance) } pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = instance_get(tx, params.instance_id)?; if let Some(game_id) = instance.current_game(account.id) { let game = game_get(tx, game_id)?; if game.phase != Phase::Finish { return Ok(RpcResult::GameState(game)) } } Ok(RpcResult::InstanceState(instance)) } pub fn global_game_finished(tx: &mut Transaction, game: &Game) -> Result<(), Error> { let winner = game.winner().ok_or(err_msg("game not finished"))?; for team in game.teams.iter() { let mut player = player_get(tx, team.id, Uuid::nil())?; match team.id == winner.id { true => player.add_win(), false => player.add_loss(), }; player.vbox.fill(); player_update(tx, player, true)?; } Ok(()) } 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)?; // println!("{:?}", instance_get(tx, instance_id)?); instance_update(tx, instance)?; Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn instance_pve_test() { let mut instance = Instance::new() .set_max_players(16).expect("unable to set max players") .add_bots(); let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); let player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); let player_id = player.id; instance.add_player(player).expect("could not add player"); assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(player_id).unwrap(); assert_eq!(instance.phase, InstancePhase::InProgress); assert_eq!(instance.rounds[0].len(), 8); instance.player_ready(player_id).unwrap(); assert!(instance.all_games_finished()); instance.next_round(); instance.player_ready(player_id).unwrap(); instance.next_round(); instance.player_ready(player_id).unwrap(); assert_eq!(instance.rounds.len(), 3); } #[test] fn instance_bot_vbox_test() { let instance = Instance::new(); let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); let _player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); } #[test] fn instance_start_test() { let mut instance = Instance::new() .set_max_players(2) .expect("could not create instance"); assert_eq!(instance.max_players, 2); let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); let player = Player::new(player_account, instance.id, &"a".to_string(), cryps); 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 cryps = instance_mobs(player_account); let player = Player::new(player_account, instance.id, &"b".to_string(), cryps); 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()); } }