This commit is contained in:
ntr 2019-11-28 15:43:48 +10:00
parent d276edab14
commit 33ad4db8f5
4 changed files with 419 additions and 232 deletions

View File

@ -15,7 +15,7 @@ use chrono::prelude::*;
use chrono::Duration; use chrono::Duration;
use account::Account; use account::Account;
use account; use vbox;
use player::{Player, Score, player_create}; use player::{Player, Score, player_create};
@ -466,35 +466,35 @@ impl Instance {
Ok(()) Ok(())
} }
pub fn vbox_discard(mut self, account: Uuid) -> Result<Instance, Error> { pub fn vbox_refill(mut self, account: Uuid) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_discard()?; .vbox_refill()?;
Ok(self) Ok(self)
} }
pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize, construct_id: Option<Uuid>) -> Result<Instance, Error> { pub fn vbox_buy(mut self, account: Uuid, group: vbox::ItemType, index: String, construct_id: Option<Uuid>) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_accept(group, index, construct_id)?; .vbox_buy(group, index, construct_id)?;
Ok(self) Ok(self)
} }
pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<Instance, Error> { pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec<String>, vbox_indices: vbox::VboxIndices) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_combine(inv_indices, vbox_indices)?; .vbox_combine(inv_indices, vbox_indices)?;
Ok(self) Ok(self)
} }
pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result<Instance, Error> { pub fn vbox_refund(mut self, account: Uuid, index: String) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_reclaim(index)?; .vbox_refund(index)?;
Ok(self) Ok(self)
} }
pub fn vbox_apply(mut self, account: Uuid, index: usize, construct_id: Uuid) -> Result<Instance, Error> { pub fn vbox_apply(mut self, account: Uuid, index: String, construct_id: Uuid) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_apply(index, construct_id)?; .vbox_apply(index, construct_id)?;
@ -834,19 +834,17 @@ mod tests {
fn instance_pve_test() { fn instance_pve_test() {
let mut instance = Instance::new(); let mut instance = Instance::new();
let bot_player = bot_player(); let bot = bot_player();
let bot = bot_player.id; let bot_one = bot.id;
instance.add_player(bot_player).unwrap(); instance.add_player(bot).unwrap();
let player_account = Uuid::new_v4(); let bot = bot_player();
let constructs = instance_mobs(player_account); let bot_two = bot.id;
let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); instance.add_player(bot).unwrap();
instance.add_player(player).expect("could not add player");
assert_eq!(instance.phase, InstancePhase::Lobby); assert_eq!(instance.phase, InstancePhase::Lobby);
instance.player_ready(player_account).unwrap(); instance.player_ready(bot_one).unwrap();
instance.player_ready(bot).unwrap(); instance.player_ready(bot_two).unwrap();
assert_eq!(instance.phase, InstancePhase::Finished); assert_eq!(instance.phase, InstancePhase::Finished);
} }

View File

@ -1,3 +1,5 @@
use std::collections::{HashMap};
use uuid::Uuid; use uuid::Uuid;
use rand::prelude::*; use rand::prelude::*;
@ -9,7 +11,7 @@ use failure::err_msg;
use account; use account;
use account::Account; use account::Account;
use construct::{Construct, Colours}; use construct::{Construct, Colours};
use vbox::{Vbox}; use vbox::{Vbox, ItemType, VboxIndices};
use item::{Item, ItemEffect}; use item::{Item, ItemEffect};
use effect::{Effect}; use effect::{Effect};
@ -155,124 +157,228 @@ impl Player {
pub fn autobuy(&mut self) -> &mut Player { pub fn autobuy(&mut self) -> &mut Player {
let mut rng = thread_rng(); let mut rng = thread_rng();
// first check if any constructs have no skills // skill buying phase
// if there is one find an item in vbox that gives a skill while self.constructs.iter().any(|c| c.skills.len() < 3) {
while let Some(c) = self.constructs.iter().position(|c| c.skills.len() == 0) { // find the construct with the smallest number of skills
if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) { let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) {
let construct_id = self.constructs[c].id; None => panic!("no constructs in autobuy"),
self.vbox_apply(s, construct_id).expect("could not apply"); 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
self.vbox_apply(i, construct_id).ok();
continue; continue;
} }
info!("no skills available..."); // need to buy one
} else {
// now keep buying and applying items cause whynot // do we have any colours in store?
// inb4 montecarlo gan let colours = self.vbox.store[&ItemType::Colours].keys()
.cloned()
.collect::<Vec<String>>();
loop { // how about a base skill?
let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) { let base = match self.vbox.store[&ItemType::Skills].iter().next() {
true => { Some(b) => Some(b.0.clone()),
let mut target_construct_i = 0; None => None,
for (j, c) in self.constructs.iter().enumerate() { };
if c.skills.len() < self.constructs[target_construct_i].skills.len() {
target_construct_i = j; // if no: try to refill and start again
} match colours.len() < 2 || base.is_none() {
} true => match self.vbox_refill() {
(target_construct_i, self.constructs[target_construct_i].id) Ok(_) => continue,
Err(_) => break, // give up
}, },
false => { false => {
let i = rng.gen_range(0, 3); let mut vbox_items = HashMap::new();
(i, self.constructs[i].id) 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
self.vbox_apply(i, 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()
.collect::<Vec<String>>();
// 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()]);
let needs_skills = self.constructs[target_construct_i].skills.len() < 3; match self.vbox_combine(vec![], Some(vbox_items)) {
let group_i = match needs_skills { Ok(_) => continue,
true => 1, Err(_) => break, // give up
false => 2, }
}; }
}
}
let num_colours = self.vbox.bound
.iter()
.filter(|v| [Item::Red, Item::Green, Item::Blue].contains(v))
.count();
if self.vbox.bound.len() < 3 || num_colours < 2 {
if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 {
// info!("insufficient balance");
break;
} }
// get 2 colours and something else // upgrading phase
let free_colours = self.vbox.free[0].iter().fold(0, |count, item| { // NYI
match item.is_some() {
true => count + 1,
false => count
}
});
if free_colours < 2 {
break;
}
self.bot_vbox_accept(0).expect("could't accept colour item");
self.bot_vbox_accept(0).expect("could't accept colour item");
self.bot_vbox_accept(group_i).expect("could't accept group item");
}
// info!("{:?}", self.vbox.bound); // // first check if any constructs have no skills
// // if there is one find an item in vbox that gives a skill
// while let Some(c) = self.constructs.iter().position(|c| c.skills.len() == 0) {
// if let Some(s) = self.vbox.stash.iter().position(|(i, v)| v.into_skill().is_some()) {
// let construct_id = self.constructs[c].id;
// self.vbox_apply(s, construct_id).expect("could not apply");
// continue;
// }
// info!("no skills available...");
// }
let skills = [Item::Attack, Item::Block, Item::Buff, Item::Debuff, Item::Stun]; // // now keep buying and applying items cause whynot
let combo_i = match group_i { // // inb4 montecarlo gan
1 => self.vbox.bound.iter().position(|v| skills.contains(v)).expect("no skill found"), // loop {
2 => self.vbox.bound.iter().position(|v| v.into_spec().is_some()).expect("no spec found"), // let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) {
_ => panic!("unknown group_i"), // true => {
}; // let mut target_construct_i = 0;
// for (j, c) in self.constructs.iter().enumerate() {
// if c.skills.len() < self.constructs[target_construct_i].skills.len() {
// target_construct_i = j;
// }
// }
// (target_construct_i, self.constructs[target_construct_i].id)
// },
// false => {
// let i = rng.gen_range(0, 3);
// (i, self.constructs[i].id)
// },
// };
// first 2 colours can be whatever // let needs_skills = self.constructs[target_construct_i].skills.len() < 3;
self.vbox_combine(vec![0, 1, combo_i], vec![]).ok(); // let group_i = match needs_skills {
let item_i = self.vbox.bound.len() - 1; // true => 1,
self.vbox_apply(item_i, target_construct_id).ok(); // false => 2,
} // };
// let num_colours = self.vbox.stash
// .iter()
// .filter(|(i, v)| [Item::Red, Item::Green, Item::Blue].contains(v))
// .count();
// if self.vbox.stash.len() < 3 || num_colours < 2 {
// if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 {
// // info!("insufficient balance");
// break;
// }
// // get 2 colours and something else
// let store_colours = self.vbox.store[&ItemType::Colours].len();
// if store_colours < 2 {
// break;
// }
// self.bot_vbox_accept(0).expect("could't accept colour item");
// self.bot_vbox_accept(0).expect("could't accept colour item");
// self.bot_vbox_accept(group_i).expect("could't accept group item");
// }
// // info!("{:?}", self.vbox.stash);
// let skills = [Item::Attack, Item::Block, Item::Buff, Item::Debuff, Item::Stun];
// let combo_i = match group_i {
// 1 => self.vbox.stash.iter().position(|v| skills.contains(v)).expect("no skill found"),
// 2 => self.vbox.stash.iter().position(|v| v.into_spec().is_some()).expect("no spec found"),
// _ => panic!("unknown group_i"),
// };
// // first 2 colours can be whatever
// self.vbox_combine(vec![0, 1, combo_i], vec![]).ok();
// let item_i = self.vbox.stash.len() - 1;
// self.vbox_apply(item_i, target_construct_id).ok();
// }
return self; return self;
} }
pub fn vbox_discard(&mut self) -> Result<&mut Player, Error> { pub fn vbox_refill(&mut self) -> Result<&mut Player, Error> {
self.vbox.balance_sub(DISCARD_COST)?; self.vbox.balance_sub(DISCARD_COST)?;
self.vbox.fill(); self.vbox.fill();
Ok(self) Ok(self)
} }
pub fn bot_vbox_accept(&mut self, group: usize) -> Result<&mut Player, Error> { pub fn bot_vbox_accept(&mut self, group: ItemType) -> Result<&mut Player, Error> {
self.vbox.bot_accept(group)?; let item = self.vbox.bot_buy(group)?;
self.vbox.stash_add(item, None)?;
Ok(self) Ok(self)
} }
pub fn vbox_accept(&mut self, group: usize, index: usize, construct_id: Option<Uuid>) -> Result<&mut Player, Error> { pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
self.vbox.accept(group, index, construct_id)?; let item = self.vbox.buy(group, &index)?;
if construct_id.is_some() {
let equip_index = self.vbox.bound.len() - 1; match construct_id {
self.vbox_apply(equip_index, construct_id.expect("no construct"))?; Some(id) => { self.vbox_apply(index, id)?; },
} None => { self.vbox.stash_add(item, None)?; },
};
Ok(self) Ok(self)
} }
pub fn vbox_combine(&mut self, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<&mut Player, Error> { pub fn vbox_combine(&mut self, inv_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<&mut Player, Error> {
self.vbox.combine(inv_indices, vbox_indices)?; self.vbox.combine(inv_indices, vbox_indices)?;
Ok(self) Ok(self)
} }
pub fn vbox_reclaim(&mut self, index: usize) -> Result<&mut Player, Error> { pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> {
self.vbox.reclaim(index)?; self.vbox.refund(index)?;
Ok(self) Ok(self)
} }
pub fn vbox_apply(&mut self, index: usize, construct_id: Uuid) -> Result<&mut Player, Error> { pub fn vbox_apply(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> {
if self.vbox.bound.get(index).is_none() { let item = self.vbox.stash.remove(&index)
return Err(format_err!("no item at index {:?}", index)); .ok_or(format_err!("no item at index {:?} {:?}", self.vbox.stash, index))?;
}
let item = self.vbox.bound.remove(index);
match item.effect() { match item.effect() {
Some(ItemEffect::Skill) => { Some(ItemEffect::Skill) => {
@ -317,8 +423,8 @@ impl Player {
} }
pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<&mut Player, Error> { pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
if self.vbox.bound.len() >= 9 && !target_construct_id.is_some() { if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() {
return Err(err_msg("too many items bound")); return Err(err_msg("too many items stash"));
} }
match target.effect() { match target.effect() {
@ -349,13 +455,12 @@ impl Player {
construct.apply_modifiers(&player_colours); construct.apply_modifiers(&player_colours);
} }
self.vbox.bound.push(target); let equip_index = self.vbox.stash_add(target, None)?;
if target_construct_id.is_some() { if target_construct_id.is_some() {
let equip_index = self.vbox.bound.len() - 1;
self.vbox_apply(equip_index, target_construct_id.expect("no construct"))?; self.vbox_apply(equip_index, target_construct_id.expect("no construct"))?;
} }
// self.vbox.bound.sort_unstable(); // self.vbox.stash.sort_unstable();
Ok(self) Ok(self)
} }
@ -408,8 +513,8 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
player.vbox.fill();
player.vbox.fill();
player.autobuy(); player.autobuy();
assert!(player.constructs.iter().all(|c| c.skills.len() >= 1)); assert!(player.constructs.iter().all(|c| c.skills.len() >= 1));

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::time::{Instant}; use std::time::{Instant};
use std::thread::{spawn}; use std::thread::{spawn};
@ -34,7 +35,7 @@ use mail::Email;
use pg::{Db}; use pg::{Db};
use pg::{PgPool}; use pg::{PgPool};
use skill::{Skill, dev_resolve, Resolutions}; use skill::{Skill, dev_resolve, Resolutions};
use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip}; use vbox::{ItemType, vbox_buy, vbox_apply, vbox_refill, vbox_combine, vbox_refund, vbox_unequip};
use http::{AUTH_CLEAR, TOKEN_HEADER}; use http::{AUTH_CLEAR, TOKEN_HEADER};
#[derive(Debug,Clone,Serialize)] #[derive(Debug,Clone,Serialize)]
@ -114,14 +115,14 @@ pub enum RpcRequest {
InstanceState { instance_id: Uuid }, InstanceState { instance_id: Uuid },
InstanceChat { instance_id: Uuid, index: usize }, InstanceChat { instance_id: Uuid, index: usize },
VboxAccept { instance_id: Uuid, group: usize, index: usize }, VboxBuy { instance_id: Uuid, group: ItemType, index: String },
VboxAcceptEquip { instance_id: Uuid, group: usize, index: usize, construct_id: Uuid }, VboxBuyEquip { instance_id: Uuid, group: ItemType, index: String, construct_id: Uuid },
VboxDiscard { instance_id: Uuid }, VboxRefill { instance_id: Uuid },
VboxCombine { instance_id: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>> }, VboxCombine { instance_id: Uuid, inv_indices: Vec<String>, vbox_indices: Option<HashMap<ItemType, Vec<String>>> },
VboxApply { instance_id: Uuid, construct_id: Uuid, index: usize }, VboxApply { instance_id: Uuid, construct_id: Uuid, index: String },
VboxUnequip { instance_id: Uuid, construct_id: Uuid, target: Item }, VboxUnequip { instance_id: Uuid, construct_id: Uuid, target: Item },
VboxUnequipApply { instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Uuid }, VboxUnequipApply { instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Uuid },
VboxReclaim { instance_id: Uuid, index: usize }, VboxRefund { instance_id: Uuid, index: String },
} }
struct Connection { struct Connection {
@ -245,11 +246,11 @@ impl Connection {
RpcRequest::InstanceAbandon { instance_id } => RpcRequest::InstanceAbandon { instance_id } =>
Ok(instance_abandon(&mut tx, account, instance_id)?), Ok(instance_abandon(&mut tx, account, instance_id)?),
RpcRequest::VboxAccept { instance_id, group, index } => RpcRequest::VboxBuy { instance_id, group, index } =>
Ok(RpcMessage::InstanceState(vbox_accept(&mut tx, account, instance_id, group, index, None)?)), Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, account, instance_id, group, index, None)?)),
RpcRequest::VboxAcceptEquip { instance_id, group, index, construct_id } => RpcRequest::VboxBuyEquip { instance_id, group, index, construct_id } =>
Ok(RpcMessage::InstanceState(vbox_accept(&mut tx, account, instance_id, group, index, Some(construct_id))?)), Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, account, instance_id, group, index, Some(construct_id))?)),
RpcRequest::VboxApply { instance_id, construct_id, index } => RpcRequest::VboxApply { instance_id, construct_id, index } =>
Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, account, instance_id, construct_id, index)?)), Ok(RpcMessage::InstanceState(vbox_apply(&mut tx, account, instance_id, construct_id, index)?)),
@ -257,11 +258,11 @@ impl Connection {
RpcRequest::VboxCombine { instance_id, inv_indices, vbox_indices } => RpcRequest::VboxCombine { instance_id, inv_indices, vbox_indices } =>
Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, inv_indices, vbox_indices)?)), Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, inv_indices, vbox_indices)?)),
RpcRequest::VboxDiscard { instance_id } => RpcRequest::VboxRefill { instance_id } =>
Ok(RpcMessage::InstanceState(vbox_discard(&mut tx, account, instance_id)?)), Ok(RpcMessage::InstanceState(vbox_refill(&mut tx, account, instance_id)?)),
RpcRequest::VboxReclaim { instance_id, index } => RpcRequest::VboxRefund { instance_id, index } =>
Ok(RpcMessage::InstanceState(vbox_reclaim(&mut tx, account, instance_id, index)?)), Ok(RpcMessage::InstanceState(vbox_refund(&mut tx, account, instance_id, index)?)),
RpcRequest::VboxUnequip { instance_id, construct_id, target } => RpcRequest::VboxUnequip { instance_id, construct_id, target } =>
Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, account, instance_id, construct_id, target, None)?)), Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, account, instance_id, construct_id, target, None)?)),

