use std::collections::{HashMap}; 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, ItemType, VboxIndices}; 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(); // skill buying phase while self.constructs.iter().any(|c| c.skills.len() < 3) { // find the construct with the smallest number of skills let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) { None => panic!("no constructs in autobuy"), Some(c) => c.id, }; let i = self.vbox.stash.iter() .find(|(_i, v)| v.into_skill().is_some()) .map(|(i, _v)| i.clone()); // got a skill in stash if let Some(i) = i { // AAAAAAAAAAAAAAAAAAAA // there's a bad bug here where if this apply fails // the item in question will be silently dropped let item = self.vbox.stash.remove(&i).unwrap(); self.vbox_apply(item, construct_id).ok(); continue; } // need to buy one else { // do we have any colours in store? let colours = self.vbox.store[&ItemType::Colours].keys() .cloned() .take(2) .collect::>(); // how about a base skill? let base = match self.vbox.store[&ItemType::Skills].iter().next() { Some(b) => Some(b.0.clone()), None => None, }; // if no: try to refill and start again match colours.len() != 2 || base.is_none() { true => { match self.vbox_refill() { Ok(_) => continue, Err(_) => break, // give up }; } false => { let mut vbox_items = HashMap::new(); vbox_items.insert(ItemType::Colours, colours); vbox_items.insert(ItemType::Skills, vec![base.unwrap()]); match self.vbox_combine(vec![], Some(vbox_items)) { Ok(_) => continue, Err(_) => break, // give up } } } } } // spec buying phase while self.constructs.iter().any(|c| c.specs.len() < 3) { // find the construct with the smallest number of skills let construct_id = match self.constructs.iter().min_by_key(|c| c.specs.len()) { None => panic!("no constructs in autobuy"), Some(c) => c.id, }; let i = self.vbox.stash.iter() .find(|(_i, v)| v.into_spec().is_some()) .map(|(i, _v)| i.clone()); // got a skill in stash if let Some(i) = i { // AAAAAAAAAAAAAAAAAAAA // there's a bad bug here where if this apply fails // the item in question will be silently dropped let item = self.vbox.stash.remove(&i).unwrap(); self.vbox_apply(item, construct_id).unwrap(); continue; } // need to buy one else { // do we have any colours in store? let colours = self.vbox.store[&ItemType::Colours].keys() .cloned() .take(2) .collect::>(); // how about a base spec? let base = match self.vbox.store[&ItemType::Specs].iter().next() { Some(b) => Some(b.0.clone()), None => None, }; // if no: try to refill and start again match colours.len() != 2 || base.is_none() { true => match self.vbox_refill() { Ok(_) => continue, Err(_) => break, // give up }, false => { let mut vbox_items = HashMap::new(); vbox_items.insert(ItemType::Colours, colours); vbox_items.insert(ItemType::Specs, vec![base.unwrap()]); match self.vbox_combine(vec![], Some(vbox_items)) { Ok(_) => continue, Err(_) => break, // give up } } } } } // upgrading phase // NYI return self; } pub fn vbox_refill(&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: ItemType) -> Result<&mut Player, Error> { let item = self.vbox.bot_buy(group)?; self.vbox.stash_add(item, None)?; Ok(self) } pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option) -> Result<&mut Player, Error> { let item = self.vbox.buy(group, &index)?; match construct_id { Some(id) => { self.vbox_apply(item, id)?; }, None => { self.vbox.stash_add(item, None)?; }, }; Ok(self) } pub fn vbox_combine(&mut self, inv_indices: Vec, vbox_indices: VboxIndices) -> Result<&mut Player, Error> { self.vbox.combine(inv_indices, vbox_indices)?; Ok(self) } pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> { self.vbox.refund(index)?; Ok(self) } pub fn vbox_equip(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> { let item = self.vbox.stash.remove(&index) .ok_or(format_err!("no item at index {:?} {:}", self, &index))?; self.vbox_apply(item, construct_id) } pub fn vbox_apply(&mut self, item: Item, construct_id: Uuid) -> Result<&mut Player, Error> { 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.stash.len() >= 9 && !target_construct_id.is_some() { return Err(err_msg("too many items stash")); } 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); } match target_construct_id { Some(cid) => { self.vbox_apply(target, cid)?; }, None => { self.vbox.stash_add(target, None)?; }, }; 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.vbox.bits = 100; player.autobuy(); assert!(player.constructs.iter().all(|c| c.skills.len() > 1)); assert!(player.constructs.iter().all(|c| c.specs.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);*/ } }