use std::iter; use uuid::Uuid; // drops use rand::prelude::*; use rand::{thread_rng}; use rand::distributions::{WeightedIndex}; use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account::Account; use rpc::{VboxStateParams, VboxAcceptParams, VboxDiscardParams, VboxCombineParams, VboxApplyParams, VboxDropParams}; use skill::{Skill}; use cryp::{cryp_get, cryp_write}; #[derive(Debug,Clone,Copy,PartialEq,Eq,Ord,PartialOrd,Serialize,Deserialize)] pub enum Var { Blue, Green, Red, Attack, Block, Stun, Buff, Debuff, Strike, Blast, Heal, Throw, Hex, } 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::Attack => Ok(Skill::Attack), Var::Block => Ok(Skill::Attack), Var::Stun => Ok(Skill::Attack), Var::Buff => Ok(Skill::Attack), Var::Debuff => Ok(Skill::Attack), Var::Strike => Ok(Skill::Attack), Var::Blast => Ok(Skill::Blast), Var::Heal => Ok(Skill::Heal), Var::Throw => Ok(Skill::Throw), Var::Hex => Ok(Skill::Hex), _ => Err(err_msg("not a usable var")) } } } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { pub id: Uuid, pub balance: u16, pub free: Vec, pub bound: Vec, pub game: Uuid, pub account: Uuid, } impl Vbox { pub fn new(account_id: Uuid, game_id: Uuid) -> Vbox { Vbox { id: Uuid::new_v4(), account: account_id, game: game_id, free: vec![], bound: vec![], balance: 0, } } pub fn fill(mut self: Vbox) -> 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() > 8 { return Err(err_msg("too many vars bound")); } println!("{:?}", self.free); 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.free.get(i).ok_or(format_err!("no var at index {:?}", i))?; self.free.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] => "rr", [0,2,0] => "gg", [0,0,2] => "bb", [1,1,0] => "rg", [0,1,1] => "gb", [1,0,1] => "rb", _ => return Err(err_msg("not a combo")), }; let new = match base { Var::Attack => match colour_code { "rr" => Var::Strike, "gg" => Var::Heal, "bb" => Var::Blast, "rg" => Var::Attack, "gb" => Var::Attack, "rb" => Var::Attack, _ => panic!("missing colour code {:?}", colour_code), }, Var::Block => match colour_code { "rr" => Var::Strike, "gg" => Var::Heal, "bb" => Var::Blast, "rg" => Var::Attack, "gb" => Var::Attack, "rb" => Var::Attack, _ => panic!("missing colour code {:?}", colour_code), }, Var::Buff => match colour_code { "rr" => Var::Strike, "gg" => Var::Heal, "bb" => Var::Blast, "rg" => Var::Attack, "gb" => Var::Attack, "rb" => Var::Attack, _ => panic!("missing colour code {:?}", colour_code), }, Var::Debuff => match colour_code { "rr" => Var::Strike, "gg" => Var::Heal, "bb" => Var::Blast, "rg" => Var::Attack, "gb" => Var::Attack, "rb" => Var::Attack, _ => panic!("missing colour code {:?}", colour_code), }, Var::Stun => match colour_code { "rr" => Var::Strike, "gg" => Var::Heal, "bb" => Var::Blast, "rg" => Var::Attack, "gb" => Var::Attack, "rb" => Var::Attack, _ => panic!("missing colour code {:?}", colour_code), }, _ => panic!("wrong base {:?}", base), }; self.bound.push(new); Ok(self) } } pub fn vbox_create(vbox: Vbox, tx: &mut Transaction, account: &Account) -> Result { let vbox_bytes = to_vec(&vbox)?; let query = " INSERT INTO vbox (id, account, game, data) VALUES ($1, $2, $3, $4) RETURNING id; "; let result = tx .query(query, &[&vbox.id, &account.id, &vbox.game, &vbox_bytes])?; result.iter().next().ok_or(format_err!("no vbox written"))?; // println!("{:} wrote vbox", vbox.id); return Ok(vbox); } pub fn vbox_write(vbox: Vbox, tx: &mut Transaction) -> Result { let vbox_bytes = to_vec(&vbox)?; let query = " UPDATE vbox SET data = $1 WHERE id = $2 RETURNING id, account, data; "; let result = tx .query(query, &[&vbox_bytes, &vbox.id])?; let _returned = result.iter().next().expect("no row returned"); // println!("{:?} wrote vbox", vbox.id); return Ok(vbox); } pub fn vbox_get(tx: &mut Transaction, game_id: Uuid, account: &Account) -> Result { let query = " SELECT * FROM vbox WHERE account = $1 AND game = $2; "; let result = tx .query(query, &[&account.id, &game_id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("vbox not found")), }; // tells from_slice to cast into a cryp let vbox_bytes: Vec = returned.get("data"); let vbox = from_slice::(&vbox_bytes)?; return Ok(vbox); } pub fn vbox_state(params: VboxStateParams, tx: &mut Transaction, account: &Account) -> Result { match vbox_get(tx, params.game_id, account) { Ok(v) => Ok(v), Err(e) => { println!("{:?}", e); vbox_create(Vbox::new(account.id, params.game_id).fill(), tx, account) } } } pub fn vbox_discard(params: VboxDiscardParams, tx: &mut Transaction, account: &Account) -> Result { let vbox = vbox_get(tx, params.game_id, account)?; return vbox_write(vbox.fill(), tx); } pub fn vbox_accept(params: VboxAcceptParams, tx: &mut Transaction, account: &Account) -> Result { let mut vbox = vbox_get(tx, params.game_id, account)?; vbox.accept(params.index)?; return vbox_write(vbox, tx); } pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &Account) -> Result { let mut vbox = vbox_get(tx, params.game_id, account)?; vbox.combine(params.indices)?; return vbox_write(vbox, tx); } pub fn vbox_drop(params: VboxDropParams, tx: &mut Transaction, account: &Account) -> Result { let mut vbox = vbox_get(tx, params.game_id, account)?; vbox.drop(params.index)?; return vbox_write(vbox, tx); } pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result { let mut vbox = vbox_get(tx, params.game_id, account)?; let mut cryp = cryp_get(tx, params.cryp_id, account.id)?; let var = 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 vbox_write(vbox, tx); } #[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); } }