use uuid::Uuid; use rand::prelude::*; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account; use account::Account; use construct::{Construct, Colours}; use vbox::{Vbox}; use item::{Item, ItemEffect}; use effect::{Effect}; const DISCARD_COST: usize = 2; #[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq)] pub enum Score { Zero, One, Two, Three, Adv, Win, Lose, } impl Score { pub fn add_win(self, _opp: &Score) -> Score { match self { Score::Zero => Score::One, Score::One => Score::Two, Score::Two => Score::Win, // Tennis scoring // Score::Three => match opp { // Score::Adv => Score::Three, // Score::Three => Score::Adv, // _ => Score::Win, // } // Score::Adv => Score::Win, _ => panic!("faulty score increment {:?}", self), } } pub fn add_loss(self) -> Score { match self { // Score::Adv => Score::Three, _ => self, } } } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Player { pub id: Uuid, pub img: Option, pub name: String, pub vbox: Vbox, pub constructs: Vec, pub bot: bool, pub ready: bool, pub draw_offered: bool, pub score: Score, } impl Player { pub fn from_account(tx: &mut Transaction, account: &Account) -> Result { let constructs = account::team(tx, account)?; let img = match account.subscribed { true => Some(account.img), false => None, }; Ok(Player { id: account.id, img, name: account.name.clone(), vbox: Vbox::new(), constructs, bot: false, ready: false, draw_offered: false, score: Score::Zero, }) } pub fn new(account: Uuid, name: &String, constructs: Vec) -> Player { Player { id: account, img: Some(account), name: name.clone(), vbox: Vbox::new(), constructs, bot: false, ready: false, draw_offered: false, score: Score::Zero, } } pub fn redact(mut self, account: Uuid) -> Player { // all g if account == self.id { return self; } // remove vbox self.vbox = Vbox::new(); // hide skills for construct in self.constructs.iter_mut() { construct.skills = vec![]; construct.specs = vec![]; } self } 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 forfeit(&mut self) -> &mut Player { for construct in self.constructs.iter_mut() { construct.force_ko(); } self } pub fn set_win(&mut self) -> &mut Player { self.score = Score::Win; self } pub fn set_lose(&mut self) -> &mut Player { self.score = Score::Lose; 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, construct_id: Option) -> Result<&mut Player, Error> { self.vbox.accept(group, index, construct_id)?; if construct_id.is_some() { let equip_index = self.vbox.bound.len() - 1; self.vbox_apply(equip_index, construct_id.expect("no construct"))?; } 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, target_construct_id: Option) -> Result<&mut Player, Error> { if self.vbox.bound.len() >= 9 && !target_construct_id.is_some() { 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); if target_construct_id.is_some() { let equip_index = self.vbox.bound.len() - 1; self.vbox_apply(equip_index, target_construct_id.expect("no construct"))?; } // 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)); } #[test] fn player_score_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.score = player.score.add_win(&Score::Zero); player.score = player.score.add_win(&Score::Zero); player.score = player.score.add_win(&Score::Zero); assert_eq!(player.score, Score::Win); // 40 / 0 // Bo7 tennis scoring /*assert_eq!(player.score, Score::Three); // 40 / 0 player.score = player.score.add_loss(); // adv -> deuce assert_eq!(player.score, Score::Three); player.score = player.score.add_loss(); // adv -> deuce assert_eq!(player.score, Score::Three); player.score = player.score.add_win(&Score::Adv); // opp adv -> stays deuce assert_eq!(player.score, Score::Three); player.score = player.score.add_win(&Score::Three); assert_eq!(player.score, Score::Adv); player.score = player.score.add_win(&Score::Three); assert_eq!(player.score, Score::Win);*/ } }