View File

@ -1,8 +1,9 @@
use uuid::Uuid; use uuid::Uuid;
use std::iter; use std::iter;
use std::collections::HashMap;
// reclaims // refunds
use rand::prelude::*; use rand::prelude::*;
use rand::{thread_rng}; use rand::{thread_rng};
use rand::distributions::{WeightedIndex}; use rand::distributions::{WeightedIndex};
@ -19,30 +20,48 @@ use construct::{Colours};
use item::*; use item::*;
pub type VboxIndices = Option<HashMap<ItemType, Vec<String>>>;
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Vbox { pub struct Vbox {
pub bits: usize, pub bits: usize,
pub free: Vec<Vec<Option<Item>>>, pub store: HashMap<ItemType, HashMap<String, Item>>,
pub bound: Vec<Item>, pub stash: HashMap<String, Item>,
} }
#[derive(Debug,Copy,Clone,Serialize,Deserialize,Hash,PartialEq,Eq)]
pub enum ItemType { pub enum ItemType {
Colours, Colours,
Skills, Skills,
Specs, 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 { impl Vbox {
pub fn new() -> Vbox { pub fn new() -> Vbox {
let starting_items = vec![ let mut colours: HashMap<String, Item> = HashMap::new();
Item::Attack, let mut skills: HashMap<String, Item> = HashMap::new();
Item::Attack, let mut specs: HashMap<String, Item> = HashMap::new();
Item::Attack,
]; 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 { Vbox {
free: vec![vec![], vec![], vec![]], store,
bound: starting_items, stash,
bits: 30, bits: 30,
} }
} }
@ -65,103 +84,130 @@ impl Vbox {
pub fn fill(&mut self) -> &mut Vbox { pub fn fill(&mut self) -> &mut Vbox {
let mut rng = thread_rng(); let mut rng = thread_rng();
self.free = [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() let colours = vec![
.map(|item_type| { (Item::Red, 1),
let items = match item_type { (Item::Green, 1),
ItemType::Colours => vec![ (Item::Blue, 1),
(Some(Item::Red), 1), ];
(Some(Item::Green), 1), let colour_dist = WeightedIndex::new(colours.iter().map(|item| item.1)).unwrap();
(Some(Item::Blue), 1),
], let skills = vec![
ItemType::Skills => vec![ (Item::Attack, 1),
(Some(Item::Attack), 1), (Item::Block, 1),
(Some(Item::Block), 1), (Item::Buff, 1),
(Some(Item::Buff), 1), (Item::Debuff, 1),
(Some(Item::Debuff), 1), (Item::Stun, 1),
(Some(Item::Stun), 1), ];
], let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap();
ItemType::Specs => vec![
(Some(Item::Power), 1), let specs = vec![
(Some(Item::Life), 1), (Item::Power, 1),
(Some(Item::Speed), 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 dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap(); let drops = iter::repeat_with(|| items[dist.sample(&mut rng)].0)
iter::repeat_with(|| { .take(num)
items[dist.sample(&mut rng)].0}).take(match item_type { .enumerate()
ItemType::Colours => 6, .map(|(i, item)| (i.to_string(), item))
_ => 3, .collect::<HashMap<String, Item>>();
}).collect::<Vec<Option<Item>>>()
}) self.store.insert(*item_type, drops);
.collect::<Vec<Vec<Option<Item>>>>(); }
self self
} }
pub fn accept(&mut self, i: usize, j: usize, construct_id: Option<Uuid>) -> Result<&mut Vbox, Error> { pub fn buy(&mut self, item: ItemType, i: &String) -> Result<Item, Error> {
if self.bound.len() >= 6 && !construct_id.is_some() {
return Err(err_msg("too many items bound"));
}
// check item exists // check item exists
self.free let selection = self.store
.get(i).ok_or(format_err!("no item group at index {:?}", i))? .get_mut(&item).ok_or(format_err!("no item group {:?}", item))?
.get(j).ok_or(format_err!("no item at index {:?}", j))?; .remove(i).ok_or(format_err!("no item at index {:?} {:}", item, i))?;
// check can purchase self.balance_sub(selection.cost())?;
let cost = match self.free[i][j] {
None => 0,
_ => self.free[i][j].unwrap().cost()
};
self.balance_sub(cost)?;
// actually move Ok(selection)
match self.free[i][j] {
None => (),
_ => self.bound.push(self.free[i][j].unwrap())
}
// self.bound.push(self.free[i][j].unwrap());
self.free[i][j] = None;
// self.bound.sort_unstable();
Ok(self)
} }
pub fn bot_accept(&mut self, i: usize) -> Result<&mut Vbox, Error> { pub fn stash_add(&mut self, item: Item, index: Option<&String>) -> Result<String, Error> {
let buy_index = self.free[i].iter().position(|item| item.is_some()); if self.stash.len() >= STASH_CAPACITY {
self.accept(i, buy_index.expect("no valid buys"), None) return Err(err_msg("stash full"));
} }
pub fn reclaim(&mut self, i: usize) -> Result<&mut Vbox, Error> { if let Some(index) = index {
self.bound.get(i).ok_or(format_err!("no item at index {:?}", i))?; if self.stash.contains_key(index) {
let reclaimed = self.bound.remove(i); return Err(format_err!("slot occupied {:?}", index));
let refund = reclaimed.cost(); }
// info!("reclaiming {:?} for {:?}", refund, reclaimed); 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); self.balance_add(refund);
Ok(self) Ok(self)
} }
pub fn combine(&mut self, mut inv_indices: Vec<usize>, vbox_indicies: Vec<Vec<usize>>) -> Result<&mut Vbox, Error> { pub fn combine(&mut self, stash_indices: Vec<String>, store_indices: Option<HashMap<ItemType, Vec<String>>>) -> Result<&mut Vbox, Error> {
if !inv_indices.iter().all(|i| self.bound.get(*i).is_some()) { // find base item for index to insert into
return Err(err_msg("item missing index")); let base_index = stash_indices.iter()
} .find(|i| match self.stash.get(i.clone()) {
// try to buy up the vbox indicies and add them to the inventory indicies for combining Some(item) => item.into_skill().is_some(),
for vi in vbox_indicies.iter() { None => false,
inv_indices.push(self.bound.len()); });
self.accept(vi[0], vi[1], Some(Uuid::nil()))?;
}
// have to sort the indices and keep track of the iteration let mut input = stash_indices
// because when removing the elements the array shifts
inv_indices.sort_unstable();
let mut input = inv_indices
.iter() .iter()
.enumerate() .map(|i| self.stash.remove(i)
.map(|(i, index)| { .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i)))
self.bound.remove(index.saturating_sub(i)) .collect::<Result<Vec<Item>, Error>>()?;
})
.collect::<Vec<Item>>(); 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 // sort the input to align with the combinations
// combos are sorted when created // combos are sorted when created
@ -169,40 +215,37 @@ impl Vbox {
let combos = get_combos(); let combos = get_combos();
let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?; let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?;
self.bound.push(combo.item); self.stash_add(combo.item, base_index)?;
// self.bound.sort_unstable();
if self.bound.len() > 6 {
return Err(err_msg("too many items bound"));
}
Ok(self) Ok(self)
} }
} }
pub fn vbox_discard(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> { pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_discard(account.id)?; .vbox_refill(account.id)?;
return instance_update(tx, instance); return instance_update(tx, instance);
} }
pub fn vbox_accept(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: usize, index: usize, construct_id: Option<Uuid>) -> Result<Instance, Error> { pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_accept(account.id, group, index, construct_id)?; .vbox_buy(account.id, group, index, construct_id)?;
return instance_update(tx, instance); return instance_update(tx, instance);
} }
pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, inv_indices: Vec<usize>, vbox_indices: Vec<Vec<usize>>) -> Result<Instance, Error> { pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_combine(account.id, inv_indices, vbox_indices)?; .vbox_combine(account.id, stash_indices, vbox_indices)?;
return instance_update(tx, instance); return instance_update(tx, instance);
} }
pub fn vbox_reclaim(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: usize) -> Result<Instance, Error> { pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_reclaim(account.id, index)?; .vbox_refund(account.id, index)?;
return instance_update(tx, instance); return instance_update(tx, instance);
} }
pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: usize) -> Result<Instance, Error> { pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result<Instance, Error> {
let instance = instance_get(tx, instance_id)? let instance = instance_get(tx, instance_id)?
.vbox_apply(account.id, index, construct_id)?; .vbox_apply(account.id, index, construct_id)?;
return instance_update(tx, instance); return instance_update(tx, instance);
@ -221,9 +264,49 @@ mod tests {
#[test] #[test]
fn combine_test() { fn combine_test() {
let mut vbox = Vbox::new(); let mut vbox = Vbox::new();
vbox.bound = vec![Item::Attack, Item::Green, Item::Green]; vbox.stash.insert(0.to_string(), Item::Attack);
vbox.combine(vec![1,2,0], vec![]).unwrap(); vbox.stash.insert(1.to_string(), Item::Green);
assert_eq!(vbox.bound[0], Item::Heal); 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] #[test]
@ -239,10 +322,10 @@ mod tests {
} }
#[test] #[test]
fn reclaim_test() { fn refund_test() {
let mut vbox = Vbox::new(); let mut vbox = Vbox::new();
vbox.bound = vec![Item::Strike]; vbox.stash.insert(0.to_string(), Item::Strike);
vbox.reclaim(0).unwrap(); vbox.refund(0.to_string()).unwrap();
assert_eq!(vbox.bits, 32); assert_eq!(vbox.bits, 32);
} }