From 33ad4db8f590b4d8207148daf4732c84a8f42a39 Mon Sep 17 00:00:00 2001 From: ntr Date: Thu, 28 Nov 2019 15:43:48 +1000 Subject: [PATCH 1/6] server --- server/src/instance.rs | 36 +++-- server/src/player.rs | 287 ++++++++++++++++++++++++++------------- server/src/rpc.rs | 31 ++--- server/src/vbox.rs | 297 ++++++++++++++++++++++++++--------------- 4 files changed, 419 insertions(+), 232 deletions(-) diff --git a/server/src/instance.rs b/server/src/instance.rs index c5ec9c04..0d052d08 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -15,7 +15,7 @@ use chrono::prelude::*; use chrono::Duration; use account::Account; -use account; +use vbox; use player::{Player, Score, player_create}; @@ -466,35 +466,35 @@ impl Instance { Ok(()) } - pub fn vbox_discard(mut self, account: Uuid) -> Result { + pub fn vbox_refill(mut self, account: Uuid) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? - .vbox_discard()?; + .vbox_refill()?; Ok(self) } - pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize, construct_id: Option) -> Result { + pub fn vbox_buy(mut self, account: Uuid, group: vbox::ItemType, index: String, construct_id: Option) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? - .vbox_accept(group, index, construct_id)?; + .vbox_buy(group, index, construct_id)?; Ok(self) } - pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec, vbox_indices: Vec>) -> Result { + pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec, vbox_indices: vbox::VboxIndices) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_combine(inv_indices, vbox_indices)?; Ok(self) } - pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result { + pub fn vbox_refund(mut self, account: Uuid, index: String) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? - .vbox_reclaim(index)?; + .vbox_refund(index)?; Ok(self) } - pub fn vbox_apply(mut self, account: Uuid, index: usize, construct_id: Uuid) -> Result { + pub fn vbox_apply(mut self, account: Uuid, index: String, construct_id: Uuid) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_apply(index, construct_id)?; @@ -834,19 +834,17 @@ mod tests { fn instance_pve_test() { let mut instance = Instance::new(); - let bot_player = bot_player(); - let bot = bot_player.id; - instance.add_player(bot_player).unwrap(); + let bot = bot_player(); + let bot_one = bot.id; + instance.add_player(bot).unwrap(); - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - - instance.add_player(player).expect("could not add player"); + let bot = bot_player(); + let bot_two = bot.id; + instance.add_player(bot).unwrap(); assert_eq!(instance.phase, InstancePhase::Lobby); - instance.player_ready(player_account).unwrap(); - instance.player_ready(bot).unwrap(); + instance.player_ready(bot_one).unwrap(); + instance.player_ready(bot_two).unwrap(); assert_eq!(instance.phase, InstancePhase::Finished); } diff --git a/server/src/player.rs b/server/src/player.rs index 6a0a5abd..9b1fbeca 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -1,3 +1,5 @@ +use std::collections::{HashMap}; + use uuid::Uuid; use rand::prelude::*; @@ -9,7 +11,7 @@ use failure::err_msg; use account; use account::Account; use construct::{Construct, Colours}; -use vbox::{Vbox}; +use vbox::{Vbox, ItemType, VboxIndices}; use item::{Item, ItemEffect}; use effect::{Effect}; @@ -155,124 +157,228 @@ impl Player { pub fn autobuy(&mut self) -> &mut Player { let mut rng = thread_rng(); - // 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.bound.iter().position(|v| v.into_skill().is_some()) { - let construct_id = self.constructs[c].id; - self.vbox_apply(s, construct_id).expect("could not apply"); + // skill buying phase + while self.constructs.iter().any(|c| c.skills.len() < 3) { + // find the construct with the smallest number of skills + let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) { + None => panic!("no constructs in autobuy"), + 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; } - info!("no skills available..."); - } + // need to buy one + else { - // now keep buying and applying items cause whynot - // inb4 montecarlo gan + // do we have any colours in store? + let colours = self.vbox.store[&ItemType::Colours].keys() + .cloned() + .collect::>(); - loop { - let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) { - 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; + // how about a base skill? + let base = match self.vbox.store[&ItemType::Skills].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::Skills, vec![base.unwrap()]); + + match self.vbox_combine(vec![], Some(vbox_items)) { + Ok(_) => continue, + Err(_) => break, // give up } } - (target_construct_i, self.constructs[target_construct_i].id) - }, - false => { - let i = rng.gen_range(0, 3); - (i, self.constructs[i].id) - }, - }; - - let needs_skills = self.constructs[target_construct_i].skills.len() < 3; - let group_i = match needs_skills { - true => 1, - 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 - let free_colours = self.vbox.free[0].iter().fold(0, |count, item| { - 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); - - let skills = [Item::Attack, Item::Block, Item::Buff, Item::Debuff, Item::Stun]; - let combo_i = match group_i { - 1 => self.vbox.bound.iter().position(|v| skills.contains(v)).expect("no skill found"), - 2 => self.vbox.bound.iter().position(|v| v.into_spec().is_some()).expect("no spec found"), - _ => panic!("unknown group_i"), + // 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, }; - // first 2 colours can be whatever - self.vbox_combine(vec![0, 1, combo_i], vec![]).ok(); - let item_i = self.vbox.bound.len() - 1; - self.vbox_apply(item_i, target_construct_id).ok(); + 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::>(); + + // 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()]); + + match self.vbox_combine(vec![], Some(vbox_items)) { + Ok(_) => continue, + Err(_) => break, // give up + } + } + } + } } + // upgrading phase + // NYI + + // // 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..."); + // } + + // // now keep buying and applying items cause whynot + // // inb4 montecarlo gan + // loop { + // let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) { + // 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) + // }, + // }; + + // let needs_skills = self.constructs[target_construct_i].skills.len() < 3; + // let group_i = match needs_skills { + // true => 1, + // 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; } - 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.fill(); Ok(self) } - pub fn bot_vbox_accept(&mut self, group: usize) -> Result<&mut Player, Error> { - self.vbox.bot_accept(group)?; + pub fn bot_vbox_accept(&mut self, group: ItemType) -> Result<&mut Player, Error> { + let item = self.vbox.bot_buy(group)?; + self.vbox.stash_add(item, None)?; Ok(self) } - pub fn vbox_accept(&mut self, group: usize, index: usize, construct_id: Option) -> Result<&mut Player, Error> { - self.vbox.accept(group, index, construct_id)?; - if construct_id.is_some() { - let equip_index = self.vbox.bound.len() - 1; - self.vbox_apply(equip_index, construct_id.expect("no construct"))?; - } + pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option) -> Result<&mut Player, Error> { + let item = self.vbox.buy(group, &index)?; + + match construct_id { + Some(id) => { self.vbox_apply(index, id)?; }, + None => { self.vbox.stash_add(item, None)?; }, + }; + Ok(self) } - pub fn vbox_combine(&mut self, inv_indices: Vec, vbox_indices: Vec>) -> Result<&mut Player, Error> { + pub fn vbox_combine(&mut self, inv_indices: Vec, vbox_indices: VboxIndices) -> Result<&mut Player, Error> { self.vbox.combine(inv_indices, vbox_indices)?; Ok(self) } - pub fn vbox_reclaim(&mut self, index: usize) -> Result<&mut Player, Error> { - self.vbox.reclaim(index)?; + pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> { + self.vbox.refund(index)?; Ok(self) } - pub fn vbox_apply(&mut self, index: usize, construct_id: Uuid) -> Result<&mut Player, Error> { - if self.vbox.bound.get(index).is_none() { - return Err(format_err!("no item at index {:?}", index)); - } - - let item = self.vbox.bound.remove(index); + pub fn vbox_apply(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> { + let item = self.vbox.stash.remove(&index) + .ok_or(format_err!("no item at index {:?} {:?}", self.vbox.stash, index))?; match item.effect() { Some(ItemEffect::Skill) => { @@ -317,8 +423,8 @@ impl Player { } pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result<&mut Player, Error> { - if self.vbox.bound.len() >= 9 && !target_construct_id.is_some() { - return Err(err_msg("too many items bound")); + if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() { + return Err(err_msg("too many items stash")); } match target.effect() { @@ -349,13 +455,12 @@ impl Player { 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() { - let equip_index = self.vbox.bound.len() - 1; self.vbox_apply(equip_index, target_construct_id.expect("no construct"))?; } - // self.vbox.bound.sort_unstable(); + // self.vbox.stash.sort_unstable(); Ok(self) } @@ -408,8 +513,8 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - player.vbox.fill(); + player.vbox.fill(); player.autobuy(); assert!(player.constructs.iter().all(|c| c.skills.len() >= 1)); diff --git a/server/src/rpc.rs b/server/src/rpc.rs index bc0ba493..9f43a928 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::time::{Instant}; use std::thread::{spawn}; @@ -34,7 +35,7 @@ use mail::Email; use pg::{Db}; use pg::{PgPool}; 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}; #[derive(Debug,Clone,Serialize)] @@ -114,14 +115,14 @@ pub enum RpcRequest { InstanceState { instance_id: Uuid }, InstanceChat { instance_id: Uuid, index: usize }, - VboxAccept { instance_id: Uuid, group: usize, index: usize }, - VboxAcceptEquip { instance_id: Uuid, group: usize, index: usize, construct_id: Uuid }, - VboxDiscard { instance_id: Uuid }, - VboxCombine { instance_id: Uuid, inv_indices: Vec, vbox_indices: Vec> }, - VboxApply { instance_id: Uuid, construct_id: Uuid, index: usize }, + VboxBuy { instance_id: Uuid, group: ItemType, index: String }, + VboxBuyEquip { instance_id: Uuid, group: ItemType, index: String, construct_id: Uuid }, + VboxRefill { instance_id: Uuid }, + VboxCombine { instance_id: Uuid, inv_indices: Vec, vbox_indices: Option>> }, + VboxApply { instance_id: Uuid, construct_id: Uuid, index: String }, VboxUnequip { instance_id: Uuid, construct_id: Uuid, target: Item }, 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 { @@ -245,11 +246,11 @@ impl Connection { RpcRequest::InstanceAbandon { instance_id } => Ok(instance_abandon(&mut tx, account, instance_id)?), - RpcRequest::VboxAccept { instance_id, group, index } => - Ok(RpcMessage::InstanceState(vbox_accept(&mut tx, account, instance_id, group, index, None)?)), + RpcRequest::VboxBuy { instance_id, group, index } => + Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, account, instance_id, group, index, None)?)), - RpcRequest::VboxAcceptEquip { instance_id, group, index, construct_id } => - Ok(RpcMessage::InstanceState(vbox_accept(&mut tx, account, instance_id, group, index, Some(construct_id))?)), + RpcRequest::VboxBuyEquip { instance_id, group, index, construct_id } => + Ok(RpcMessage::InstanceState(vbox_buy(&mut tx, account, instance_id, group, index, Some(construct_id))?)), RpcRequest::VboxApply { 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 } => Ok(RpcMessage::InstanceState(vbox_combine(&mut tx, account, instance_id, inv_indices, vbox_indices)?)), - RpcRequest::VboxDiscard { instance_id } => - Ok(RpcMessage::InstanceState(vbox_discard(&mut tx, account, instance_id)?)), + RpcRequest::VboxRefill { instance_id } => + Ok(RpcMessage::InstanceState(vbox_refill(&mut tx, account, instance_id)?)), - RpcRequest::VboxReclaim { instance_id, index } => - Ok(RpcMessage::InstanceState(vbox_reclaim(&mut tx, account, instance_id, index)?)), + RpcRequest::VboxRefund { instance_id, index } => + Ok(RpcMessage::InstanceState(vbox_refund(&mut tx, account, instance_id, index)?)), RpcRequest::VboxUnequip { instance_id, construct_id, target } => Ok(RpcMessage::InstanceState(vbox_unequip(&mut tx, account, instance_id, construct_id, target, None)?)), diff --git a/server/src/vbox.rs b/server/src/vbox.rs index faf93fdc..e2507c74 100644 --- a/server/src/vbox.rs +++ b/server/src/vbox.rs @@ -1,8 +1,9 @@ use uuid::Uuid; use std::iter; +use std::collections::HashMap; -// reclaims +// refunds use rand::prelude::*; use rand::{thread_rng}; use rand::distributions::{WeightedIndex}; @@ -19,30 +20,48 @@ use construct::{Colours}; use item::*; +pub type VboxIndices = Option>>; + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { pub bits: usize, - pub free: Vec>>, - pub bound: Vec, + 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 starting_items = vec![ - Item::Attack, - Item::Attack, - Item::Attack, - ]; + let mut colours: HashMap = HashMap::new(); + let mut skills: HashMap = HashMap::new(); + let mut 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 { - free: vec![vec![], vec![], vec![]], - bound: starting_items, + store, + stash, bits: 30, } } @@ -65,103 +84,130 @@ impl Vbox { pub fn fill(&mut self) -> &mut Vbox { let mut rng = thread_rng(); - self.free = [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() - .map(|item_type| { - let items = match item_type { - ItemType::Colours => vec![ - (Some(Item::Red), 1), - (Some(Item::Green), 1), - (Some(Item::Blue), 1), - ], - ItemType::Skills => vec![ - (Some(Item::Attack), 1), - (Some(Item::Block), 1), - (Some(Item::Buff), 1), - (Some(Item::Debuff), 1), - (Some(Item::Stun), 1), - ], - ItemType::Specs => vec![ - (Some(Item::Power), 1), - (Some(Item::Life), 1), - (Some(Item::Speed), 1), - ], - }; + 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 dist = WeightedIndex::new(items.iter().map(|item| item.1)).unwrap(); - iter::repeat_with(|| { - items[dist.sample(&mut rng)].0}).take(match item_type { - ItemType::Colours => 6, - _ => 3, - }).collect::>>() - }) - .collect::>>>(); + 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 accept(&mut self, i: usize, j: usize, construct_id: Option) -> Result<&mut Vbox, Error> { - if self.bound.len() >= 6 && !construct_id.is_some() { - return Err(err_msg("too many items bound")); - } - + pub fn buy(&mut self, item: ItemType, i: &String) -> Result { // 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))?; + 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 {:?} {:}", item, i))?; - // check can purchase - let cost = match self.free[i][j] { - None => 0, - _ => self.free[i][j].unwrap().cost() - }; - self.balance_sub(cost)?; + self.balance_sub(selection.cost())?; - // actually move - match self.free[i][j] { - None => (), - _ => self.bound.push(self.free[i][j].unwrap()) + 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")); } - // self.bound.push(self.free[i][j].unwrap()); - self.free[i][j] = None; - // self.bound.sort_unstable(); - Ok(self) + 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_accept(&mut self, i: usize) -> Result<&mut Vbox, Error> { - let buy_index = self.free[i].iter().position(|item| item.is_some()); - self.accept(i, buy_index.expect("no valid buys"), None) + 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 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); + 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, mut inv_indices: Vec, vbox_indicies: Vec>) -> Result<&mut Vbox, Error> { - if !inv_indices.iter().all(|i| self.bound.get(*i).is_some()) { - return Err(err_msg("item missing index")); - } - // try to buy up the vbox indicies and add them to the inventory indicies for combining - for vi in vbox_indicies.iter() { - inv_indices.push(self.bound.len()); - self.accept(vi[0], vi[1], Some(Uuid::nil()))?; - } + 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, + }); - // have to sort the indices and keep track of the iteration - // because when removing the elements the array shifts - inv_indices.sort_unstable(); - let mut input = inv_indices + let mut input = stash_indices .iter() - .enumerate() - .map(|(i, index)| { - self.bound.remove(index.saturating_sub(i)) - }) - .collect::>(); + .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 @@ -169,40 +215,37 @@ impl Vbox { 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(); - if self.bound.len() > 6 { - return Err(err_msg("too many items bound")); - } + self.stash_add(combo.item, base_index)?; + Ok(self) } } -pub fn vbox_discard(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { +pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { let instance = instance_get(tx, instance_id)? - .vbox_discard(account.id)?; + .vbox_refill(account.id)?; return instance_update(tx, instance); } -pub fn vbox_accept(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: usize, index: usize, construct_id: Option) -> Result { +pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option) -> Result { 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); } -pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, inv_indices: Vec, vbox_indices: Vec>) -> Result { +pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec, vbox_indices: VboxIndices) -> Result { 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); } -pub fn vbox_reclaim(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: usize) -> Result { +pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result { let instance = instance_get(tx, instance_id)? - .vbox_reclaim(account.id, index)?; + .vbox_refund(account.id, index)?; return instance_update(tx, instance); } -pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: usize) -> Result { +pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result { let instance = instance_get(tx, instance_id)? .vbox_apply(account.id, index, construct_id)?; return instance_update(tx, instance); @@ -221,9 +264,49 @@ mod tests { #[test] fn combine_test() { let mut vbox = Vbox::new(); - vbox.bound = vec![Item::Attack, Item::Green, Item::Green]; - vbox.combine(vec![1,2,0], vec![]).unwrap(); - assert_eq!(vbox.bound[0], Item::Heal); + 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] @@ -239,10 +322,10 @@ mod tests { } #[test] - fn reclaim_test() { + fn refund_test() { let mut vbox = Vbox::new(); - vbox.bound = vec![Item::Strike]; - vbox.reclaim(0).unwrap(); + vbox.stash.insert(0.to_string(), Item::Strike); + vbox.refund(0.to_string()).unwrap(); assert_eq!(vbox.bits, 32); } From 4920821ec2a6e918a7441458ea16a48fba7f9345 Mon Sep 17 00:00:00 2001 From: ntr Date: Thu, 28 Nov 2019 15:48:31 +1000 Subject: [PATCH 2/6] rm free and bound --- client/src/components/instance.constructs.jsx | 6 +- client/src/components/vbox.combiner.jsx | 2 +- client/src/components/vbox.component.jsx | 2 +- client/src/components/vbox.stash.jsx | 4 +- client/src/components/vbox.store.jsx | 10 ++-- client/src/components/vbox.utils.jsx | 16 +++--- client/src/tutorial.utils.jsx | 56 +++++++++---------- 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/client/src/components/instance.constructs.jsx b/client/src/components/instance.constructs.jsx index c0a0b36d..3ed752b7 100644 --- a/client/src/components/instance.constructs.jsx +++ b/client/src/components/instance.constructs.jsx @@ -98,7 +98,7 @@ function Construct(props) { const duplicateSkill = construct.skills.length !== 0 && construct.skills.every(sk => { if (!itemEquip && itemEquip !== 0) return false; if (!sk) return false; - return sk.skill === vbox.bound[itemEquip]; + return sk.skill === vbox.stash[itemEquip]; }); const tutorialDisableEquip = tutorialShouldDisableEquip(tutorial, iter, instance, construct); function onClick(e) { @@ -136,7 +136,7 @@ function Construct(props) { return true; } - const equipping = skillList.includes(vbox.bound[itemEquip]) && !skill + const equipping = skillList.includes(vbox.stash[itemEquip]) && !skill && !tutorialDisableEquip && !duplicateSkill && i === construct.skills.length; const border = () => { if (!skill) return ''; @@ -169,7 +169,7 @@ function Construct(props) { const s = construct.specs[i]; if (!s) { - const equipping = specList.includes(vbox.bound[itemEquip]) && i === construct.specs.length; + const equipping = specList.includes(vbox.stash[itemEquip]) && i === construct.specs.length; const classes = `${equipping ? 'equipping' : 'gray'} empty`; return ( ); } diff --git a/client/src/components/vbox.component.jsx b/client/src/components/vbox.component.jsx index f00e9b63..9d192b9c 100644 --- a/client/src/components/vbox.component.jsx +++ b/client/src/components/vbox.component.jsx @@ -161,7 +161,7 @@ class Vbox extends preact.Component { function stashHdr() { const refund = storeSelect.length === 0 && stashSelect.length === 1 - ? itemInfo.items.find(i => i.item === vbox.bound[stashSelect[0]]).cost + ? itemInfo.items.find(i => i.item === vbox.stash[stashSelect[0]]).cost : 0; const tutorialDisabled = tutorial && tutorial < 8 && instance.time_control === 'Practice' && instance.rounds.length === 1; diff --git a/client/src/components/vbox.stash.jsx b/client/src/components/vbox.stash.jsx index 97511443..cec94686 100644 --- a/client/src/components/vbox.stash.jsx +++ b/client/src/components/vbox.stash.jsx @@ -81,7 +81,7 @@ class stashElement extends preact.Component { } if (notValidCombo) { - setInfo(vbox.bound[i]); + setInfo(vbox.stash[i]); return setVboxSelected({ storeSelect: [], stashSelect: [i] }); } @@ -123,7 +123,7 @@ class stashElement extends preact.Component { onDragOver={ev => ev.preventDefault()} onDrop={stashClick} > - {range(0, 6).map(i => stashBtn(vbox.bound[i], i))} + {range(0, 6).map(i => stashBtn(vbox.stash[i], i.toString()))} ); } diff --git a/client/src/components/vbox.store.jsx b/client/src/components/vbox.store.jsx index 9a168fc6..f4d1878c 100644 --- a/client/src/components/vbox.store.jsx +++ b/client/src/components/vbox.store.jsx @@ -32,7 +32,7 @@ class storeElement extends preact.Component { const { storeSelect, stashSelect } = vboxSelected; function availableBtn(v, group, index) { - if (!v) return ; + if (!v) return ; const selected = storeSelect.length && storeSelect.some(vs => vs[0] === group && vs[1] === index); const notValidCombo = vboxHighlight && !vboxHighlight.includes(v); @@ -63,7 +63,7 @@ class storeElement extends preact.Component { const disabled = vbox.bits <= group; return (