mnml/server/src/player.rs

448 lines
14 KiB
Rust

use uuid::Uuid;
use rand::prelude::*;
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
use account;
use account::Account;
use construct::{Construct, Colours};
use vbox::{Vbox};
use item::{Item, ItemEffect};
use effect::{Effect};
const DISCARD_COST: usize = 2;
#[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq)]
pub enum Score {
Zero,
One,
Two,
Three,
Adv,
Win,
Lose,
}
impl Score {
pub fn add_win(self, _opp: &Score) -> Score {
match self {
Score::Zero => Score::One,
Score::One => Score::Two,
Score::Two => Score::Win,
// Tennis scoring
// Score::Three => match opp {
// Score::Adv => Score::Three,
// Score::Three => Score::Adv,
// _ => Score::Win,
// }
// Score::Adv => Score::Win,
_ => panic!("faulty score increment {:?}", self),
}
}
pub fn add_loss(self) -> Score {
match self {
// Score::Adv => Score::Three,
_ => self,
}
}
}
#[derive(Debug,Clone,Serialize,Deserialize)]
pub struct Player {
pub id: Uuid,
pub img: Option<Uuid>,
pub name: String,
pub vbox: Vbox,
pub constructs: Vec<Construct>,
pub bot: bool,
pub ready: bool,
pub draw_offered: bool,
pub score: Score,
}
impl Player {
pub fn from_account(tx: &mut Transaction, account: &Account) -> Result<Player, Error> {
let constructs = account::team(tx, account)?;
let img = match account.subscribed {
true => Some(account.img),
false => None,
};
Ok(Player {
id: account.id,
img,
name: account.name.clone(),
vbox: Vbox::new(),
constructs,
bot: false,
ready: false,
draw_offered: false,
score: Score::Zero,
})
}
pub fn new(account: Uuid, name: &String, constructs: Vec<Construct>) -> Player {
Player {
id: account,
img: Some(account),
name: name.clone(),
vbox: Vbox::new(),
constructs,
bot: false,
ready: false,
draw_offered: false,
score: Score::Zero,
}
}
pub fn redact(mut self, account: Uuid) -> Player {
// all g
if account == self.id {
return self;
}
// remove vbox
self.vbox = Vbox::new();
// hide skills
for construct in self.constructs.iter_mut() {
construct.skills = vec![];
construct.specs = vec![];
}
self
}
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 forfeit(&mut self) -> &mut Player {
for construct in self.constructs.iter_mut() {
construct.force_ko();
}
self
}
pub fn set_win(&mut self) -> &mut Player {
self.score = Score::Win;
self
}
pub fn set_lose(&mut self) -> &mut Player {
self.score = Score::Lose;
self
}
pub fn construct_get(&mut self, id: Uuid) -> Result<&mut Construct, Error> {
self.constructs.iter_mut().find(|c| c.id == id).ok_or(err_msg("construct not found"))
}
pub fn autobuy(&mut self) -> &mut Player {
let mut rng = thread_rng();
// first check if any constructs have no skills
// if there is one find an item in vbox that gives a skill
while let Some(c) = self.constructs.iter().position(|c| c.skills.len() == 0) {
if let Some(s) = self.vbox.bound.iter().position(|v| v.into_skill().is_some()) {
let construct_id = self.constructs[c].id;
self.vbox_apply(s, construct_id).expect("could not apply");
continue;
}
info!("no skills available...");
}
// now keep buying and applying items cause whynot
// inb4 montecarlo gan
loop {
let (target_construct_i, target_construct_id) = match self.constructs.iter().any(|c| c.skills.len() < 3) {
true => {
let mut target_construct_i = 0;
for (j, c) in self.constructs.iter().enumerate() {
if c.skills.len() < self.constructs[target_construct_i].skills.len() {
target_construct_i = j;
}
}
(target_construct_i, self.constructs[target_construct_i].id)
},
false => {
let i = rng.gen_range(0, 3);
(i, self.constructs[i].id)
},
};
let needs_skills = self.constructs[target_construct_i].skills.len() < 3;
let group_i = match needs_skills {
true => 1,
false => 2,
};
let num_colours = self.vbox.bound
.iter()
.filter(|v| [Item::Red, Item::Green, Item::Blue].contains(v))
.count();
if self.vbox.bound.len() < 3 || num_colours < 2 {
if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 {
// info!("insufficient balance");
break;
}
// get 2 colours and something else
let free_colours = self.vbox.free[0].iter().fold(0, |count, item| {
match item.is_some() {
true => count + 1,
false => count
}
});
if free_colours < 2 {
break;
}
self.bot_vbox_accept(0).expect("could't accept colour item");
self.bot_vbox_accept(0).expect("could't accept colour item");
self.bot_vbox_accept(group_i).expect("could't accept group item");
}
// info!("{:?}", self.vbox.bound);
let skills = [Item::Attack, Item::Block, Item::Buff, Item::Debuff, Item::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 item_i = self.vbox.bound.len() - 1;
self.vbox_apply(item_i, target_construct_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 bot_vbox_accept(&mut self, group: usize) -> Result<&mut Player, Error> {
self.vbox.bot_accept(group)?;
Ok(self)
}
pub fn vbox_accept(&mut self, group: usize, index: usize, construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
self.vbox.accept(group, index, construct_id)?;
if construct_id.is_some() {
let equip_index = self.vbox.bound.len() - 1;
self.vbox_apply(equip_index, construct_id.expect("no construct"))?;
}
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, construct_id: Uuid) -> Result<&mut Player, Error> {
if self.vbox.bound.get(index).is_none() {
return Err(format_err!("no item at index {:?}", index));
}
let item = self.vbox.bound.remove(index);
match item.effect() {
Some(ItemEffect::Skill) => {
let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?;
let construct = self.construct_get(construct_id)?;
// done here because i teach them a tonne of skills for tests
let max_skills = 3;
if construct.skills.len() >= max_skills {
return Err(format_err!("construct at max skills ({:?})", max_skills));
}
if construct.knows(skill) {
return Err(format_err!("construct already knows skill ({:?})" , skill));
}
construct.learn_mut(skill);
},
Some(ItemEffect::Spec) => {
let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?;
let construct = self.construct_get(construct_id)?;
construct.spec_add(spec)?;
},
None => return Err(err_msg("item has no effect on constructs")),
}
// now the item has been applied
// recalculate the stats of the whole player
let player_colours = self.constructs.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 construct in self.constructs.iter_mut() {
construct.apply_modifiers(&player_colours);
}
Ok(self)
}
pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
if self.vbox.bound.len() >= 9 && !target_construct_id.is_some() {
return Err(err_msg("too many items bound"));
}
match target.effect() {
Some(ItemEffect::Skill) => {
let skill = target.into_skill().ok_or(format_err!("item {:?} has no associated skill", target))?;
let construct = self.construct_get(construct_id)?;
construct.forget(skill)?;
},
Some(ItemEffect::Spec) => {
let spec = target.into_spec().ok_or(format_err!("item {:?} has no associated spec", target))?;
let construct = self.construct_get(construct_id)?;
construct.spec_remove(spec)?;
},
None => return Err(err_msg("item has no effect on constructs")),
}
// now the item has been applied
// recalculate the stats of the whole player
let player_colours = self.constructs.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 construct in self.constructs.iter_mut() {
construct.apply_modifiers(&player_colours);
}
self.vbox.bound.push(target);
if target_construct_id.is_some() {
let equip_index = self.vbox.bound.len() - 1;
self.vbox_apply(equip_index, target_construct_id.expect("no construct"))?;
}
// self.vbox.bound.sort_unstable();
Ok(self)
}
// GAME METHODS
pub fn skills_required(&self) -> usize {
let required = self.constructs.iter()
.filter(|c| !c.is_ko())
.filter(|c| c.available_skills().len() > 0)
.collect::<Vec<&Construct>>().len();
// info!("{:} requires {:} skills this turn", self.id, required);
return required;
}
pub fn intercepting(&self) -> Option<&Construct> {
self.constructs.iter()
.find(|c| c.affected(Effect::Intercept))
}
pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> {
self.constructs.iter_mut().find(|c| c.id == id)
}
}
pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result<Player, Error> {
let query = "
INSERT INTO players (id, instance, account)
VALUES ($1, $2, $3)
RETURNING id, account;
";
let result = tx
.query(query, &[&Uuid::new_v4(), &instance, &account.id])?;
let _returned = result.iter().next().expect("no row written");
info!("wrote player {:} joined instance: {:}", account.name, instance);
return Ok(player);
}
#[cfg(test)]
mod tests {
use mob::instance_mobs;
use super::*;
#[test]
fn player_bot_vbox_test() {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
player.vbox.fill();
player.autobuy();
assert!(player.constructs.iter().all(|c| c.skills.len() >= 1));
}
#[test]
fn player_score_test() {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
player.score = player.score.add_win(&Score::Zero);
player.score = player.score.add_win(&Score::Zero);
player.score = player.score.add_win(&Score::Zero);
assert_eq!(player.score, Score::Win); // 40 / 0
// Bo7 tennis scoring
/*assert_eq!(player.score, Score::Three); // 40 / 0
player.score = player.score.add_loss(); // adv -> deuce
assert_eq!(player.score, Score::Three);
player.score = player.score.add_loss(); // adv -> deuce
assert_eq!(player.score, Score::Three);
player.score = player.score.add_win(&Score::Adv); // opp adv -> stays deuce
assert_eq!(player.score, Score::Three);
player.score = player.score.add_win(&Score::Three);
assert_eq!(player.score, Score::Adv);
player.score = player.score.add_win(&Score::Three);
assert_eq!(player.score, Score::Win);*/
}
}