1use crate::fegame::FEGame;
7
8use serde::{Deserialize, Serialize};
9
10
11#[derive(Default, Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
14pub struct CombatStats {
15 pub dmg: u32,
17
18 pub hit: u32,
20
21 pub crit: u32,
23
24 pub is_brave: bool
28}
29
30impl CombatStats {
31 pub fn possible_outcomes(&self, game: FEGame, outcomes: Vec<Outcome>) -> Vec<Outcome> {
34 let after_one = self.after_single_strike(game, outcomes);
35 if self.is_brave {
36 self.after_single_strike(game, after_one)
38 } else {
39 after_one
40 }
41 }
42
43 fn after_single_strike(&self, game: FEGame, states: Vec<Outcome>) -> Vec<Outcome> {
47 let mut new_states = vec!();
48 for state in states {
49 if state.atk_hp == 0 {
50 new_states.push(state);
52 } else {
53 let prob_hit = game.true_hit(self.hit);
55 let prob_miss = 1.0 - prob_hit;
56 let prob_crit = prob_hit * self.crit as f64 / 100.0;
57 let prob_reg_hit = prob_hit - prob_crit;
58
59 new_states.push(Outcome{
61 prob: state.prob * prob_miss,
62 atk_hp: state.atk_hp,
63 def_hp: state.def_hp
64 });
65
66 new_states.push(Outcome{
68 prob: state.prob * prob_reg_hit,
69 atk_hp: state.atk_hp,
70 def_hp: state.def_hp.saturating_sub(self.dmg)
71 });
72
73 new_states.push(Outcome{
77 prob: state.prob * prob_crit,
78 atk_hp: state.atk_hp,
79 def_hp: state.def_hp.saturating_sub(3 * self.dmg)
80 });
81 }
82 }
83 Outcome::collect(new_states)
84 }
85}
86
87#[derive(Eq, PartialEq, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
88pub enum SpeedDiff {
91 Even,
93 AtkDoubles,
95 DefDoubles,
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
100pub struct Outcome {
102 pub prob: f64,
103 pub atk_hp: u32,
104 pub def_hp: u32,
105}
106
107impl Outcome {
108 pub fn collect(outcomes: Vec<Outcome>) -> Vec<Outcome> {
112 outcomes.into_iter().filter(|x| x.prob != 0.0).fold(vec![], |acc, outcome| outcome.add_into(acc))
113 }
114
115 pub fn add_into(&self, outcomes: Vec<Outcome>) -> Vec<Outcome> {
118 let mut new_outcomes = vec!();
119 let mut has_added = false;
120 for outcome in outcomes {
121 if (self.atk_hp == outcome.atk_hp) && (self.def_hp == outcome.def_hp) {
122 new_outcomes.push(Outcome{
123 prob: self.prob + outcome.prob,
124 atk_hp: self.atk_hp,
125 def_hp: self.def_hp,
126 });
127 has_added = true;
128 } else {
129 new_outcomes.push(outcome.clone());
130 }
131 }
132 if !has_added {
133 new_outcomes.push(self.clone());
134 }
135 new_outcomes
136 }
137
138 pub fn switch(&self) -> Outcome {
140 Outcome{
141 prob: self.prob,
142 atk_hp: self.def_hp,
143 def_hp: self.atk_hp,
144 }
145 }
146}
147
148
149pub fn possible_outcomes(game: FEGame, atk: CombatStats, atk_hp: u32,
152 def: CombatStats, def_hp: u32,
153 speed: SpeedDiff) -> Vec<Outcome> {
154 let initial = vec!(Outcome{
155 prob: 1.0,
156 atk_hp,
157 def_hp,
158 });
159
160 let after_atk = atk.possible_outcomes(game, initial);
161 let after_def = def.possible_outcomes(
162 game,
163 after_atk.into_iter().map(|x| x.switch()).collect()
164 ).into_iter().map(|x| x.switch()).collect();
165
166 match speed {
167 SpeedDiff::Even => {
168 after_def
170 },
171 SpeedDiff::AtkDoubles => {
172 atk.possible_outcomes(game, after_def)
174 },
175 SpeedDiff::DefDoubles => {
176 def.possible_outcomes(
178 game,
179 after_def.into_iter().map(|x| x.switch()).collect()
180 ).into_iter().map(|x| x.switch()).collect()
181 },
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_outcomes() {
191 dbg!(Outcome{prob: 1.0, atk_hp: 20, def_hp: 30}.add_into(vec!()));
192 dbg!(CombatStats{
193 dmg: 10, hit: 90, crit: 0, is_brave: false,
194 }.possible_outcomes(FEGame::FE15,
195 vec![Outcome{prob: 1.0, atk_hp: 1, def_hp: 40}]));
196 dbg!(possible_outcomes(FEGame::FE15, CombatStats{
197 dmg: 10, hit: 50, crit: 0, is_brave: false,
198 }, 30, CombatStats{
199 dmg: 10, hit: 100, crit: 0, is_brave: false
200 }, 20, SpeedDiff::AtkDoubles));
201 }
202}