diff --git a/client/assets/icons/colour.lines.svg b/client/assets/icons/colour.lines.svg new file mode 100644 index 00000000..99cb59ad --- /dev/null +++ b/client/assets/icons/colour.lines.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/client/assets/icons/cross.svg b/client/assets/icons/cross.svg new file mode 100644 index 00000000..cceb5eb6 --- /dev/null +++ b/client/assets/icons/cross.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/client/assets/icons/hourglass.lines.svg b/client/assets/icons/hourglass.lines.svg new file mode 100644 index 00000000..dfeaea61 --- /dev/null +++ b/client/assets/icons/hourglass.lines.svg @@ -0,0 +1,18 @@ + + + + + + diff --git a/client/cryps.css b/client/cryps.css index e6cd22ed..f0033438 100644 --- a/client/cryps.css +++ b/client/cryps.css @@ -412,8 +412,8 @@ header { } .vbox-table td svg { - stroke-width: 1.5px; - height: 95%; + stroke-width: 2px; + height: 96%; vertical-align: text-bottom; } diff --git a/client/src/components/constants.jsx b/client/src/components/constants.jsx index 1d5efddd..23f51b3f 100644 --- a/client/src/components/constants.jsx +++ b/client/src/components/constants.jsx @@ -229,7 +229,7 @@ module.exports = { upgrades: 'combine with 2 red / blue / green', }, - LifeI: { + GreenLifeI: { description: 'Increases life', colours: ['green'], thresholds: [5, 10, 20], diff --git a/client/src/components/svgs/colour.jsx b/client/src/components/svgs/colour.jsx index 02114b34..b8130546 100644 --- a/client/src/components/svgs/colour.jsx +++ b/client/src/components/svgs/colour.jsx @@ -1,31 +1,31 @@ const preact = require('preact'); -module.exports = function triangle(classes) { +module.exports = function vboxColour(classes) { return ( - + ); -} \ No newline at end of file +}; diff --git a/client/src/utils.jsx b/client/src/utils.jsx index 2a5a6535..4d8cff06 100644 --- a/client/src/utils.jsx +++ b/client/src/utils.jsx @@ -92,9 +92,9 @@ const SPECS = { }; const COLOUR_ICONS = { - red: { colour: 'red', caption: 'red', svg: shapes.hexagon }, - blue: { colour: 'blue', caption: 'blue', svg: shapes.hexagon }, - green: { colour: 'green', caption: 'green', svg: shapes.hexagon }, + red: { colour: 'red', caption: 'red', svg: shapes.square }, + blue: { colour: 'blue', caption: 'blue', svg: shapes.square }, + green: { colour: 'green', caption: 'green', svg: shapes.square }, }; module.exports = { diff --git a/server/src/game.rs b/server/src/game.rs index a9480710..44fa0673 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -270,7 +270,13 @@ impl Game { } for (team_id, mob_id, target_id, s) in pve_skills { - self.add_skill(team_id, mob_id, target_id, s).expect("unable to add pve mob skill"); + match self.add_skill(team_id, mob_id, target_id, s) { + Ok(_) => (), + Err(e) => { + println!("{:?}", self.cryp_by_id(mob_id)); + panic!("{:?} unable to add pve mob skill {:?}", e, s); + }, + } } self diff --git a/server/src/instance.rs b/server/src/instance.rs index 4cf3ddfc..c6a2bbec 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -59,7 +59,9 @@ impl Instance { self.players = iter::repeat_with(|| { let bot_id = Uuid::new_v4(); let cryps = instance_mobs(bot_id); - Player::new(bot_id, self.id, &bot_id.to_string(), cryps).set_bot(true) + let mut p = Player::new(bot_id, self.id, &bot_id.to_string(), cryps).set_bot(true); + p.autobuy(); + p }) .take(15) .collect::>(); @@ -199,16 +201,7 @@ impl Instance { fn bot_vbox_phase(&mut self) -> &mut Instance { for bot in self.players.iter_mut().filter(|p| p.bot) { bot.vbox.fill(); - - // paths for what to do - // use a spec - // craft a spec - // craft a skill - // check for cryps w/ no skills - // create a skill() - // find base - // pick 2 random colours - + bot.autobuy(); bot.set_ready(true); } @@ -585,6 +578,7 @@ mod tests { let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); let mut player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); + player.autobuy(); instance.add_player(player.clone()); assert!(instance.can_start()); @@ -605,4 +599,13 @@ mod tests { assert_eq!(instance.rounds.len(), 3); } + + #[test] + fn instance_bot_vbox_test() { + let mut instance = Instance::new(); + let player_account = Uuid::new_v4(); + let cryps = instance_mobs(player_account); + let mut player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); + } + } \ No newline at end of file diff --git a/server/src/mob.rs b/server/src/mob.rs index 96823ecb..95a63ac4 100644 --- a/server/src/mob.rs +++ b/server/src/mob.rs @@ -33,8 +33,8 @@ pub fn generate_mob() -> Cryp { pub fn instance_mobs(team_id: Uuid) -> Vec { iter::repeat_with(|| generate_mob() - .set_account(team_id) - .learn(Skill::Attack)) + .set_account(team_id)) + // .learn(Skill::Attack)) .take(3) .collect::>() } diff --git a/server/src/player.rs b/server/src/player.rs index 73360727..d0e3f4f2 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -1,4 +1,5 @@ use uuid::Uuid; +use rand::prelude::*; use serde_cbor::{from_slice, to_vec}; @@ -8,11 +9,13 @@ use failure::Error; use failure::err_msg; use account::Account; -use cryp::{Cryp, cryp_get}; -use vbox::{Vbox}; +use cryp::{Cryp, Colours, cryp_get}; +use vbox::{Vbox, Var, VarEffect}; use rpc::{PlayerStateParams, PlayerCrypsSetParams}; use instance::{instance_get, instance_update}; +const DISCARD_COST: u16 = 5; + #[derive(Debug,Clone,Copy,Serialize,Deserialize)] pub struct Score { pub wins: u8, @@ -74,6 +77,193 @@ impl Player { 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, + }; + + if self.vbox.bound.len() < 3 { + if (needs_skills && self.vbox.bits < 4) || self.vbox.bits < 5 { + // println!("insufficient balance"); + return self; + } + + // get 2 colours and something else + if self.vbox.free[0].len() < 2 { + return self; + } + 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 + match self.vbox_combine(vec![0, 1, combo_i]) { + Ok(_) => { + // println!("refined {:?}", self.vbox.bound[self.vbox.bound.len() - 1]); + } + Err(e) => println!("{:?}", e), + }; + + let var_i = self.vbox.bound.len() - 1; + match self.vbox_apply(var_i, target_cryp_id) { + Ok(_) => { + // println!("{:?} improved", self.cryps[target_cryp_i].name); + }, + Err(e) => println!("{:?}", e), + } + } + } + + 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) -> 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 team + let team_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(&team_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 team + let team_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(&team_colours); + } + + self.vbox.bound.push(target); + self.vbox.bound.sort_unstable(); + + Ok(self) + } } pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> Result { @@ -191,3 +381,19 @@ pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, a } } } + +#[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, Uuid::new_v4(), &"test".to_string(), cryps).set_bot(true); + + player.autobuy(); + println!("{:#?}", player); + } +} \ No newline at end of file diff --git a/server/src/skill.rs b/server/src/skill.rs index e5745a9d..160446ce 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -1075,23 +1075,23 @@ fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Re } fn purge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { - for (i, ce) in target.effects.clone().iter_mut().enumerate() { - if [Category::BlueBuff, Category::RedBuff].contains(&ce.effect.category()) { - target.effects.remove(i); + while let Some(i) = target.effects + .iter() + .position(|ce| [Category::BlueBuff, Category::RedBuff].contains(&ce.effect.category())) { + let ce = target.effects.remove(i); results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect })); } - } return results; } fn purify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { - for (i, ce) in target.effects.clone().iter_mut().enumerate() { - if [Category::BlueDebuff, Category::RedDebuff].contains(&ce.effect.category()) { - target.effects.remove(i); + while let Some(i) = target.effects + .iter() + .position(|ce| [Category::BlueDebuff, Category::RedDebuff].contains(&ce.effect.category())) { + let ce = target.effects.remove(i); results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect })); } - } return results; } diff --git a/server/src/vbox.rs b/server/src/vbox.rs index de93bc17..2605dafd 100644 --- a/server/src/vbox.rs +++ b/server/src/vbox.rs @@ -101,7 +101,7 @@ pub enum Var { TestSiphon, } -enum VarEffect { +pub enum VarEffect { Skill, Spec, } @@ -167,7 +167,7 @@ impl Var { } } - fn effect(&self) -> Option { + pub fn effect(&self) -> Option { if let Some(_skill) = self.into_skill() { return Some(VarEffect::Skill); } @@ -177,7 +177,7 @@ impl Var { return None; } - fn into_skill(&self) -> Option { + pub fn into_skill(&self) -> Option { match self { Var::Attack => Some(Skill::Attack), Var::Amplify => Some(Skill::Amplify), @@ -215,7 +215,7 @@ impl Var { } } - fn into_spec(&self) -> Option { + pub fn into_spec(&self) -> Option { match *self { Var::Speed => Some(Spec::Speed), Var::RedSpeedI => Some(Spec::RedSpeedI), @@ -255,6 +255,7 @@ impl From for Var { Skill::Blast => Var::Blast, Skill::Block => Var::Block, Skill::Curse => Var::Curse, + Skill::Clutch => Var::Clutch, Skill::Decay => Var::Decay, Skill::Empower => Var::Empower, Skill::Haste => Var::Haste, @@ -363,7 +364,7 @@ fn get_combos() -> Vec { Combo { units: vec![Var::Attack, Var::Blue, Var::Blue], var: Var::Blast }, Combo { units: vec![Var::Attack, Var::Red, Var::Green], var: Var::Purify }, Combo { units: vec![Var::Attack, Var::Green, Var::Blue], var: Var::Decay }, - // Combo { units: vec![Var::Attack, Var::Red, Var::Blue], var: Var::Blast }, + Combo { units: vec![Var::Attack, Var::Red, Var::Blue], var: Var::Blast }, // AAAAAAAAAAAAAAAAAA Combo { units: vec![Var::Damage, Var::Red, Var::Red], var: Var::RedDamageI }, Combo { units: vec![Var::Damage, Var::Green, Var::Green], var: Var::GreenDamageI }, @@ -495,6 +496,7 @@ impl Vbox { // actually move self.bound.push(self.free[i].remove(j)); + self.bound.sort_unstable(); Ok(self) } @@ -535,118 +537,45 @@ impl Vbox { let combo = combos.iter().find(|c| c.units == input).ok_or(err_msg("not a combo"))?; self.bound.push(combo.var); + self.bound.sort_unstable(); Ok(self) } } -const DISCARD_COST: u16 = 5; - pub fn vbox_discard(params: VboxDiscardParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - player.vbox.balance_sub(DISCARD_COST)?; - player.vbox.fill(); + player.vbox_discard()?; return player_update(tx, player, false); } pub fn vbox_accept(params: VboxAcceptParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - player.vbox.accept(params.group, params.index)?; + player.vbox_accept(params.group, params.index)?; return player_update(tx, player, false); } pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - player.vbox.combine(params.indices)?; + player.vbox_combine(params.indices)?; return player_update(tx, player, false); } pub fn vbox_reclaim(params: VboxReclaimParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - player.vbox.reclaim(params.index)?; + player.vbox_reclaim(params.index)?; return player_update(tx, player, false); } pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - let var = player.vbox.bound.remove(params.index); - - match var.effect() { - Some(VarEffect::Skill) => { - let skill = var.into_skill().ok_or(format_err!("var {:?} has no associated skill", var))?; - let cryp = player.cryp_get(params.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)); - } - - cryp.learn_mut(skill); - }, - Some(VarEffect::Spec) => { - let spec = var.into_spec().ok_or(format_err!("var {:?} has no associated spec", var))?; - let cryp = player.cryp_get(params.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 team - let team_colours = player.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 player.cryps.iter_mut() { - cryp.apply_modifiers(&team_colours); - } - + player.vbox_apply(params.index, params.cryp_id)?; return player_update(tx, player, false); } pub fn vbox_unequip(params: VboxUnequipParams, tx: &mut Transaction, account: &Account) -> Result { let mut player = player_get(tx, account.id, params.instance_id)?; - - if player.vbox.bound.len() >= 9 { - return Err(err_msg("too many vars bound")); - } - - println!("{:?}", params); - - match params.target.effect() { - Some(VarEffect::Skill) => { - let skill = params.target.into_skill().ok_or(format_err!("var {:?} has no associated skill", params.target))?; - let cryp = player.cryp_get(params.cryp_id)?; - cryp.forget(skill)?; - }, - Some(VarEffect::Spec) => { - let spec = params.target.into_spec().ok_or(format_err!("var {:?} has no associated spec", params.target))?; - let cryp = player.cryp_get(params.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 team - let team_colours = player.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 player.cryps.iter_mut() { - cryp.apply_modifiers(&team_colours); - } - - player.vbox.bound.push(params.target); + player.vbox_unequip(params.target, params.cryp_id)?; return player_update(tx, player, false); }