448 lines
14 KiB
Rust
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);*/
|
|
}
|
|
|
|
} |