use uuid::Uuid; use rand::prelude::*; // Db Commons use serde_cbor::{from_slice, to_vec}; use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; use account::Account; use rpc::{GameAbilityParams, GamePveParams}; use cryp::{Cryp, CrypStat, Stat}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Roll { pub base: u64, pub result: u64, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum AbilityKind { Attack, Block, Heal, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Ability { id: Uuid, kind: AbilityKind, cryp_id: Uuid, roll: Option, target_cryp_id: Option, target_team_id: Uuid, } impl Ability { pub fn new(cryp_id: Uuid, target_team_id: Uuid, kind: AbilityKind) -> Ability { return Ability { id: Uuid::new_v4(), cryp_id, target_cryp_id: None, target_team_id, roll: None, kind, }; } fn roll(&self, c: &Cryp) -> Roll { let mut rng = thread_rng(); let base: u64 = rng.gen(); let stat = match self.kind { AbilityKind::Attack => &c.str, AbilityKind::Block => &c.str, AbilityKind::Heal => &c.int, }; let mut roll = Roll { base, result: base }; // // apply skills // roll = c.skills.iter().fold(roll, |roll, s| s.apply(roll)); // finally combine with stat println!("{:064b} <- finalised", roll.result); roll.result = roll.result & stat.value; println!("{:064b} & <- attribute roll", stat.value); println!("{:064b} = {:?}", roll.result, roll.result); println!(""); return roll; } pub fn resolve(&mut self, cryp: &mut Cryp, target: &mut Cryp) -> &mut Ability { let roll = self.roll(&cryp); println!("{:?} gettin clapped for {:?}", target.name, roll.result); target.hp.reduce(roll.result); self } pub fn set_target(&mut self, cryp_id: Uuid) -> &mut Ability { self.target_cryp_id = Some(cryp_id); self } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Phase { Start, Ability, Target, Damage, Finish, } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Team { id: Uuid, cryps: Vec, abilities: Vec, incoming: Vec, } impl Team { pub fn new(account: Uuid) -> Team { return Team { id: account, cryps: vec![], abilities: vec![], incoming: vec![], }; } pub fn set_cryps(&mut self, cryps: Vec) -> &mut Team { 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 ability_by_id(&mut self, id: Uuid) -> &mut Ability { match self.incoming.iter_mut().find(|a| a.id == id) { Some(a) => a, None => panic!("abiltity not in game"), } } } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Game { pub id: Uuid, pub team_size: usize, pub team_num: usize, pub teams: Vec, pub is_pve: bool, pub phase: Phase, pub log: Vec, } impl Game { pub fn new() -> Game { return Game { id: Uuid::new_v4(), team_size: 0, team_num: 0, teams: vec![], is_pve: true, phase: Phase::Start, log: vec![], }; } pub fn set_team_num(&mut self, size: usize) -> &mut Game { self.team_num = size; self } pub fn set_team_size(&mut self, size: usize) -> &mut Game { self.team_size = size; self } pub fn add_team(&mut self, team: Team) -> &mut Game { if self.teams.len() == self.team_num { panic!("maximum number of teams"); } self.teams.push(team); self } // handle missing team properly pub fn team_by_id(&mut self, id: Uuid) -> &mut Team { match self.teams.iter_mut().find(|t| t.id == id) { Some(t) => t, None => panic!("id not in game {:?}", id), } } pub fn cryp_by_id(&mut self, id: Uuid) -> &mut Cryp { for team in self.teams.iter_mut() { let in_team = team.cryp_by_id(id); if in_team.is_some() { return in_team.unwrap(); } } panic!("cryp not in game"); } fn replace_cryp(&mut self, updated: Cryp) -> &mut Game { for team in self.teams.iter_mut() { if let Some(index) = team.cryps.iter().position(|c| c.id == updated.id) { team.cryps.remove(index); team.cryps.push(updated); break; } } self } pub fn can_start(&self) -> bool { self.teams.len() == self.team_num } pub fn start(&mut self) -> &mut Game { self.ability_phase_start(); self } pub fn ability_phase_start(&mut self) -> &mut Game { if ![Phase::Start, Phase::Damage].contains(&self.phase) { panic!("game not in damage or start phase"); } self.phase = Phase::Ability; for team in self.teams.iter_mut() { team.abilities.clear(); team.incoming.clear(); } self } // abilities can target any team, but we have to check if the caller is the owner of the cryp // and that the cryp has the ability they are trying to add pub fn add_ability(&mut self, team_id: Uuid, cryp_id: Uuid, target_team_id: Uuid, kind: AbilityKind) -> Uuid { let team = self.team_by_id(team_id); match team.cryp_by_id(cryp_id) { Some(c) => c, None => panic!("cryp not in team"), }; let ability = Ability::new(cryp_id, target_team_id, kind); team.abilities.push(ability); return ability.id; } pub fn ability_phase_finished(&self) -> bool { self.teams.iter().all(|t| t.abilities.len() == self.team_size) } // move all abilities into their target team's targets list pub fn targets_phase_start(&mut self) -> &mut Game { if self.phase != Phase::Ability { panic!("game not in ability phase"); } self.phase = Phase::Target; // have to clone the teams because we change them while iterating let teams = self.teams.clone(); for mut team in teams { for ability in team.abilities.iter_mut() { let target_team = self.team_by_id(ability.target_team_id); target_team.incoming.push(*ability); } } self } // targets can only be added by the owner of the team pub fn add_target(&mut self, team_id: Uuid, cryp_id: Uuid, ability_id: Uuid) -> &mut Ability { // whose team is this? let team = self.team_by_id(team_id); // is the target in the team? match team.cryp_by_id(cryp_id) { Some(c) => c, None => panic!("cryp not in team"), }; // set the target let ability = team.ability_by_id(ability_id); ability.set_target(cryp_id) } pub fn target_phase_finished(&self) -> bool { self.teams.iter().all(|t| t.incoming.iter().all(|i| i.target_cryp_id.is_some())) } // requires no input // just do it pub fn damage_phase_start(&mut self) -> &mut Game { if self.phase != Phase::Target { panic!("game not in target phase"); } self.phase = Phase::Damage; self.resolve_abilities(); if self.is_finished() { self.phase = Phase::Finish; return self; } self.ability_phase_start(); self } fn resolve_abilities(&mut self) -> &mut Game { if self.phase != Phase::Damage { panic!("game not in damage phase"); } // sometimes... you just gotta for team in self.teams.clone().iter_mut() { for incoming in team.incoming.clone().iter_mut() { // they better fuckin be there let mut cryp = self.cryp_by_id(incoming.target_cryp_id.unwrap()).clone(); let mut target_cryp = self.cryp_by_id(incoming.target_cryp_id.unwrap()).clone(); incoming.resolve(&mut cryp, &mut target_cryp); self.replace_cryp(target_cryp); } } self } pub fn is_finished(&self) -> bool { self.teams.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) } } // add client function call // check for cryp ability ownership // check for game participation pub fn game_ability(params: GameAbilityParams, tx: &mut Transaction, account: &Account) -> Result { let query = " SELECT * FROM games WHERE id = $1 "; let result = tx .query(query, &[¶ms.game_id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("game not found")), }; // tells from_slice to cast into a cryp let game_bytes: Vec = returned.get("data"); let mut game = from_slice::(&game_bytes)?; game.add_ability(account.id, params.cryp_id, params.target_team_id, params.kind); return game_write(game, tx); } pub fn game_new(game: Game, tx: &mut Transaction) -> Result { let game_bytes = to_vec(&game)?; let query = " INSERT INTO games (id, data) VALUES ($1, $2, $3) RETURNING id, account; "; let result = tx .query(query, &[&game_bytes, &game.id])?; let _returned = result.iter().next().expect("no row returned"); println!("{:?} wrote game", game.id); return Ok(game); } pub fn game_write(game: Game, tx: &mut Transaction) -> Result { let game_bytes = to_vec(&game)?; let query = " UPDATE games SET data = $1 WHERE id = $2 RETURNING id, data; "; let result = tx .query(query, &[&game_bytes, &game.id])?; let _returned = result.iter().next().expect("no row returned"); println!("{:?} wrote game", game.id); return Ok(game); } fn generate_mob(plr: &Cryp) -> Cryp { let mut rng = thread_rng(); // rng panics on min == max let mob_lvl: u8 = match plr.lvl { 1 => 1, _ => rng.gen_range(1, plr.lvl) }; return Cryp::new() .named(&"bamboo basher".to_string()) .level(mob_lvl) .create(); } pub fn game_pve(params: GamePveParams, tx: &mut Transaction, account: &Account) -> Result { let query = " SELECT * FROM cryps WHERE id = $1 AND account = $2; "; let result = tx .query(query, &[¶ms.id, &account.id])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("cryp not found")), }; // tells from_slice to cast into a cryp let cryp_bytes: Vec = returned.get("data"); let plr: Cryp = from_slice::(&cryp_bytes)?; // TEMP if plr.hp.value == 0 { return Err(err_msg("cryp is ko")); // plr.rez(); } let mob = generate_mob(&plr); let mut game = Game::new(); game .set_team_num(2) .set_team_size(1); let mut plr_team = Team::new(account.id); plr_team .set_cryps(vec![plr]); let mut mob_team = Team::new(Uuid::nil()); mob_team .set_cryps(vec![mob]); game .add_team(plr_team) .add_team(mob_team); game.start(); Ok(game) } #[cfg(test)] mod tests { use game::*; use cryp::*; #[test] fn game_test() { let x = Cryp::new() .named(&"pronounced \"creeep\"".to_string()) .level(8) .create(); let x_id = x.id; let y = Cryp::new() .named(&"lemongrass tea".to_string()) .level(8) .create(); let y_id = y.id; let mut game = Game::new(); game .set_team_num(2) .set_team_size(1); let x_team_id = Uuid::new_v4(); let mut x_team = Team::new(x_team_id); x_team .set_cryps(vec![x]); let y_team_id = Uuid::new_v4(); let mut y_team = Team::new(y_team_id); y_team .set_cryps(vec![y]); game .add_team(x_team) .add_team(y_team); assert!(game.can_start()); game.start(); let x_attack_id = game.add_ability(x_team_id, x_id, y_team_id, AbilityKind::Attack); let y_attack_id = game.add_ability(y_team_id, y_id, x_team_id, AbilityKind::Attack); assert!(game.ability_phase_finished()); game.targets_phase_start(); println!("{:?}", game); game.add_target(x_team_id, x_id, y_attack_id); game.add_target(y_team_id, y_id, x_attack_id); assert!(game.target_phase_finished()); game.damage_phase_start(); assert!([Phase::Ability, Phase::Finish].contains(&game.phase)); println!("{:?}", game); return; } }