mnml/server/src/vbox.rs

239 lines
6.9 KiB
Rust

use std::iter;
// reclaims
use rand::prelude::*;
use rand::{thread_rng};
use rand::distributions::{WeightedIndex};
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
use account::Account;
use rpc::{VboxAcceptParams, VboxDiscardParams, VboxCombineParams, VboxApplyParams, VboxReclaimParams, VboxUnequipParams};
use instance::{Instance, instance_get, instance_update};
use construct::{Colours};
use item::*;
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Vbox {
pub bits: u16,
pub free: Vec<Vec<Item>>,
pub bound: Vec<Item>,
}
impl Vbox {
pub fn new() -> Vbox {
let starting_items = vec![
Item::Attack,
Item::Attack,
Item::Attack,
];
Vbox {
free: vec![vec![], vec![], vec![]],
bound: starting_items,
bits: 18,
}
}
pub fn balance_sub(&mut self, amount: u16) -> 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: u16) -> &mut Vbox {
self.bits = self.bits.saturating_add(amount);
self
}
pub fn fill(&mut self) -> &mut Vbox {
let colours = vec![
(Item::Red, 1),
(Item::Green, 1),
(Item::Blue, 1),
];
let skills = vec![
(Item::Attack, 1),
(Item::Block, 1),
(Item::Buff, 1),
(Item::Debuff, 1),
(Item::Stun, 1),
];
let specs = vec![
(Item::Power, 1),
(Item::Life, 1),
(Item::Speed, 1),
];
let mut rng = thread_rng();
self.free = [&colours, &skills, &specs].iter()
.map(|items| {
let dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap();
iter::repeat_with(|| {
items[dist.sample(&mut rng)].0
})
.take(6)
.collect::<Vec<Item>>()
})
.collect::<Vec<Vec<Item>>>();
self
}
pub fn accept(&mut self, i: usize, j: usize) -> Result<&mut Vbox, Error> {
if self.bound.len() >= 9 {
return Err(err_msg("too many items bound"));
}
// check item exists
self.free
.get(i).ok_or(format_err!("no item group at index {:?}", i))?
.get(j).ok_or(format_err!("no item at index {:?}", j))?;
// check can purchase
let cost = self.free[i][j].cost();
self.balance_sub(cost)?;
// actually move
self.bound.push(self.free[i].remove(j));
// self.bound.sort_unstable();
Ok(self)
}
pub fn reclaim(&mut self, i: usize) -> Result<&mut Vbox, Error> {
self.bound.get(i).ok_or(format_err!("no item at index {:?}", i))?;
let reclaimed = self.bound.remove(i);
let refund = reclaimed.cost();
// info!("reclaiming {:?} for {:?}", refund, reclaimed);
self.balance_add(refund);
Ok(self)
}
pub fn combine(&mut self, mut indices: Vec<usize>) -> Result<&mut Vbox, Error> {
if indices.len() != 3 {
return Err(err_msg("exactly 3 indices required"));
}
if !indices.iter().all(|i| self.bound.get(*i).is_some()) {
return Err(err_msg("item missing index"));
}
// have to sort the indices and keep track of the iteration
// because when removing the elements the array shifts
indices.sort_unstable();
let mut input = indices
.iter()
.enumerate()
.map(|(i, index)| {
self.bound.remove(index.saturating_sub(i))
})
.collect::<Vec<Item>>();
// 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.bound.push(combo.item);
self.bound.sort_unstable();
Ok(self)
}
}
pub fn vbox_discard(params: VboxDiscardParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_discard(account.id)?;
return instance_update(tx, instance);
}
pub fn vbox_accept(params: VboxAcceptParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_accept(account.id, params.group, params.index)?;
return instance_update(tx, instance);
}
pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_combine(account.id, params.indices)?;
return instance_update(tx, instance);
}
pub fn vbox_reclaim(params: VboxReclaimParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_reclaim(account.id, params.index)?;
return instance_update(tx, instance);
}
pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_apply(account.id, params.index, params.construct_id)?;
return instance_update(tx, instance);
}
pub fn vbox_unequip(params: VboxUnequipParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let instance = instance_get(tx, params.instance_id)?
.vbox_unequip(account.id, params.target, params.construct_id)?;
return instance_update(tx, instance);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combine_test() {
let mut vbox = Vbox::new();
vbox.bound = vec![Item::Attack, Item::Green, Item::Green];
vbox.combine(vec![1,2,0]).unwrap();
assert_eq!(vbox.bound[0], Item::HealI);
}
#[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 reclaim_test() {
let mut vbox = Vbox::new();
vbox.bound = vec![Item::StrikeI];
vbox.reclaim(0).unwrap();
assert_eq!(vbox.bits, 22);
}
#[test]
fn colours_count_test() {
let strike = Item::StrikeI;
let mut count = Colours::new();
strike.colours(&mut count);
assert_eq!(count.red, 2);
}
// #[test]
// fn item_info_test() {
// info!("{:#?}", item_info());
// }
}