mnml/core/src/player.rs
2021-03-20 12:53:28 +10:00

510 lines
16 KiB
Rust

use std::collections::{HashMap};
use uuid::Uuid;
use failure::Error;
use failure::err_msg;
use construct::{Construct, Colours};
use vbox::{Vbox, ItemType, VboxIndices};
use item::{Item, ItemEffect, get_combos};
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 new(account: Uuid, img: Option<Uuid>, name: &String, mut constructs: Vec<Construct>) -> Player {
constructs.sort_unstable_by_key(|c| c.id);
Player {
id: account,
img,
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();
// skill buying phase
while self.constructs.iter().any(|c| c.skills.len() < 3) {
// find the construct with the smallest number of skills
let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) {
None => panic!("no constructs in autobuy"),
Some(c) => c.id,
};
let i = self.vbox.stash.iter()
.find(|(_i, v)| v.into_skill().is_some())
.map(|(i, _v)| i.clone());
// got a skill in stash
if let Some(i) = i {
// AAAAAAAAAAAAAAAAAAAA
// there's a bad bug here where if this apply fails
// the item in question will be silently dropped
let item = self.vbox.stash.remove(&i).unwrap();
self.vbox_apply(item, construct_id).ok();
continue;
}
// need to buy one
else {
// do we have any colours in store?
let colours = self.vbox.store[&ItemType::Colours].keys()
.cloned()
.take(2)
.collect::<Vec<String>>();
// how about a base skill?
let base = match self.vbox.store[&ItemType::Skills].iter().next() {
Some(b) => Some(b.0.clone()),
None => None,
};
// if no: try to refill and start again
match colours.len() != 2 || base.is_none() {
true => {
match self.vbox_refill() {
Ok(_) => continue,
Err(_) => break, // give up
};
}
false => {
let mut vbox_items = HashMap::new();
vbox_items.insert(ItemType::Colours, colours);
vbox_items.insert(ItemType::Skills, vec![base.unwrap()]);
match self.vbox_combine(vec![], Some(vbox_items)) {
Ok(_) => continue,
Err(_) => break, // give up
}
}
}
}
}
// spec buying phase
while self.constructs.iter().any(|c| c.specs.len() < 3) {
// find the construct with the smallest number of skills
let construct_id = match self.constructs.iter().min_by_key(|c| c.specs.len()) {
None => panic!("no constructs in autobuy"),
Some(c) => c.id,
};
let i = self.vbox.stash.iter()
.find(|(_i, v)| v.into_spec().is_some())
.map(|(i, _v)| i.clone());
// got a skill in stash
if let Some(i) = i {
// AAAAAAAAAAAAAAAAAAAA
// there's a bad bug here where if this apply fails
// the item in question will be silently dropped
let item = self.vbox.stash.remove(&i).unwrap();
self.vbox_apply(item, construct_id).unwrap();
continue;
}
// need to buy one
else {
// do we have any colours in store?
let colours = self.vbox.store[&ItemType::Colours].keys()
.cloned()
.take(2)
.collect::<Vec<String>>();
// how about a base spec?
let base = match self.vbox.store[&ItemType::Specs].iter().next() {
Some(b) => Some(b.0.clone()),
None => None,
};
// if no: try to refill and start again
match colours.len() != 2 || base.is_none() {
true => match self.vbox_refill() {
Ok(_) => continue,
Err(_) => break, // give up
},
false => {
let mut vbox_items = HashMap::new();
vbox_items.insert(ItemType::Colours, colours);
vbox_items.insert(ItemType::Specs, vec![base.unwrap()]);
match self.vbox_combine(vec![], Some(vbox_items)) {
Ok(_) => continue,
Err(_) => break, // give up
}
}
}
}
}
// upgrading phase
// NYI
return self;
}
pub fn vbox_refill(&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: ItemType) -> Result<&mut Player, Error> {
let item = self.vbox.bot_buy(group)?;
self.vbox.stash_add(item, None)?;
Ok(self)
}
pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option<Uuid>) -> Result<&mut Player, Error> {
let item = self.vbox.buy(group, &index)?;
match construct_id {
Some(id) => { self.vbox_apply(item, id)?; },
None => { self.vbox.stash_add(item, None)?; },
};
Ok(self)
}
pub fn vbox_combine(&mut self, inv_indices: Vec<String>, vbox_indices: VboxIndices) -> Result<&mut Player, Error> {
self.vbox.combine(inv_indices, vbox_indices)?;
Ok(self)
}
pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> {
self.vbox.refund(index)?;
Ok(self)
}
pub fn vbox_equip(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> {
let item = self.vbox.stash.remove(&index)
.ok_or(format_err!("no item at index {:?} {:}", self, &index))?;
self.vbox_apply(item, construct_id)
}
pub fn vbox_apply(&mut self, item: Item, construct_id: Uuid) -> Result<&mut Player, Error> {
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)?;
construct.skills = vec![];
construct.learn_mut(skill);
// // done here because i teach them a tonne of skills for tests
// let max_skills = 1;
// 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")),
}
let construct = self.construct_get(construct_id)?;
// force 1 skill for auto
let mut colour_counts = vec![
(Item::Red, construct.colours.red),
(Item::Green, construct.colours.green),
(Item::Blue, construct.colours.blue),
];
colour_counts.sort_unstable_by_key(|cc| cc.1);
colour_counts.reverse();
println!("{:?}", colour_counts);
let total = (construct.colours.red + construct.colours.green + construct.colours.blue) as f64;
let colour_pcts = colour_counts.iter_mut()
.map(|cc| (cc.0, cc.1 as f64 / total))
.collect::<Vec<_>>();
println!("{:?}", colour_pcts);
let skill_item = match item.into_skill().is_some() {
true => item,
false => construct.skills[0].skill.base(),
};
let colour_skill = {
// no colours
if colour_pcts[0].1.is_infinite() {
construct.skills[0].skill
} else {
let mut skill_item_combo = {
if colour_pcts[0].1 > 0.75 {
vec![skill_item, colour_pcts[0].0, colour_pcts[0].0]
} else if colour_pcts[1].1 > 0.4 {
vec![skill_item, colour_pcts[0].0, colour_pcts[1].0]
} else {
// special triple skill_item
vec![skill_item, colour_pcts[0].0, colour_pcts[1].0]
}
};
skill_item_combo.sort_unstable();
println!("{:?}", skill_item_combo);
let combos = get_combos();
let combo = combos.iter().find(|c| c.components == skill_item_combo)
.ok_or(err_msg("no combo for colour skill"))?;
combo.item.into_skill()
.ok_or(format_err!("item {:?} has no associated skill", combo.item))?
}
};
// unlearn everything
construct.skills = vec![];
construct.learn_mut(colour_skill);
// // 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
// }
// });
let player_colours = Colours { red: 0, blue: 0, green: 0 };
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, construct: Option<Uuid>) -> Result<&mut Player, Error> {
if self.vbox.stash.len() >= 9 && !construct.is_some() {
return Err(err_msg("too many items stash"));
}
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);
}
match construct {
Some(cid) => { self.vbox_apply(target, cid)?; },
None => { self.vbox.stash_add(target, None)?; },
};
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)
}
}
#[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, None, &"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, None, &"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);*/
}
}