301 lines
9.0 KiB
Rust
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());
|
|
// }
|
|
} |