use crate::fegame::FEGame;
use serde::{Deserialize, Serialize};
#[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct CombatStats {
pub dmg: u32,
pub hit: u32,
pub crit: u32,
pub is_brave: bool
}
impl CombatStats {
pub fn possible_outcomes(&self, game: FEGame, outcomes: Vec<Outcome>) -> Vec<Outcome> {
let after_one = self.after_single_strike(game, outcomes);
if self.is_brave {
self.after_single_strike(game, after_one)
} else {
after_one
}
}
fn after_single_strike(&self, game: FEGame, states: Vec<Outcome>) -> Vec<Outcome> {
let mut new_states = vec!();
for state in states {
if state.atk_hp == 0 {
new_states.push(state);
} else {
let prob_hit = game.true_hit(self.hit);
let prob_miss = 1.0 - prob_hit;
let prob_crit = prob_hit * self.crit as f64 / 100.0;
let prob_reg_hit = prob_hit - prob_crit;
new_states.push(Outcome{
prob: state.prob * prob_miss,
atk_hp: state.atk_hp,
def_hp: state.def_hp
});
new_states.push(Outcome{
prob: state.prob * prob_reg_hit,
atk_hp: state.atk_hp,
def_hp: state.def_hp.saturating_sub(self.dmg)
});
new_states.push(Outcome{
prob: state.prob * prob_crit,
atk_hp: state.atk_hp,
def_hp: state.def_hp.saturating_sub(3 * self.dmg)
});
}
}
Outcome::collect(new_states)
}
}
#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum SpeedDiff {
Even,
AtkDoubles,
DefDoubles,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Outcome {
pub prob: f64,
pub atk_hp: u32,
pub def_hp: u32,
}
impl Outcome {
pub fn collect(outcomes: Vec<Outcome>) -> Vec<Outcome> {
outcomes.into_iter().filter(|x| x.prob != 0.0).fold(vec![], |acc, outcome| outcome.add_into(acc))
}
pub fn add_into(&self, outcomes: Vec<Outcome>) -> Vec<Outcome> {
let mut new_outcomes = vec!();
let mut has_added = false;
for outcome in outcomes {
if (self.atk_hp == outcome.atk_hp) && (self.def_hp == outcome.def_hp) {
new_outcomes.push(Outcome{
prob: self.prob + outcome.prob,
atk_hp: self.atk_hp,
def_hp: self.def_hp,
});
has_added = true;
} else {
new_outcomes.push(outcome.clone());
}
}
if !has_added {
new_outcomes.push(self.clone());
}
new_outcomes
}
pub fn switch(&self) -> Outcome {
Outcome{
prob: self.prob,
atk_hp: self.def_hp,
def_hp: self.atk_hp,
}
}
}
pub fn possible_outcomes(game: FEGame, atk: CombatStats, atk_hp: u32,
def: CombatStats, def_hp: u32,
speed: SpeedDiff) -> Vec<Outcome> {
let initial = vec!(Outcome{
prob: 1.0,
atk_hp,
def_hp,
});
let after_atk = atk.possible_outcomes(game, initial);
let after_def = def.possible_outcomes(
game,
after_atk.into_iter().map(|x| x.switch()).collect()
).into_iter().map(|x| x.switch()).collect();
match speed {
SpeedDiff::Even => {
after_def
},
SpeedDiff::AtkDoubles => {
atk.possible_outcomes(game, after_def)
},
SpeedDiff::DefDoubles => {
def.possible_outcomes(
game,
after_def.into_iter().map(|x| x.switch()).collect()
).into_iter().map(|x| x.switch()).collect()
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_outcomes() {
dbg!(Outcome{prob: 1.0, atk_hp: 20, def_hp: 30}.add_into(vec!()));
dbg!(CombatStats{
dmg: 10, hit: 90, crit: 0, is_brave: false,
}.possible_outcomes(FEGame::FE15,
vec![Outcome{prob: 1.0, atk_hp: 1, def_hp: 40}]));
dbg!(possible_outcomes(FEGame::FE15, CombatStats{
dmg: 10, hit: 50, crit: 0, is_brave: false,
}, 30, CombatStats{
dmg: 10, hit: 100, crit: 0, is_brave: false
}, 20, SpeedDiff::AtkDoubles));
}
}