use uuid::Uuid; use rand::prelude::*; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account::Account; use construct::{Construct, Colours}; use vbox::{Vbox}; use item::{Item, ItemEffect}; use effect::{Effect}; const DISCARD_COST: u16 = 2; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Player { pub id: Uuid, pub name: String, pub vbox: Vbox, pub constructs: Vec, pub bot: bool, pub ready: bool, pub warnings: u8, pub wins: u8, pub losses: u8, } impl Player { pub fn new(account: Uuid, name: &String, constructs: Vec) -> Player { Player { id: account, name: name.clone(), vbox: Vbox::new(), wins: 0, losses: 0, constructs, 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 construct in self.constructs.iter_mut() { construct.force_ko(); } self } pub fn add_win(&mut self) -> &mut Player { self.wins += 1; let win_bonus = 12 + 6 * (self.wins + self.losses); self.vbox.balance_add(win_bonus.into()); self } pub fn add_loss(&mut self) -> &mut Player { self.losses += 1; let loss_bonus = 12 + 6 * (self.wins + self.losses); self.vbox.balance_add(loss_bonus.into()); self } pub fn construct_get(&mut self, id: Uuid) -> Result<&mut Construct, Error> { self.constructs.iter_mut().find(|c| c.id == id).ok_or(err_msg("construct not found")) } pub fn autobuy(&mut self) -> &mut Player { let mut rng = thread_rng(); // first check if any constructs have no skills // if there is one find an item in vbox that gives a skill while let Some(c) = self.constructs.iter().position(|c| c.skills.len() == 0) { if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) { let construct_id = self.constructs[c].id; self.vbox_apply(s, construct_id).expect("could not apply"); continue; } info!("no skills available..."); } // now keep buying and applying items cause whynot // inb4 montecarlo gan loop { let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) { true => { let mut target_construct_i = 0; for (j, c) in self.constructs.iter().enumerate() { if c.skills.len() < self.constructs[target_construct_i].skills.len() { target_construct_i = j; } } (target_construct_i, self.constructs[target_construct_i].id) }, false => { let i = rng.gen_range(0, 3); (i, self.constructs[i].id) }, }; let needs_skills = self.constructs[target_construct_i].skills.len() < 3; let group_i = match needs_skills { true => 1, false => 2, }; let num_colours = self.vbox.bound .iter() .filter(|v| [Item::Red, Item::Green, Item::Blue].contains(v)) .count(); if self.vbox.bound.len() < 3 || num_colours < 2 { if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 { // info!("insufficient balance"); break; } // get 2 colours and something else let free_colours = self.vbox.free[0].iter().fold(0, |count, item| { match item.is_some() { true => count + 1, false => count } }); if free_colours < 2 { break; } self.bot_vbox_accept(0).expect("could't accept colour item"); self.bot_vbox_accept(0).expect("could't accept colour item"); self.bot_vbox_accept(group_i).expect("could't accept group item"); } // info!("{:?}", self.vbox.bound); let skills = [Item::Attack, Item::Block, Item::Buff, Item::Debuff, Item::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 item_i = self.vbox.bound.len() - 1; self.vbox_apply(item_i, target_construct_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 bot_vbox_accept(&mut self, group: usize) -> Result<&mut Player, Error> { self.vbox.bot_accept(group)?; 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, construct_id: Uuid) -> Result<&mut Player, Error> { if self.vbox.bound.get(index).is_none() { return Err(format_err!("no item at index {:?}", index)); } let item = self.vbox.bound.remove(index); match item.effect() { Some(ItemEffect::Skill) => { let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?; let construct = self.construct_get(construct_id)?; // done here because i teach them a tonne of skills for tests let max_skills = 3; if construct.skills.len() >= max_skills { return Err(format_err!("construct at max skills ({:?})", max_skills)); } if construct.knows(skill) { return Err(format_err!("construct already knows skill ({:?})" , skill)); } construct.learn_mut(skill); }, Some(ItemEffect::Spec) => { let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?; let construct = self.construct_get(construct_id)?; construct.spec_add(spec)?; }, None => return Err(err_msg("item has no effect on constructs")), } // now the item has been applied // recalculate the stats of the whole player let player_colours = self.constructs.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 construct in self.constructs.iter_mut() { construct.apply_modifiers(&player_colours); } Ok(self) } pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid) -> Result<&mut Player, Error> { if self.vbox.bound.len() >= 9 { return Err(err_msg("too many items bound")); } match target.effect() { Some(ItemEffect::Skill) => { let skill = target.into_skill().ok_or(format_err!("item {:?} has no associated skill", target))?; let construct = self.construct_get(construct_id)?; construct.forget(skill)?; }, Some(ItemEffect::Spec) => { let spec = target.into_spec().ok_or(format_err!("item {:?} has no associated spec", target))?; let construct = self.construct_get(construct_id)?; construct.spec_remove(spec)?; }, None => return Err(err_msg("item has no effect on constructs")), } // now the item has been applied // recalculate the stats of the whole player let player_colours = self.constructs.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 construct in self.constructs.iter_mut() { construct.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.constructs.iter() .filter(|c| !c.is_ko()) .filter(|c| c.available_skills().len() > 0) .collect::>().len(); // info!("{:} requires {:} skills this turn", self.id, required); return required; } pub fn intercepting(&self) -> Option<&Construct> { self.constructs.iter() .find(|c| c.affected(Effect::Intercept)) } pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> { self.constructs.iter_mut().find(|c| c.id == id) } } pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result { let query = " INSERT INTO players (id, instance, account) VALUES ($1, $2, $3) RETURNING id, account; "; let result = tx .query(query, &[&Uuid::new_v4(), &instance, &account.id])?; let _returned = result.iter().next().expect("no row written"); info!("wrote player {:} joined instance: {:}", account.name, instance); return Ok(player); } #[cfg(test)] mod tests { use mob::instance_mobs; use super::*; #[test] fn player_bot_vbox_test() { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); player.vbox.fill(); player.autobuy(); assert!(player.constructs.iter().all(|c| c.skills.len() >= 1)); } }