use std::collections::{HashMap}; use uuid::Uuid; use failure::Error; use failure::err_msg; use construct::{Construct, Colours}; use vbox::{Vbox, ItemType, VboxIndices}; use item::{Item, ItemEffect, get_combos}; 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 new(account: Uuid, img: Option, name: &String, mut constructs: Vec) -> Player { constructs.sort_unstable_by_key(|c| c.id); Player { id: account, img, 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)?; construct.skills = vec![]; construct.learn_mut(skill); // // done here because i teach them a tonne of skills for tests // let max_skills = 1; // 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")), } let construct = self.construct_get(construct_id)?; // force 1 skill for auto let mut colour_counts = vec![ (Item::Red, construct.colours.red), (Item::Green, construct.colours.green), (Item::Blue, construct.colours.blue), ]; colour_counts.sort_unstable_by_key(|cc| cc.1); colour_counts.reverse(); println!("{:?}", colour_counts); let total = (construct.colours.red + construct.colours.green + construct.colours.blue) as f64; let colour_pcts = colour_counts.iter_mut() .map(|cc| (cc.0, cc.1 as f64 / total)) .collect::>(); println!("{:?}", colour_pcts); let skill_item = match item.into_skill().is_some() { true => item, false => construct.skills[0].skill.base(), }; let colour_skill = { // no colours if colour_pcts[0].1.is_infinite() { construct.skills[0].skill } else { let mut skill_item_combo = { if colour_pcts[0].1 > 0.75 { vec![skill_item, colour_pcts[0].0, colour_pcts[0].0] } else if colour_pcts[1].1 > 0.4 { vec![skill_item, colour_pcts[0].0, colour_pcts[1].0] } else { // special triple skill_item vec![skill_item, colour_pcts[0].0, colour_pcts[1].0] } }; skill_item_combo.sort_unstable(); println!("{:?}", skill_item_combo); let combos = get_combos(); let combo = combos.iter().find(|c| c.components == skill_item_combo) .ok_or(err_msg("no combo for colour skill"))?; combo.item.into_skill() .ok_or(format_err!("item {:?} has no associated skill", combo.item))? } }; // unlearn everything construct.skills = vec![]; construct.learn_mut(colour_skill); // // 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 // } // }); let player_colours = Colours { red: 0, blue: 0, green: 0 }; 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, construct: Option) -> Result<&mut Player, Error> { if self.vbox.stash.len() >= 9 && !construct.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 construct { 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) } } #[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, None, &"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, None, &"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);*/ } }