use std::iter; use std::collections::HashMap; // refunds use rand::prelude::*; use rand::{thread_rng}; use rand::distributions::{WeightedIndex}; use failure::Error; use failure::err_msg; use item::*; pub type VboxIndices = Option>>; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { pub bits: usize, pub store: HashMap>, pub stash: HashMap, } #[derive(Debug,Copy,Clone,Serialize,Deserialize,Hash,PartialEq,Eq)] pub enum ItemType { Colours, Skills, Specs, } const STORE_COLOURS_CAPACITY: usize = 6; const STORE_SKILLS_CAPACITY: usize = 3; const STORE_SPECS_CAPACITY: usize = 3; const STASH_CAPACITY: usize = 6; const STARTING_ATTACK_COUNT: usize = 3; impl Vbox { pub fn new() -> Vbox { let colours: HashMap = HashMap::new(); let skills: HashMap = HashMap::new(); let specs: HashMap = HashMap::new(); let store = [ (ItemType::Colours, colours), (ItemType::Skills, skills), (ItemType::Colours, specs), ].iter().cloned().collect(); let mut stash = HashMap::new(); for i in 0..STARTING_ATTACK_COUNT { stash.insert(i.to_string(), Item::Attack); } Vbox { store, stash, bits: 30, } } pub fn balance_sub(&mut self, amount: usize) -> Result<&mut Vbox, Error> { let new_balance = self.bits .checked_sub(amount) .ok_or(format_err!("insufficient balance: {:?}", self.bits))?; self.bits = new_balance; Ok(self) } pub fn balance_add(&mut self, amount: usize) -> &mut Vbox { self.bits = self.bits.saturating_add(amount); self } pub fn fill(&mut self) -> &mut Vbox { let mut rng = thread_rng(); let colours = vec![ (Item::Red, 1), (Item::Green, 1), (Item::Blue, 1), ]; let colour_dist = WeightedIndex::new(colours.iter().map(|item| item.1)).unwrap(); let skills = vec![ (Item::Attack, 1), (Item::Block, 1), (Item::Buff, 1), (Item::Debuff, 1), (Item::Stun, 1), ]; let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap(); let specs = vec![ (Item::Power, 1), (Item::Life, 1), (Item::Speed, 1), ]; let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap(); for item_type in [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() { let (items, num, dist) = match item_type { ItemType::Colours => (&colours, STORE_COLOURS_CAPACITY, &colour_dist), ItemType::Skills => (&skills, STORE_SKILLS_CAPACITY, &skill_dist), ItemType::Specs => (&specs, STORE_SPECS_CAPACITY, &spec_dist), }; let drops = iter::repeat_with(|| items[dist.sample(&mut rng)].0) .take(num) .enumerate() .map(|(i, item)| (i.to_string(), item)) .collect::>(); self.store.insert(*item_type, drops); } self } pub fn buy(&mut self, item: ItemType, i: &String) -> Result { // check item exists let selection = self.store .get_mut(&item).ok_or(format_err!("no item group {:?}", item))? .remove(i).ok_or(format_err!("no item at index {:?} {:}", self, i))?; self.balance_sub(selection.cost())?; Ok(selection) } pub fn stash_add(&mut self, item: Item, index: Option<&String>) -> Result { if self.stash.len() >= STASH_CAPACITY { return Err(err_msg("stash full")); } if let Some(index) = index { if self.stash.contains_key(index) { return Err(format_err!("slot occupied {:?}", index)); } self.stash.insert(index.clone(), item); return Ok(index.to_string()); } for i in (0..STASH_CAPACITY).map(|i| i.to_string()) { if !self.stash.contains_key(&i) { self.stash.insert(i.clone(), item); return Ok(i); } } return Err(err_msg("stash full")); } pub fn bot_buy(&mut self, item: ItemType) -> Result { let buy_index = self.store[&item] .keys() .next() .ok_or(format_err!("no item in group {:?}", item))? .clone(); self.buy(item, &buy_index) } pub fn refund(&mut self, i: String) -> Result<&mut Vbox, Error> { let refunded = self.stash.remove(&i) .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))?; let refund = refunded.cost(); // info!("refunding {:?} for {:?}", refund, refunded); self.balance_add(refund); Ok(self) } pub fn combine(&mut self, stash_indices: Vec, store_indices: Option>>) -> Result<&mut Vbox, Error> { // find base item for index to insert into let base_index = stash_indices.iter() .find(|i| match self.stash.get(i.clone()) { Some(item) => item.into_skill().is_some(), None => false, }); let mut input = stash_indices .iter() .map(|i| self.stash.remove(i) .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))) .collect::, Error>>()?; if let Some(store_indices) = store_indices { let mut purchased = store_indices.iter() .map(|(g, list)| list.iter() .map(|i| self.buy(*g, i)) .collect::, Error>>() ) .collect::>, Error>>()? .into_iter() .flatten() .collect(); input.append(&mut purchased); } // sort the input to align with the combinations // combos are sorted when created input.sort_unstable(); let combos = get_combos(); let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?; self.stash_add(combo.item, base_index)?; Ok(self) } } #[cfg(test)] mod tests { use super::*; use construct::{Colours}; #[test] fn combine_test() { let mut vbox = Vbox::new(); vbox.stash.insert(0.to_string(), Item::Attack); vbox.stash.insert(1.to_string(), Item::Green); vbox.stash.insert(2.to_string(), Item::Green); vbox.combine(vec![0.to_string(), 1.to_string(), 2.to_string()], None).unwrap(); assert_eq!(vbox.stash["0"], Item::Heal); } #[test] fn buy_test() { let mut vbox = Vbox::new(); vbox.fill(); // cannot rebuy same vbox.buy(ItemType::Skills, &0.to_string()).unwrap(); assert!(vbox.store[&ItemType::Skills].get(&0.to_string()).is_none()); assert!(vbox.buy(ItemType::Skills, &0.to_string()).is_err()); } #[test] fn capacity_test() { let mut vbox = Vbox::new(); vbox.fill(); vbox.stash_add(Item::Red, None).unwrap(); vbox.stash_add(Item::Red, None).unwrap(); vbox.stash_add(Item::Red, None).unwrap(); assert!(vbox.stash_add(Item::Red, None).is_err()); } #[test] fn store_and_stash_combine_test() { let mut vbox = Vbox::new(); vbox.fill(); let mut skill_combine_args = HashMap::new(); skill_combine_args.insert(ItemType::Colours, vec![0.to_string(), 1.to_string()]); skill_combine_args.insert(ItemType::Skills, vec![0.to_string()]); let mut spec_combine_args = HashMap::new(); spec_combine_args.insert(ItemType::Colours, vec![2.to_string(), 3.to_string()]); spec_combine_args.insert(ItemType::Specs, vec![0.to_string()]); vbox.combine(vec![], Some(skill_combine_args)).unwrap(); vbox.combine(vec![], Some(spec_combine_args)).unwrap(); } #[test] fn combos_test() { let mut input = vec![Item::Green, Item::Attack, Item::Green]; let combos = get_combos(); // sort input so they align input.sort_unstable(); let combo = combos.iter().find(|c| c.components == input); assert!(combo.is_some()); } #[test] fn refund_test() { let mut vbox = Vbox::new(); vbox.stash.insert(0.to_string(), Item::Strike); vbox.refund(0.to_string()).unwrap(); assert_eq!(vbox.bits, 32); } #[test] fn colours_count_test() { let strike = Item::Strike; let mut count = Colours::new(); strike.colours(&mut count); assert_eq!(count.red, 2); } // #[test] // fn item_info_test() { // info!("{:#?}", item_info()); // } }