mnml/server/src/player.rs
2019-04-29 18:42:03 +10:00

427 lines
13 KiB
Rust

use uuid::Uuid;
use rand::prelude::*;
use serde_cbor::{from_slice, to_vec};
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
use account::Account;
use cryp::{Cryp, Colours, cryp_get};
use vbox::{Vbox, Var, VarEffect};
use rpc::{PlayerCrypsSetParams};
use instance::{Instance, instance_get, instance_update};
use skill::{Effect};
const DISCARD_COST: u16 = 5;
#[derive(Debug,Clone,Copy,Serialize,Deserialize)]
pub struct Score {
pub wins: u8,
pub losses: u8,
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Player {
pub id: Uuid,
pub name: String,
pub vbox: Vbox,
pub score: Score,
pub cryps: Vec<Cryp>,
pub bot: bool,
pub ready: bool,
pub warnings: u8,
}
impl Player {
pub fn new(account: Uuid, name: &String, cryps: Vec<Cryp>) -> Player {
Player {
id: account,
name: name.clone(),
vbox: Vbox::new(),
score: Score { wins: 0, losses: 0 },
cryps,
bot: false,
ready: false,
warnings: 0,
}
}
pub fn set_bot(mut self, bot: bool) -> Player {
self.bot = bot;
self
}
pub fn set_ready(&mut self, ready: bool) -> &mut Player {
self.ready = ready;
self
}
pub fn add_warning(&mut self) -> &mut Player {
self.warnings += 1;
self
}
pub fn forfeit(&mut self) -> &mut Player {
for cryp in self.cryps.iter_mut() {
cryp.force_ko();
}
self
}
pub fn add_win(&mut self) -> &mut Player {
self.score.wins += 1;
self.set_ready(false);
self.vbox.balance_add(12);
self
}
pub fn add_loss(&mut self) -> &mut Player {
self.score.losses += 1;
self.set_ready(false);
self.vbox.balance_add(9);
self
}
pub fn cryp_get(&mut self, id: Uuid) -> Result<&mut Cryp, Error> {
self.cryps.iter_mut().find(|c| c.id == id).ok_or(err_msg("cryp not found"))
}
pub fn autobuy(&mut self) -> &mut Player {
let mut rng = thread_rng();
// first check if any cryps have no skills
// if there is one find an item in vbox that gives a skill
while let Some(c) = self.cryps.iter().position(|c| c.skills.len() == 0) {
if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) {
let cryp_id = self.cryps[c].id;
self.vbox_apply(s, cryp_id).expect("could not apply");
continue;
}
println!("no skills available...");
}
// now keep buying and applying items cause whynot
// inb4 montecarlo gan
loop {
let (target_cryp_i, target_cryp_id) = match self.cryps.iter().any(|c| c.skills.len() < 3) {
true => {
let mut target_cryp_i = 0;
for (j, c) in self.cryps.iter().enumerate() {
if c.skills.len() < self.cryps[target_cryp_i].skills.len() {
target_cryp_i = j;
}
}
(target_cryp_i, self.cryps[target_cryp_i].id)
},
false => {
let i = rng.gen_range(0, 3);
(i, self.cryps[i].id)
},
};
let needs_skills = self.cryps[target_cryp_i].skills.len() < 3;
let group_i = match needs_skills {
true => 1,
false => 2,
};
let num_colours = self.vbox.bound
.iter()
.filter(|v| [Var::Red, Var::Green, Var::Blue].contains(v))
.count();
if self.vbox.bound.len() < 3 || num_colours < 2 {
if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 {
// println!("insufficient balance");
break;
}
// get 2 colours and something else
if self.vbox.free[0].len() < 2 {
break;
}
self.vbox_accept(0, 0).expect("could't accept colour 0");
self.vbox_accept(0, 0).expect("could't accept colour 1");
self.vbox_accept(group_i, 0).expect("could't accept group 0");
}
// println!("{:?}", self.vbox.bound);
let skills = [Var::Attack, Var::Block, Var::Buff, Var::Debuff, Var::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"),
};
// first 2 colours can be whatever
self.vbox_combine(vec![0, 1, combo_i]).ok();
let var_i = self.vbox.bound.len() - 1;
self.vbox_apply(var_i, target_cryp_id).ok();
}
return self;
}
pub fn vbox_discard(&mut self) -> Result<&mut Player, Error> {
self.vbox.balance_sub(DISCARD_COST)?;
self.vbox.fill();
Ok(self)
}
pub fn vbox_accept(&mut self, group: usize, index: usize) -> Result<&mut Player, Error> {
self.vbox.accept(group, index)?;
Ok(self)
}
pub fn vbox_combine(&mut self, indices: Vec<usize>) -> Result<&mut Player, Error> {
self.vbox.combine(indices)?;
Ok(self)
}
pub fn vbox_reclaim(&mut self, index: usize) -> Result<&mut Player, Error> {
self.vbox.reclaim(index)?;
Ok(self)
}
pub fn vbox_apply(&mut self, index: usize, cryp_id: Uuid) -> Result<&mut Player, Error> {
let var = self.vbox.bound.remove(index);
match var.effect() {
Some(VarEffect::Skill) => {
let skill = var.into_skill().ok_or(format_err!("var {:?} has no associated skill", var))?;
let cryp = self.cryp_get(cryp_id)?;
// done here because i teach them a tonne of skills for tests
let max_skills = 3;
if cryp.skills.len() >= max_skills {
return Err(format_err!("cryp at max skills ({:?})", max_skills));
}
if cryp.knows(skill) {
return Err(format_err!("cryp already knows skill ({:?})" , skill));
}
cryp.learn_mut(skill);
},
Some(VarEffect::Spec) => {
let spec = var.into_spec().ok_or(format_err!("var {:?} has no associated spec", var))?;
let cryp = self.cryp_get(cryp_id)?;
cryp.spec_add(spec)?;
},
None => return Err(err_msg("var has no effect on cryps")),
}
// now the var has been applied
// recalculate the stats of the whole player
let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| {
Colours {
red: tc.red + c.colours.red,
green: tc.green + c.colours.green,
blue: tc.blue + c.colours.blue
}
});
for cryp in self.cryps.iter_mut() {
cryp.apply_modifiers(&player_colours);
}
Ok(self)
}
pub fn vbox_unequip(&mut self, target: Var, cryp_id: Uuid) -> Result<&mut Player, Error> {
if self.vbox.bound.len() >= 9 {
return Err(err_msg("too many vars bound"));
}
match target.effect() {
Some(VarEffect::Skill) => {
let skill = target.into_skill().ok_or(format_err!("var {:?} has no associated skill", target))?;
let cryp = self.cryp_get(cryp_id)?;
cryp.forget(skill)?;
},
Some(VarEffect::Spec) => {
let spec = target.into_spec().ok_or(format_err!("var {:?} has no associated spec", target))?;
let cryp = self.cryp_get(cryp_id)?;
cryp.spec_remove(spec)?;
},
None => return Err(err_msg("var has no effect on cryps")),
}
// now the var has been applied
// recalculate the stats of the whole player
let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| {
Colours {
red: tc.red + c.colours.red,
green: tc.green + c.colours.green,
blue: tc.blue + c.colours.blue
}
});
for cryp in self.cryps.iter_mut() {
cryp.apply_modifiers(&player_colours);
}
self.vbox.bound.push(target);
self.vbox.bound.sort_unstable();
Ok(self)
}
// GAME METHODS
pub fn skills_required(&self) -> usize {
let required = self.cryps.iter()
.filter(|c| !c.is_ko())
.filter(|c| c.available_skills().len() > 0)
.collect::<Vec<&Cryp>>().len();
// println!("{:} requires {:} skills this turn", self.id, required);
return required;
}
pub fn taunting(&self) -> Option<&Cryp> {
self.cryps.iter()
.find(|c| c.affected(Effect::Taunt))
}
pub fn set_cryps(&mut self, mut cryps: Vec<Cryp>) -> &mut Player {
cryps.sort_unstable_by_key(|c| c.id);
self.cryps = cryps;
self
}
pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> {
self.cryps.iter_mut().find(|c| c.id == id)
}
}
pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> Result<Player, Error> {
let query = "
SELECT *
FROM players
WHERE account = $1
AND instance = $2
FOR UPDATE;
";
let result = tx
.query(query, &[&account_id, &instance_id])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("player not found")),
};
// tells from_slice to cast into a cryp
let bytes: Vec<u8> = returned.get("data");
let data = from_slice::<Player>(&bytes)?;
return Ok(data);
}
pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result<Player, Error> {
let player_bytes = to_vec(&player)?;
let query = "
INSERT INTO players (id, instance, account, data)
VALUES ($1, $2, $3, $4)
RETURNING id, account;
";
let result = tx
.query(query, &[&Uuid::new_v4(), &instance, &account.id, &player_bytes])?;
let _returned = result.iter().next().expect("no row written");
println!("wrote player {:} joined instance: {:}", account.name, instance);
return Ok(player);
}
pub fn player_global_update(tx: &mut Transaction, mut player: Player, ignore_phase: bool) -> Result<Player, Error> {
// sort vbox for niceness
player.vbox.bound.sort_unstable();
let bytes = to_vec(&player)?;
let query = "
UPDATE players
SET data = $1, updated_at = now()
WHERE id = $2
RETURNING id, data;
";
let result = tx
.query(query, &[&bytes, &player.id])?;
result.iter().next().ok_or(format_err!("player {:?} could not be written", player))?;
Ok(player)
}
pub fn player_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
let query = "
DELETE
FROM players
WHERE id = $1;
";
let result = tx
.execute(query, &[&id])?;
if result != 1 {
return Err(format_err!("unable to delete player {:?}", id));
}
println!("player deleted {:?}", id);
return Ok(());
}
pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
if params.cryp_ids.len() != 3 {
return Err(err_msg("player size is 3"));
}
let cryps = params.cryp_ids
.iter()
.map(|id| cryp_get(tx, *id, account.id))
.collect::<Result<Vec<Cryp>, Error>>()?;
let player = match player_get(tx, account.id, Uuid::nil()) {
Ok(mut p) => {
p.cryps = cryps;
p.vbox = Vbox::new();
player_global_update(tx, p, false)?
},
Err(_) => {
player_create(tx, Player::new(account.id, &account.name, cryps), Uuid::nil(), &account)?
}
};
let instance = Instance::global(player);
Ok(instance)
}
#[cfg(test)]
mod tests {
use mob::instance_mobs;
use super::*;
#[test]
fn player_bot_vbox_test() {
let player_account = Uuid::new_v4();
let cryps = instance_mobs(player_account);
let mut player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true);
player.vbox.fill();
player.autobuy();
assert!(player.cryps.iter().all(|c| c.skills.len() >= 1));
}
}