use std::iter; use uuid::Uuid; // drops 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, VboxDropParams}; use skill::{Skill}; use player::{Player, player_get, player_update}; use cryp::{cryp_get, cryp_write}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Var { Blue, Green, Red, Attack, Block, Stun, Buff, Debuff, Amplify, Banish, Blast, Curse, Empower, Haste, Heal, Hex, Parry, Purge, Purify, Reflect, Ruin, Shield, Silence, Slay, Slow, Snare, Strangle, Strike, Siphon, Survival, Taunt, Throw, Toxic, Triage, } impl Var { fn is_base(&self) -> bool { match self { Var::Attack | Var::Block | Var::Stun | Var::Debuff | Var::Buff => true, _ => false, } } fn skill(&self) -> Result { match self { Var::Amplify => Ok(Skill::Amplify), Var::Banish => Ok(Skill::Banish), Var::Blast => Ok(Skill::Blast), Var::Curse => Ok(Skill::Curse), Var::Empower => Ok(Skill::Empower), Var::Haste => Ok(Skill::Haste), Var::Heal => Ok(Skill::Heal), Var::Hex => Ok(Skill::Hex), Var::Parry => Ok(Skill::Parry), Var::Purge => Ok(Skill::Purge), Var::Purify => Ok(Skill::Purify), // Var::Reflect => Ok(Skill::Reflect), Var::Ruin => Ok(Skill::Ruin), Var::Shield => Ok(Skill::Shield), Var::Silence => Ok(Skill::Silence), Var::Slay => Ok(Skill::Slay), Var::Slow => Ok(Skill::Slow), Var::Snare => Ok(Skill::Snare), Var::Strangle => Ok(Skill::Strangle), // Var::Strike => Ok(Skill::Strike), // Var::Survival => Ok(Skill::Survival), // Var::Taunt => Ok(Skill::Taunt), Var::Throw => Ok(Skill::Throw), // Var::Toxic => Ok(Skill::Toxic), Var::Triage => Ok(Skill::Triage), _ => Err(err_msg("not a usable var")) } } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum ColourCode { RR, GG, BB, RG, BR, GB, } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { pub id: Uuid, pub balance: u16, pub free: Vec, pub bound: Vec, pub instance: Uuid, pub account: Uuid, } impl Vbox { pub fn new(account_id: Uuid, instance_id: Uuid) -> Vbox { Vbox { id: Uuid::new_v4(), account: account_id, instance: instance_id, free: vec![], bound: vec![], balance: 0, } } pub fn fill(&mut self) -> &mut Vbox { let vars = vec![ (Var::Red, 1), (Var::Green, 1), (Var::Blue, 1), (Var::Attack, 1), (Var::Block, 1), (Var::Buff, 1), (Var::Debuff, 1), (Var::Stun, 1), ]; self.free = iter:: repeat_with(|| { let mut rng = thread_rng(); let dist = WeightedIndex::new(vars.iter().map(|item| item.1)).unwrap(); return vars[dist.sample(&mut rng)].0; }) .take(8) .collect::>(); self } pub fn accept(&mut self, i: usize) -> Result<&mut Vbox, Error> { if self.bound.len() >= 9 { return Err(err_msg("too many vars bound")); } self.free.get(i).ok_or(format_err!("no var at index {:?}", i))?; self.bound.push(self.free.remove(i)); Ok(self) } pub fn drop(&mut self, i: usize) -> Result<&mut Vbox, Error> { self.bound.get(i).ok_or(format_err!("no var at index {:?}", i))?; self.bound.remove(i); // balance update Ok(self) } pub fn combine(&mut self, mut indices: Vec) -> 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("var missing index")); } // have to sort the indices and keep track of the iteration // because when removing the elements the array shifts indices.sort(); let mut vars = indices .iter() .enumerate() .map(|(i, index)| { self.bound.remove(*index - i) }) .collect::>(); let base_index = vars .iter() .position(|v| v.is_base()) .ok_or(err_msg("no base item selected"))?; let base = vars.remove(base_index); // fold colours into RGB let colours = vars .iter() .fold([0, 0, 0], |mut acc, c| { match c { Var::Red => acc[0] += 1, Var::Green => acc[1] += 1, Var::Blue => acc[2] += 1, _ => (), }; acc }); let colour_code = match colours { [2,0,0] => ColourCode::RR, [0,2,0] => ColourCode::GG, [0,0,2] => ColourCode::BB, [1,1,0] => ColourCode::RG, [0,1,1] => ColourCode::GB, [1,0,1] => ColourCode::BR, _ => return Err(err_msg("not a combo")), }; let new = match base { Var::Attack => match colour_code { ColourCode::RR => Var::Strike, ColourCode::GG => Var::Heal, ColourCode::BB => Var::Blast, ColourCode::RG => Var::Slay, // ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => Var::Banish, // }, Var::Block => match colour_code { ColourCode::RR => Var::Parry, ColourCode::GG => Var::Reflect, ColourCode::BB => Var::Toxic, ColourCode::RG => Var::Taunt, ColourCode::GB => Var::Shield, ColourCode::BR => return Err(err_msg("unhandled skill combo")), }, Var::Buff => match colour_code { ColourCode::RR => Var::Empower, ColourCode::GG => Var::Triage, ColourCode::BB => Var::Amplify, ColourCode::RG => Var::Survival, ColourCode::GB => return Err(err_msg("unhandled skill combo")), ColourCode::BR => Var::Haste, }, Var::Debuff => match colour_code { ColourCode::RR => Var::Snare, ColourCode::GG => Var::Purge, ColourCode::BB => Var::Curse, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => Var::Siphon, ColourCode::BR => Var::Slow, }, Var::Stun => match colour_code { ColourCode::RR => Var::Strangle, ColourCode::GG => Var::Throw, ColourCode::BB => Var::Ruin, ColourCode::RG => return Err(err_msg("unhandled skill combo")), ColourCode::GB => Var::Silence, ColourCode::BR => Var::Hex, }, _ => panic!("wrong base {:?}", base), }; self.bound.push(new); Ok(self) } } pub fn vbox_discard(params: VboxDiscardParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.fill(); return player_update(tx, player); } pub fn vbox_accept(params: VboxAcceptParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.accept(params.index)?; return player_update(tx, player); } pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.combine(params.indices)?; return player_update(tx, player); } pub fn vbox_drop(params: VboxDropParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; player.vbox.drop(params.index)?; return player_update(tx, player); } pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; let mut cryp = cryp_get(tx, params.cryp_id, account.id)?; let var = player.vbox.bound.remove(params.index); // done here because i teach them a tonne of skills for tests let max_skills = 4; if cryp.skills.len() >= max_skills { return Err(format_err!("cryp at max skills ({:?})", max_skills)); } let skill = var.skill()?; cryp = cryp.learn(skill); cryp_write(cryp, tx)?; return player_update(tx, player); } #[cfg(test)] mod tests { use super::*; #[test] fn combine_test() { let mut vbox = Vbox::new(Uuid::new_v4(), Uuid::new_v4()); vbox.bound = vec![Var::Attack, Var::Green, Var::Green]; vbox.combine(vec![1,2,0]).unwrap(); assert_eq!(vbox.bound[0], Var::Heal); } }