mnml/server/src/instance.rs
2019-04-29 15:31:12 +10:00

798 lines
23 KiB
Rust

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<Uuid>,
game_id: Uuid,
finished: bool,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Instance {
id: Uuid,
players: Vec<Player>,
phase: InstancePhase,
rounds: Vec<Vec<Round>>,
open: bool,
max_players: usize,
password: Option<String>,
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,
}
}
pub fn upkeep(mut self) -> Instance {
self
}
fn set_max_players(mut self, max: usize) -> Result<Instance, Error> {
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<Instance, Error> {
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::<Vec<Player>>();
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<Instance, Error> {
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<Game, Error> {
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::<Vec<Uuid>>();
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::<Vec<Round>>();
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(&self, player_id: Uuid) -> Option<Uuid> {
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 => match current_round.finished {
true => None,
false => 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_action_allowed(&self, account: Uuid) -> Result<(), Error> {
if self.phase == InstancePhase::Lobby {
return Err(err_msg("game not yet started"));
}
if self.current_game(account).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<Instance, Error> {
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) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?;
self.account_player(account)?
.vbox_accept(group, index)?;
Ok(self)
}
pub fn vbox_combine(mut self, account: Uuid, indices: Vec<usize>) -> Result<Instance, Error> {
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<Instance, Error> {
self.vbox_action_allowed(account)?;
self.account_player(account)?
.vbox_reclaim(index)?;
Ok(self)
}
pub fn vbox_apply(mut self, account: Uuid, index: usize, cryp_id: Uuid) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?;
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<Instance, Error> {
self.vbox_action_allowed(account)?;
self.account_player(account)?
.vbox_unequip(target, cryp_id)?;
Ok(self)
}
}
pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
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<Instance, Error> {
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<Instance, Error> {
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<u8> = returned.get("data");
let instance = from_slice::<Instance>(&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<Instance, Error> {
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<u8> = returned.get("data");
let instance = from_slice::<Instance>(&instance_bytes)?;
return Ok(instance);
}
pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
WHERE updated_at < now() - interval '5 seconds'
AND id != '00000000-0000-0000-0000-000000000000';
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&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<Instance, Error> {
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<Instance, Error> {
let mut instance = instance_get(tx, params.instance_id)?;
let cryps = params.cryp_ids
.iter()
.map(|id| cryp_get(tx, *id, account.id))
.collect::<Result<Vec<Cryp>, 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<Game, Error> {
// 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<Instance, Error> {
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<RpcResult, Error> {
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());
}
}