mnml/server/src/vbox.rs
2019-02-21 15:02:19 +11:00

333 lines
9.6 KiB
Rust

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<Skill, Error> {
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<Var>,
pub bound: Vec<Var>,
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::<Vec<Var>>();
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<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("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::<Vec<Var>>();
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<Player, Error> {
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<Player, Error> {
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<Player, Error> {
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<Player, Error> {
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<Player, Error> {
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);
}
}