1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::collections::HashMap;

use super::{
    character::{Character, CharacterBuilder},
    combat::{Combat, CombatStatistics},
    feat::feat_db::get_feat, string::align_string,
};

#[derive(Default, Debug)]
pub struct DamageTestResult {
    total_rounds: i32,
    statistics: HashMap<i32, CombatStatistics>,
}

impl DamageTestResult {
    pub fn new() -> Self {
        Self::default()
    }

    fn target_ac_result_string(&self, target_ac: i32) -> String {
        let target = self.statistics.get(&target_ac);

        if target.is_none() {
            return "".into();
        }

        let target = target.unwrap();
        let mut string_list: Vec<String> = vec![];

        string_list.push(align_string("TARGET AC", target_ac.to_string()));
        string_list.push("".into());
        string_list.push(target.to_string());
        string_list.push("".into());
        string_list.push(align_string("AVERAGE DAMAGE PER ROUND", format!("{:.2}", target.dmg_dealt.total_dmg() / self.total_rounds)));

        string_list.join("\n")
    }
}

impl ToString for DamageTestResult {
    fn to_string(&self) -> String {
        let mut string_list: Vec<String> = vec![];
        
        let mut ac_list = self.statistics.keys().collect::<Vec<&i32>>();
        ac_list.sort();

        for (i, &&ac) in ac_list.iter().enumerate() {
            string_list.push(self.target_ac_result_string(ac));
            string_list.push("".into());

            if i != ac_list.len() - 1 {
                string_list.push("=".repeat(50));
                string_list.push("".into());
            }
        }

        string_list.join("\n")
    }
}

#[derive(Default)]
pub struct CombatSimulator {
    total_rounds: i32,
}

impl CombatSimulator {
    pub fn new(total_rounds: i32) -> Self {
        Self { total_rounds }
    }

    pub fn begin(&self, attacker: &Character, defender: &Character) -> CombatStatistics {
        let mut statistics = CombatStatistics::new();
        let combat = Combat::new(attacker, defender);

        for _ in 1..=self.total_rounds {
            let round_statistics = combat.resolve_round();

            statistics.total_hits += round_statistics.total_hits;
            statistics.total_misses += round_statistics.total_misses;
            statistics.concealed_attacks += round_statistics.concealed_attacks;
            statistics.epic_dodged_attacks += round_statistics.epic_dodged_attacks;
            statistics.critical_hits += round_statistics.critical_hits;
            statistics.dmg_dealt.add_from(&round_statistics.dmg_dealt);
        }

        statistics
    }

    pub fn damage_test(
        &self,
        attacker: &Character,
        target_ac_list: Vec<i32>,
        target_has_epic_dodge: bool,
    ) -> DamageTestResult {
        let mut result = DamageTestResult::new();

        for target_ac in target_ac_list {
            let mut dummy = CharacterBuilder::standard_dummy(target_ac);

            if target_has_epic_dodge {
                dummy = dummy.add_feat(get_feat("Epic Dodge"));
            }

            let dummy = dummy.build();
            let round_statistics = self.begin(attacker, &dummy);

            result.statistics.insert(target_ac, round_statistics);
        }

        result.total_rounds = self.total_rounds;
        result
    }
}