369 lines
10 KiB
Rust
369 lines
10 KiB
Rust
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<Skill, Error> {
|
|
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<Var>,
|
|
pub bound: Vec<Var>,
|
|
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::<Vec<Var>>();
|
|
|
|
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<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] => "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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<u8> = returned.get("data");
|
|
let vbox = from_slice::<Vbox>(&vbox_bytes)?;
|
|
|
|
return Ok(vbox);
|
|
}
|
|
|
|
pub fn vbox_state(params: VboxStateParams, tx: &mut Transaction, account: &Account) -> Result<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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<Vbox, Error> {
|
|
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);
|
|
}
|
|
} |