mnml/core/src/vbox.rs
2020-01-03 15:46:38 +10:00

301 lines
9.0 KiB
Rust

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<HashMap<ItemType, Vec<String>>>;
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Vbox {
pub bits: usize,
pub store: HashMap<ItemType, HashMap<String, Item>>,
pub stash: HashMap<String, Item>,
}
#[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<String, Item> = HashMap::new();
let skills: HashMap<String, Item> = HashMap::new();
let specs: HashMap<String, Item> = 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::<HashMap<String, Item>>();
self.store.insert(*item_type, drops);
}
self
}
pub fn buy(&mut self, item: ItemType, i: &String) -> Result<Item, Error> {
// 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<String, Error> {
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<Item, Error> {
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<String>, store_indices: Option<HashMap<ItemType, Vec<String>>>) -> 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::<Result<Vec<Item>, 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::<Result<Vec<Item>, Error>>()
)
.collect::<Result<Vec<Vec<Item>>, 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());
// }
}