warden working

This commit is contained in:
ntr 2019-05-01 00:59:45 +10:00
parent d3c301a3ac
commit 5d7095a06d
5 changed files with 320 additions and 251 deletions

View File

@ -153,15 +153,39 @@ function Info(args) {
);
}
function playerRound(id) {
if (!instance.rounds.length) return null;
return instance.rounds[instance.rounds.length - 1].find(r => r.player_ids.includes(id));
}
function playerText(player) {
const round = playerRound(player.id);
if (!round) {
return player.ready
? 'ready'
: '';
}
if (round.finished) return 'finished';
return player.ready
? 'ready'
: '';
}
function scoreBoard() {
const players = instance.players.map((p, i) =>
<tr key={i}
className={p.ready ? 'ready' : ''}>
<td>{p.name}</td>
<td>{p.score.wins} / {p.score.losses}</td>
<td>{p.ready ? 'ready' : ''}</td>
</tr>
);
const players = instance.players.map((p, i) => {
const pText = playerText(p);
console.log(pText);
return (
<tr key={i}
className={p.ready ? 'ready' : ''}>
<td>{p.name}</td>
<td>{p.score.wins} / {p.score.losses}</td>
<td>{pText}</td>
</tr>
);
});
return (
<table>

View File

@ -26,16 +26,6 @@ pub enum Phase {
Finish,
}
#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
pub enum GameMode {
Normal,
Pvp,
Zone3v2Attack,
Zone2v2Caster,
Zone3v3MeleeMiniboss,
Zone3v3HealerBoss,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Game {
pub id: Uuid,
@ -47,7 +37,6 @@ pub struct Game {
pub resolved: Vec<Resolution>,
pub log: Vec<String>,
pub instance: Option<Uuid>,
pub mode: GameMode,
phase_start: DateTime<Utc>,
}
@ -63,7 +52,6 @@ impl Game {
resolved: vec![],
log: vec![],
instance: None,
mode: GameMode::Normal,
phase_start: Utc::now(),
};
}
@ -83,16 +71,11 @@ impl Game {
self
}
pub fn set_mode(&mut self, mode: GameMode) -> &mut Game {
self.mode = mode;
self
}
pub fn joinable(&self) -> bool {
self.can_start()
}
pub fn player_add(&mut self, player: Player) -> Result<&mut Game, Error> {
pub fn player_add(&mut self, mut player: Player) -> Result<&mut Game, Error> {
if self.players.len() == self.player_num {
return Err(err_msg("maximum number of players"));
}
@ -102,11 +85,12 @@ impl Game {
}
if player.cryps.iter().all(|c| c.skills.len() == 0) {
return Err(err_msg("your cryps have no skills"));
println!("WARNING: {:?} has no skills and has forfeited {:?}", player.name, self.id);
player.forfeit();
}
let player_description = player.cryps.iter().map(|c| c.name.clone()).collect::<Vec<String>>().join(", ");
self.log.push(format!("{:} has joined the game.", player_description));
self.log.push(format!("{:} has joined the game. [{:?}]", player.name, player_description));
self.players.push(player);
@ -162,13 +146,19 @@ impl Game {
self
}
fn can_start(&self) -> bool {
pub fn can_start(&self) -> bool {
return self.players.len() == self.player_num
&& self.players.iter().all(|t| t.cryps.len() == self.player_cryps)
}
pub fn start(mut self) -> Game {
self.log.push("Game starting...".to_string());
// forfeit
if self.finished() {
return self.finish();
}
self.skill_phase_start()
}
@ -190,6 +180,7 @@ impl Game {
self.phase = Phase::Skill;
self.pve_add_skills();
if self.skill_phase_finished() {
return self.resolve_phase_start()
}
@ -317,6 +308,10 @@ impl Game {
}
fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> {
if self.phase != Phase::Skill {
return Err(err_msg("game not in skill phase"));
}
self.player_by_id(player_id)?
.set_ready(true);
@ -571,10 +566,12 @@ impl Game {
}
pub fn upkeep(mut self) -> Game {
if self.phase != Phase::Skill {
panic!("{:?} game not in skill phase during upkeep", self);
if self.phase == Phase::Finish {
return self;
}
println!("upkeep beginning: {:?} vs {:?}", self.players[0].name, self.players[1].name);
if !self.phase_timed_out() {
return self;
}
@ -583,8 +580,10 @@ impl Game {
if !player.ready {
player.set_ready(true);
player.add_warning();
println!("upkeep: {:?} warned", player.name);
if player.warnings >= 3 {
player.forfeit();
println!("upkeep: {:?} forfeited", player.name);
self.log.push(format!("{:?} forfeited.", player.name));
}
}
@ -891,8 +890,7 @@ pub fn game_instance_new(tx: &mut Transaction, players: Vec<Player>, game_id: Uu
game
.set_player_num(2)
.set_player_cryps(3)
.set_instance(instance_id)
.set_mode(GameMode::Pvp);
.set_instance(instance_id);
// create the initiators player
for player in players {

View File

@ -9,12 +9,16 @@ use failure::err_msg;
use std::iter;
// timekeeping
use chrono::prelude::*;
use chrono::Duration;
use rpc::{InstanceLobbyParams, InstanceJoinParams, InstanceReadyParams, InstanceStateParams};
use account::Account;
use player::{Player, player_create, player_get, player_global_update};
use cryp::{Cryp, cryp_get};
use mob::{instance_mobs};
use game::{Game, Phase, game_get, game_write, game_instance_new, game_instance_join};
use game::{Game, Phase, game_get, game_write, game_instance_new};
use vbox::{Var};
use rpc::{RpcResult};
use names::{name};
@ -29,7 +33,7 @@ enum InstancePhase {
#[derive(Debug,Clone,Serialize,Deserialize)]
struct Round {
player_ids: Vec<Uuid>,
game_id: Uuid,
game_id: Option<Uuid>,
finished: bool,
}
@ -41,8 +45,10 @@ pub struct Instance {
rounds: Vec<Vec<Round>>,
open: bool,
max_players: usize,
max_rounds: usize,
password: Option<String>,
pub name: String,
phase_start: DateTime<Utc>,
}
impl Instance {
@ -54,8 +60,10 @@ impl Instance {
phase: InstancePhase::Lobby,
open: true,
max_players: 2,
max_rounds: 16,
name: String::new(),
password: None,
phase_start: Utc::now(),
}
}
@ -67,13 +75,42 @@ impl Instance {
phase: InstancePhase::InProgress,
open: false,
max_players: 0,
max_rounds: 1,
name: "Global Matchmaking".to_string(),
password: None,
phase_start: Utc::now(),
}
}
pub fn upkeep(mut self) -> Instance {
self
fn phase_timed_out(&self) -> bool {
Utc::now().signed_duration_since(self.phase_start).num_seconds() > 60
}
fn timed_out_players(&self) -> Vec<Uuid> {
self.players
.iter()
.filter(|p| !p.ready)
.filter(|p| self.current_game_id(p.id).is_none())
.map(|p| p.id)
.collect::<Vec<Uuid>>()
}
pub fn upkeep(mut self) -> (Instance, Vec<Game>) {
if self.phase != InstancePhase::InProgress {
return (self, vec![]);
}
if !self.phase_timed_out() {
return (self, vec![]);
}
let new_games = self
.timed_out_players()
.iter()
.filter_map(|p| self.player_ready(*p).unwrap())
.collect::<Vec<Game>>();
(self, new_games)
}
fn set_max_players(mut self, max: usize) -> Result<Instance, Error> {
@ -94,6 +131,15 @@ impl Instance {
Ok(self)
}
fn set_max_rounds(mut self, rounds: usize) -> Result<Instance, Error> {
if rounds == 0 {
return Err(err_msg("max rounds must be nonzero"));
}
self.max_rounds = rounds;
Ok(self)
}
fn add_bots(mut self) -> Instance {
self.open = false;
self.players = iter::repeat_with(|| {
@ -118,79 +164,98 @@ impl Instance {
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> {
fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, 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"))?;
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();
// start the game even if afk noobs have no skills
if !self.phase_timed_out() && self.players[i].cryps.iter().all(|c| c.skills.len() == 0) {
return Err(err_msg("your cryps have no skills"));
}
Ok(self)
// create a game object if both players are ready
// this should only happen once
let all_ready = self.round_ready_check(player_id);
if !all_ready {
return Ok(None);
}
let game = self.create_round_game(player_id);
{
let round_num = self.rounds.len() - 1;
let current_round = self.rounds[round_num]
.iter_mut()
.find(|g| g.player_ids.contains(&player_id))
.unwrap();
current_round.game_id = Some(game.id);
}
return Ok(Some(game));
}
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> {
fn round_ready_check(&mut self, player_id: Uuid) -> bool {
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();
self.players
.iter()
.filter(|p| current_round.player_ids.contains(&p.id))
.all(|p| p.ready)
}
// maybe just embed the games in the instance
// but seems hella inefficient
fn create_round_game(&self, player_id: Uuid) -> Game {
let current_round = self.current_round(player_id);
let mut game = Game::new();
game.id = current_round.game_id;
game
.set_player_num(2)
.set_player_cryps(3)
.set_instance(self.id);
game
.player_add(plr)?
.player_add(bot)?;
// create the initiators player
for player in self.players
.clone()
.into_iter()
.filter(|p| current_round.player_ids.contains(&p.id)) {
game.player_add(player).unwrap();
}
game = game.start();
Ok(game)
assert!(game.can_start());
return game.start();
}
fn can_start(&self) -> bool {
@ -205,120 +270,47 @@ impl Instance {
fn next_round(&mut self) -> &mut Instance {
self.phase = InstancePhase::InProgress;
self.phase_start = Utc::now();
if self.rounds.len() >= self.max_rounds {
return self.finish();
}
self.players.iter_mut().for_each(|p| {
p.ready = false;
p.set_ready(false);
p.vbox.fill();
});
self.generate_rounds();
self.bot_vbox_phase();
self.bot_games_phase();
self.bot_round_actions();
self
}
fn all_ready(&self) -> bool {
self.players.iter().all(|p| p.ready)
fn finish(&mut self) -> &mut Instance {
self.phase = InstancePhase::Finished;
self
}
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;
// if you don't win, you lose
// ties can happen if both players forfeit
let winner_id = match game.winner() {
Some(w) => w.id,
None => Uuid::nil(),
};
for player in game.players.iter() {
let mut player = self.account_player(player.id)?;
match player.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 {
fn bot_round_actions(&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
}
let games = self.players
.clone()
.iter()
.filter(|b| b.bot)
.filter_map(|b| self.player_ready(b.id).unwrap())
.collect::<Vec<Game>>();
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_player_num(2)
.set_player_cryps(3);
game
.player_add(a).unwrap()
.player_add(b).unwrap();
game = game.start();
if !game.finished() {
panic!("game not finished {:?}", game)
}
let winner = match game.winner() {
Some(w) => w,
None => panic!("game has no winner {:?}", game),
};
round.finished = true;
for player in game.players.iter() {
let mut player = self.players.iter_mut().find(|p| p.id == player.id).unwrap();
match player.id == winner.id {
true => player.add_win(),
false => player.add_loss(),
};
}
}
for game in games {
if game.finished() {
self.game_finished(&game).unwrap();
} else {
println!("{:?} unfishededes", game);
}
}
self
@ -345,7 +337,7 @@ impl Instance {
.enumerate()
.map(|(i, id)| Round {
player_ids: vec![*id, matched_players[np - (i + 1)]],
game_id: Uuid::new_v4(),
game_id: None,
finished: false,
})
.collect::<Vec<Round>>();
@ -366,24 +358,59 @@ impl Instance {
current_round
}
fn current_game(&self, player_id: Uuid) -> Option<Uuid> {
fn current_game_id(&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);
if current_round.finished || current_round.game_id.is_none() {
return None;
}
match can_start {
true => match current_round.finished {
true => None,
false => Some(current_round.game_id),
},
false => None,
return current_round.game_id;
}
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
let round_num = self.rounds.len() - 1;
self.rounds[round_num]
.iter_mut()
.filter(|r| r.game_id.is_some())
.find(|r| r.game_id.unwrap() == game.id)
.ok_or(err_msg("could not find matchup in current round"))?
.finished = true;
// if you don't win, you lose
// ties can happen if both players forfeit
let winner_id = match game.winner() {
Some(w) => w.id,
None => Uuid::nil(),
};
for player in game.players.iter() {
let mut player = self.account_player(player.id)?;
match player.id == winner_id {
true => player.add_win(),
false => player.add_loss(),
};
}
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.iter().all(|g| g.finished),
None => true,
}
}
@ -399,7 +426,7 @@ impl Instance {
if self.phase == InstancePhase::Lobby {
return Err(err_msg("game not yet started"));
}
if self.current_game(account).is_some() {
if self.current_game_id(account).is_some() {
return Err(err_msg("you cannot perform vbox actions while in a game"));
}
@ -552,8 +579,7 @@ pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Erro
let query = "
SELECT data, id
FROM instances
WHERE updated_at < now() - interval '5 seconds'
AND id != '00000000-0000-0000-0000-000000000000';
WHERE id != '00000000-0000-0000-0000-000000000000';
";
let result = tx
@ -636,32 +662,9 @@ pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account:
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 players = vec![a, b];
game_instance_new(tx, players, game_id, instance.id)?
}
};
if let Some(game) = instance.player_ready(player_id)? {
game_write(tx, &game)?;
}
instance_update(tx, instance)
@ -670,11 +673,11 @@ pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account
pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result<RpcResult, Error> {
let instance = instance_get(tx, params.instance_id)?;
if let Some(game_id) = instance.current_game(account.id) {
if let Some(game_id) = instance.current_game_id(account.id) {
let game = game_get(tx, game_id)?;
// return the game until it's finished
if game.phase != Phase::Finish {
return Ok(RpcResult::GameState(game))
}
}
@ -716,6 +719,7 @@ mod tests {
fn instance_pve_test() {
let mut instance = Instance::new()
.set_max_players(16).expect("unable to set max players")
.set_max_rounds(2).expect("max rounds failure")
.add_bots();
let player_account = Uuid::new_v4();
@ -728,21 +732,9 @@ mod tests {
assert_eq!(instance.phase, InstancePhase::Lobby);
instance.player_ready(player_id).unwrap();
assert_eq!(instance.phase, InstancePhase::InProgress);
assert_eq!(instance.phase, InstancePhase::Finished);
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);
assert_eq!(instance.rounds.len(), 2);
}
#[test]
@ -784,5 +776,55 @@ mod tests {
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()
.set_max_players(2)
.expect("could not create instance");
let player_account = Uuid::new_v4();
let cryps = instance_mobs(player_account);
let player = Player::new(player_account, &"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, &"b".to_string(), cryps);
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_start = Utc::now().checked_sub_signed(Duration::seconds(61)).unwrap();
let (mut instance, new_games) = instance.upkeep();
assert_eq!(new_games.len(), 1);
let game = &new_games[0];
assert!(game.finished());
instance.game_finished(game).unwrap();
assert_eq!(instance.rounds.len(), 2);
assert!(instance.players.iter().all(|p| !p.ready));
println!("{:#?}", instance);
}
}

View File

@ -73,14 +73,12 @@ impl Player {
pub fn add_win(&mut self) -> &mut Player {
self.score.wins += 1;
self.set_ready(false);
self.vbox.balance_add(12);
self
}
pub fn add_loss(&mut self) -> &mut Player {
self.score.losses += 1;
self.set_ready(false);
self.vbox.balance_add(9);
self
}

View File

@ -1,25 +1,24 @@
use std::time::{Duration};
use std::thread::sleep;
// Db Commons
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
use r2d2::{Pool};
use r2d2_postgres::{PostgresConnectionManager};
use game::{Game, games_need_upkeep, game_update};
use instance::{Instance, instances_need_upkeep, instance_update};
use game::{games_need_upkeep, game_update, game_write, game_delete};
use instance::{instances_need_upkeep, instance_update};
use net::{Db};
fn fetch_games(mut tx: Transaction) -> Result<Transaction, Error> {
let games = games_need_upkeep(&mut tx)?;
println!("warden: {:?} games active", games.len());
for mut game in games {
game_update(&mut tx, &game.upkeep())?;
let game = game.upkeep();
match game_update(&mut tx, &game) {
Ok(_) => (),
Err(e) => {
println!("{:?}", e);
game_delete(&mut tx, game.id)?;
}
}
}
Ok(tx)
@ -29,18 +28,26 @@ fn fetch_instances(mut tx: Transaction) -> Result<Transaction, Error> {
let instances = instances_need_upkeep(&mut tx)?;
for mut instance in instances {
instance_update(&mut tx, instance.upkeep())?;
let (instance, new_games) = instance.upkeep();
println!("{:?} new games", new_games.len());
for game in new_games {
game_write(&mut tx, &game)?;
}
instance_update(&mut tx, instance)?;
}
Ok(tx)
}
pub fn warden(db: Db) -> Result<(), Error> {
println!("upkeep beginning");
fetch_games(db.transaction()?)?
.commit()?;
fetch_instances(db.transaction()?)?
.commit()?;
println!("upkeep done");
Ok(())
}