use uuid::Uuid; use rand::prelude::*; use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account::Account; use cryp::{Cryp, Colours, cryp_get}; use vbox::{Vbox, Var, VarEffect}; use rpc::{PlayerCrypsSetParams}; use instance::{Instance, instance_get, instance_update}; use skill::{Effect}; const DISCARD_COST: u16 = 5; #[derive(Debug,Clone,Copy,Serialize,Deserialize)] pub struct Score { pub wins: u8, pub losses: u8, } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Player { pub id: Uuid, pub name: String, pub vbox: Vbox, pub score: Score, pub cryps: Vec, pub bot: bool, pub ready: bool, pub warnings: u8, } impl Player { pub fn new(account: Uuid, name: &String, cryps: Vec) -> Player { Player { id: account, name: name.clone(), vbox: Vbox::new(), score: Score { wins: 0, losses: 0 }, cryps, bot: false, ready: false, warnings: 0, } } pub fn set_bot(mut self, bot: bool) -> Player { self.bot = bot; self } pub fn set_ready(&mut self, ready: bool) -> &mut Player { self.ready = ready; self } pub fn add_warning(&mut self) -> &mut Player { self.warnings += 1; self } pub fn forfeit(&mut self) -> &mut Player { for cryp in self.cryps.iter_mut() { cryp.force_ko(); } self } 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 } pub fn cryp_get(&mut self, id: Uuid) -> Result<&mut Cryp, Error> { self.cryps.iter_mut().find(|c| c.id == id).ok_or(err_msg("cryp not found")) } pub fn autobuy(&mut self) -> &mut Player { let mut rng = thread_rng(); // first check if any cryps have no skills // if there is one find an item in vbox that gives a skill while let Some(c) = self.cryps.iter().position(|c| c.skills.len() == 0) { if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) { let cryp_id = self.cryps[c].id; self.vbox_apply(s, cryp_id).expect("could not apply"); continue; } println!("no skills available..."); } // now keep buying and applying items cause whynot // inb4 montecarlo gan loop { let (target_cryp_i, target_cryp_id) = match self.cryps.iter().any(|c| c.skills.len() < 3) { true => { let mut target_cryp_i = 0; for (j, c) in self.cryps.iter().enumerate() { if c.skills.len() < self.cryps[target_cryp_i].skills.len() { target_cryp_i = j; } } (target_cryp_i, self.cryps[target_cryp_i].id) }, false => { let i = rng.gen_range(0, 3); (i, self.cryps[i].id) }, }; let needs_skills = self.cryps[target_cryp_i].skills.len() < 3; let group_i = match needs_skills { true => 1, false => 2, }; let num_colours = self.vbox.bound .iter() .filter(|v| [Var::Red, Var::Green, Var::Blue].contains(v)) .count(); if self.vbox.bound.len() < 3 || num_colours < 2 { if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 { // println!("insufficient balance"); break; } // get 2 colours and something else if self.vbox.free[0].len() < 2 { break; } self.vbox_accept(0, 0).expect("could't accept colour 0"); self.vbox_accept(0, 0).expect("could't accept colour 1"); self.vbox_accept(group_i, 0).expect("could't accept group 0"); } // println!("{:?}", self.vbox.bound); let skills = [Var::Attack, Var::Block, Var::Buff, Var::Debuff, Var::Stun]; let combo_i = match group_i { 1 => self.vbox.bound.iter().position(|v| skills.contains(v)).expect("no skill found"), 2 => self.vbox.bound.iter().position(|v| v.into_spec().is_some()).expect("no spec found"), _ => panic!("unknown group_i"), }; // first 2 colours can be whatever self.vbox_combine(vec![0, 1, combo_i]).ok(); let var_i = self.vbox.bound.len() - 1; self.vbox_apply(var_i, target_cryp_id).ok(); } return self; } pub fn vbox_discard(&mut self) -> Result<&mut Player, Error> { self.vbox.balance_sub(DISCARD_COST)?; self.vbox.fill(); Ok(self) } pub fn vbox_accept(&mut self, group: usize, index: usize) -> Result<&mut Player, Error> { self.vbox.accept(group, index)?; Ok(self) } pub fn vbox_combine(&mut self, indices: Vec) -> Result<&mut Player, Error> { self.vbox.combine(indices)?; Ok(self) } pub fn vbox_reclaim(&mut self, index: usize) -> Result<&mut Player, Error> { self.vbox.reclaim(index)?; Ok(self) } pub fn vbox_apply(&mut self, index: usize, cryp_id: Uuid) -> Result<&mut Player, Error> { let var = self.vbox.bound.remove(index); match var.effect() { Some(VarEffect::Skill) => { let skill = var.into_skill().ok_or(format_err!("var {:?} has no associated skill", var))?; let cryp = self.cryp_get(cryp_id)?; // done here because i teach them a tonne of skills for tests let max_skills = 3; if cryp.skills.len() >= max_skills { return Err(format_err!("cryp at max skills ({:?})", max_skills)); } if cryp.knows(skill) { return Err(format_err!("cryp already knows skill ({:?})" , skill)); } cryp.learn_mut(skill); }, Some(VarEffect::Spec) => { let spec = var.into_spec().ok_or(format_err!("var {:?} has no associated spec", var))?; let cryp = self.cryp_get(cryp_id)?; cryp.spec_add(spec)?; }, None => return Err(err_msg("var has no effect on cryps")), } // now the var has been applied // recalculate the stats of the whole player let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { Colours { red: tc.red + c.colours.red, green: tc.green + c.colours.green, blue: tc.blue + c.colours.blue } }); for cryp in self.cryps.iter_mut() { cryp.apply_modifiers(&player_colours); } Ok(self) } pub fn vbox_unequip(&mut self, target: Var, cryp_id: Uuid) -> Result<&mut Player, Error> { if self.vbox.bound.len() >= 9 { return Err(err_msg("too many vars bound")); } match target.effect() { Some(VarEffect::Skill) => { let skill = target.into_skill().ok_or(format_err!("var {:?} has no associated skill", target))?; let cryp = self.cryp_get(cryp_id)?; cryp.forget(skill)?; }, Some(VarEffect::Spec) => { let spec = target.into_spec().ok_or(format_err!("var {:?} has no associated spec", target))?; let cryp = self.cryp_get(cryp_id)?; cryp.spec_remove(spec)?; }, None => return Err(err_msg("var has no effect on cryps")), } // now the var has been applied // recalculate the stats of the whole player let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { Colours { red: tc.red + c.colours.red, green: tc.green + c.colours.green, blue: tc.blue + c.colours.blue } }); for cryp in self.cryps.iter_mut() { cryp.apply_modifiers(&player_colours); } self.vbox.bound.push(target); self.vbox.bound.sort_unstable(); Ok(self) } // GAME METHODS pub fn skills_required(&self) -> usize { let required = self.cryps.iter() .filter(|c| !c.is_ko()) .filter(|c| c.available_skills().len() > 0) .collect::>().len(); // println!("{:} requires {:} skills this turn", self.id, required); return required; } pub fn taunting(&self) -> Option<&Cryp> { self.cryps.iter() .find(|c| c.affected(Effect::Taunt)) } pub fn set_cryps(&mut self, mut cryps: Vec) -> &mut Player { cryps.sort_unstable_by_key(|c| c.id); self.cryps = cryps; self } pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { self.cryps.iter_mut().find(|c| c.id == id) } } pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> Result { let query = " SELECT * FROM players WHERE account = $1 AND instance = $2 FOR UPDATE; "; let result = tx .query(query, &[&account_id, &instance_id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("player not found")), }; // tells from_slice to cast into a cryp let bytes: Vec = returned.get("data"); let data = from_slice::(&bytes)?; return Ok(data); } pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result { let player_bytes = to_vec(&player)?; let query = " INSERT INTO players (id, instance, account, data) VALUES ($1, $2, $3, $4) RETURNING id, account; "; let result = tx .query(query, &[&Uuid::new_v4(), &instance, &account.id, &player_bytes])?; let _returned = result.iter().next().expect("no row written"); println!("wrote player {:} joined instance: {:}", account.name, instance); return Ok(player); } pub fn player_global_update(tx: &mut Transaction, mut player: Player, ignore_phase: bool) -> Result { // sort vbox for niceness player.vbox.bound.sort_unstable(); let bytes = to_vec(&player)?; let query = " UPDATE players SET data = $1, updated_at = now() WHERE id = $2 RETURNING id, data; "; let result = tx .query(query, &[&bytes, &player.id])?; result.iter().next().ok_or(format_err!("player {:?} could not be written", player))?; Ok(player) } pub fn player_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " DELETE FROM players WHERE id = $1; "; let result = tx .execute(query, &[&id])?; if result != 1 { return Err(format_err!("unable to delete player {:?}", id)); } println!("player deleted {:?}", id); return Ok(()); } pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, account: &Account) -> Result { if params.cryp_ids.len() != 3 { return Err(err_msg("player size is 3")); } let cryps = params.cryp_ids .iter() .map(|id| cryp_get(tx, *id, account.id)) .collect::, Error>>()?; let player = match player_get(tx, account.id, Uuid::nil()) { Ok(mut p) => { p.cryps = cryps; p.vbox = Vbox::new(); player_global_update(tx, p, false)? }, Err(_) => { player_create(tx, Player::new(account.id, &account.name, cryps), Uuid::nil(), &account)? } }; let instance = Instance::global(player); Ok(instance) } #[cfg(test)] mod tests { use mob::instance_mobs; use super::*; #[test] fn player_bot_vbox_test() { let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); let mut player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true); player.vbox.fill(); player.autobuy(); assert!(player.cryps.iter().all(|c| c.skills.len() >= 1)); } }