43 #if defined(BENCHMARK) || defined(CHECK)
49 #ifdef ATTACK_PREDICTION_DEBUG
50 #define debug(x) printf x
55 #ifdef ATTACK_PREDICTION_DEBUG
61 std::ostringstream ss;
63 ss <<
"==================================";
67 <<
"\n" <<
"is_slowed: " << stats.
is_slowed
68 <<
"\n" <<
"slows: " << stats.
slows
69 <<
"\n" <<
"drains: " << stats.
drains
70 <<
"\n" <<
"petrifies: " << stats.
petrifies
71 <<
"\n" <<
"poisons: " << stats.
poisons
72 <<
"\n" <<
"swarm: " << stats.
swarm
75 <<
"\n" <<
"rounds: " << stats.
rounds
77 <<
"\n" <<
"hp: " << stats.
hp
78 <<
"\n" <<
"max_hp: " << stats.
max_hp
80 <<
"\n" <<
"damage: " << stats.
damage
84 <<
"\n" <<
"num_blows: " << stats.
num_blows
85 <<
"\n" <<
"swarm_min: " << stats.
swarm_min
86 <<
"\n" <<
"swarm_max: " << stats.
swarm_max
89 std::cout << ss.rdbuf() << std::endl;
96 using summary_t = std::array<std::vector<double>, 2>;
115 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes);
116 combat_slice(
const summary_t& src_summary,
unsigned num_strikes);
122 combat_slice::combat_slice(
123 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes)
127 , strikes(num_strikes)
129 if(src_summary[0].empty()) {
136 if(end > src_summary[0].
size()) {
137 end = src_summary[0].size();
141 for(
unsigned i = begin;
i <
end; ++
i) {
142 prob += src_summary[0][
i];
145 if(!src_summary[1].empty()) {
146 for(
unsigned i = begin;
i <
end; ++
i) {
147 prob += src_summary[1][
i];
156 combat_slice::combat_slice(
const summary_t& src_summary,
unsigned num_strikes)
158 , end_hp(src_summary[0].
size())
160 , strikes(num_strikes)
172 unsigned old_strikes = stats.
calc_blows(cur_hp);
176 while(++cur_hp <= stats.
max_hp) {
189 std::vector<combat_slice> split_summary(
192 std::vector<combat_slice> result;
196 result.emplace_back(summary, unit_stats.
num_blows);
200 debug((
"Slicing:\n"));
202 unsigned cur_end = 0;
205 const unsigned cur_begin = cur_end;
206 cur_end = hp_for_next_attack(cur_begin, unit_stats);
209 combat_slice slice(summary, cur_begin, cur_end, unit_stats.
calc_blows(cur_begin));
210 if(slice.prob != 0.0) {
211 result.push_back(slice);
212 debug((
"\t%2u-%2u hp; strikes: %u; probability: %6.2f\n", cur_begin, cur_end, slice.strikes,
213 slice.prob * 100.0));
215 }
while(cur_end <= unit_stats.
max_hp);
231 prob_matrix(
unsigned int a_max,
237 const summary_t& a_initial,
238 const summary_t& b_initial);
241 void shift_cols(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
243 void shift_rows(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
246 void move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col);
248 void move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row);
251 void merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row);
252 void merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row);
253 void merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col);
254 void merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col);
260 void record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed);
263 static unsigned int plane_index(
bool a_slowed,
bool b_slowed)
265 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
269 double prob_of_zero(
bool check_a,
bool check_b)
const;
271 double row_sum(
unsigned plane,
unsigned row)
const;
273 double col_sum(
unsigned plane,
unsigned column)
const;
275 void sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const;
278 bool plane_used(
unsigned p)
const
280 return p < NUM_PLANES && plane_[
p] !=
nullptr;
283 unsigned int num_rows()
const
287 unsigned int num_cols()
const
307 std::unique_ptr<double[]> new_plane()
const;
309 void initialize_plane(
unsigned plane,
312 const std::vector<double>& a_initial,
313 const std::vector<double>& b_initial);
315 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial);
317 double& val(
unsigned plane,
unsigned row,
unsigned col);
318 const double& val(
unsigned plane,
unsigned row,
unsigned col)
const;
321 void xfer(
unsigned dst_plane,
329 void xfer(
unsigned dst_plane,
336 void shift_cols_in_row(
unsigned dst,
339 const std::vector<unsigned>& cols,
345 void shift_rows_in_col(
unsigned dst,
348 const std::vector<unsigned>& rows,
356 const unsigned int rows_, cols_;
357 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
361 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
377 prob_matrix::prob_matrix(
unsigned int a_max,
383 const summary_t& a_initial,
384 const summary_t& b_initial)
392 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
393 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
396 for(
unsigned plane = 0; plane != NUM_PLANES; ++plane) {
397 used_rows_[plane].insert(0u);
398 used_cols_[plane].insert(0u);
402 need_a_slowed = need_a_slowed || !a_initial[1].empty();
403 need_b_slowed = need_b_slowed || !b_initial[1].empty();
406 plane_[NEITHER_SLOWED] = new_plane();
407 plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
408 plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
409 plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ?
nullptr : new_plane();
412 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
414 if(!a_initial[1].empty()) {
415 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
418 if(!b_initial[1].empty()) {
419 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
422 if(!a_initial[1].empty() && !b_initial[1].empty()) {
423 initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
427 if(!a_initial[0].empty()) {
428 debug((
"A has fought before (or is slowed).\n"));
432 if(!b_initial[0].empty()) {
433 debug((
"B has fought before (or is slowed).\n"));
439 std::unique_ptr<double[]> prob_matrix::new_plane()
const
441 const unsigned int size = rows_ * cols_;
442 std::unique_ptr<double[]> res(
new double[
size]);
443 std::fill_n(res.get(),
size, 0);
456 void prob_matrix::initialize_plane(
unsigned plane,
459 const std::vector<double>& a_initial,
460 const std::vector<double>& b_initial)
462 if(!a_initial.empty()) {
463 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
465 for(
unsigned row = 0; row < row_count; ++row) {
466 if(a_initial[row] != 0.0) {
467 used_rows_[plane].insert(row);
468 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
472 used_rows_[plane].insert(a_cur);
474 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
487 void prob_matrix::initialize_row(
488 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
490 if(!b_initial.empty()) {
491 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
493 for(
unsigned col = 0; col < col_count; ++col) {
494 if(b_initial[col] != 0.0) {
495 used_cols_[plane].insert(col);
496 val(plane, row, col) = row_prob * b_initial[col];
501 used_cols_[plane].insert(b_cur);
502 val(plane, row, b_cur) = row_prob;
506 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
510 return plane_[plane][row * cols_ + col];
513 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const
517 return plane_[plane][row * cols_ + col];
524 void prob_matrix::xfer(
unsigned dst_plane,
532 double& src = val(src_plane, row_src, col_src);
534 double diff = src * prob;
537 double& dst = val(dst_plane, row_dst, col_dst);
540 used_rows_[dst_plane].insert(row_dst);
541 used_cols_[dst_plane].insert(col_dst);
545 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
546 src_plane == NEITHER_SLOWED
548 : src_plane == A_SLOWED
550 : src_plane == B_SLOWED
552 : src_plane == BOTH_SLOWED
557 dst_plane == NEITHER_SLOWED
559 : dst_plane == A_SLOWED
561 : dst_plane == B_SLOWED
563 : dst_plane == BOTH_SLOWED
574 void prob_matrix::xfer(
575 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
577 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
581 double& src = val(src_plane, row_src, col_src);
583 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
584 src_plane == NEITHER_SLOWED
586 : src_plane == A_SLOWED
588 : src_plane == B_SLOWED
590 : src_plane == BOTH_SLOWED
594 dst_plane == NEITHER_SLOWED
596 : dst_plane == A_SLOWED
598 : dst_plane == B_SLOWED
600 : dst_plane == BOTH_SLOWED
606 double& dst = val(dst_plane, row_dst, col_dst);
609 used_rows_[dst_plane].insert(row_dst);
610 used_cols_[dst_plane].insert(col_dst);
622 void prob_matrix::shift_cols_in_row(
unsigned dst,
625 const std::vector<unsigned>& cols,
633 int row_i =
static_cast<int>(row);
634 int max_row =
static_cast<int>(rows_) - 1;
640 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
643 int col_i =
static_cast<int>(cols[col_x]);
644 int drain_amount = col_i * drain_percent / 100 + drain_constant;
645 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
646 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
650 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
651 for(; col_x < cols.size(); ++col_x) {
652 xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
662 void prob_matrix::shift_cols(
663 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
665 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
667 if(drain_constant || drain_percent) {
668 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
673 const std::vector<unsigned> rows(used_rows_[src].
begin(), used_rows_[src].
end());
674 const std::vector<unsigned> cols(used_cols_[src].
begin(), used_cols_[src].
end());
680 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
681 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
685 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
686 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
695 void prob_matrix::shift_rows_in_col(
unsigned dst,
698 const std::vector<unsigned>& rows,
706 int col_i =
static_cast<int>(col);
707 int max_col =
static_cast<int>(cols_) - 1;
713 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
716 int row_i =
static_cast<int>(rows[row_x]);
717 int drain_amount = row_i * drain_percent / 100 + drain_constant;
718 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
719 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
723 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
724 for(; row_x < rows.size(); ++row_x) {
725 xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
735 void prob_matrix::shift_rows(
736 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
738 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
740 if(drain_constant || drain_percent) {
741 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
746 const std::vector<unsigned> rows(used_rows_[src].
begin(), used_rows_[src].
end());
747 const std::vector<unsigned> cols(used_cols_[src].
begin(), used_cols_[src].
end());
753 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
754 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
757 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
758 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
766 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
769 for(
const unsigned& row : used_rows_[s_plane]) {
770 xfer(d_plane, s_plane, row, d_col, row, s_col);
777 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
780 for(
const unsigned& col : used_cols_[s_plane]) {
781 xfer(d_plane, s_plane, d_row, col, s_row, col);
789 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
791 auto rows_end = used_rows_[s_plane].end();
792 auto row_it = used_rows_[s_plane].begin();
795 for(++row_it; row_it != rows_end; ++row_it) {
796 xfer(d_plane, s_plane, d_row, col, *row_it, col);
804 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
806 auto rows_end = used_rows_[s_plane].end();
807 auto row_it = used_rows_[s_plane].begin();
810 for(++row_it; row_it != rows_end; ++row_it) {
811 for(
const unsigned& col : used_cols_[s_plane]) {
812 xfer(d_plane, s_plane, d_row, col, *row_it, col);
821 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
823 auto cols_end = used_cols_[s_plane].end();
824 auto col_it = used_cols_[s_plane].begin();
827 for(++col_it; col_it != cols_end; ++col_it) {
828 xfer(d_plane, s_plane, row, d_col, row, *col_it);
836 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
838 auto cols_end = used_cols_[s_plane].end();
840 auto cols_begin = std::next(used_cols_[s_plane].
begin());
843 for(
const unsigned row : used_rows_[s_plane]) {
844 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
845 xfer(d_plane, s_plane, row, d_col, row, *col_it);
855 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
860 if(used_rows_[
p].empty()) {
865 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].
begin(), used_rows_[
p].
end());
866 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
867 for(
unsigned int c = 0u;
c < cols_; ++
c) {
868 plane_[
p][r * cols_ +
c] = 0.0;
872 used_rows_[
p].clear();
873 used_cols_[
p].clear();
879 used_rows_[
p].insert(0u);
880 used_cols_[
p].insert(0u);
887 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
889 assert(a_hp <= rows_);
890 assert(b_hp <= cols_);
891 unsigned int plane = plane_index(a_slowed, b_slowed);
892 ++val(plane, a_hp, b_hp);
893 used_rows_[plane].insert(a_hp);
894 used_cols_[plane].insert(b_hp);
900 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const
904 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
911 for(
const unsigned& row : used_rows_[
p]) {
912 prob += val(
p, row, 0);
918 for(
const unsigned& col : used_cols_[
p]) {
919 prob += val(
p, 0, col);
932 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const
934 if(!plane_used(plane)) {
939 for(
unsigned col : used_cols_[plane]) {
940 sum += val(plane, row, col);
948 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const
950 if(!plane_used(plane)) {
955 for(
unsigned row : used_rows_[plane]) {
956 sum += val(plane, row, column);
966 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const
968 for(
const unsigned& row : used_rows_[plane]) {
969 for(
const unsigned& col : used_cols_[plane]) {
970 const double& prob = val(plane, row, col);
971 row_sums[row] += prob;
972 col_sums[col] += prob;
977 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG)
978 void prob_matrix::dump()
const
980 unsigned int row, col, m;
981 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
983 for(m = 0; m < NUM_PLANES; ++m) {
989 for(row = 0; row < rows_; ++row) {
991 for(col = 0; col < cols_; ++col) {
992 debug((
"%4.3g ", val(m, row, col) * 100));
1000 void prob_matrix::dump()
const
1010 class combat_matrix :
protected prob_matrix
1013 combat_matrix(
unsigned int a_max_hp,
1014 unsigned int b_max_hp,
1017 const summary_t& a_summary,
1018 const summary_t& b_summary,
1021 unsigned int a_damage,
1022 unsigned int b_damage,
1023 unsigned int a_slow_damage,
1024 unsigned int b_slow_damage,
1025 int a_drain_percent,
1026 int b_drain_percent,
1027 int a_drain_constant,
1028 int b_drain_constant);
1030 virtual ~combat_matrix()
1035 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1036 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1038 void forced_levelup_a();
1039 void conditional_levelup_a();
1041 void forced_levelup_b();
1042 void conditional_levelup_b();
1044 using prob_matrix::row_sum;
1045 using prob_matrix::col_sum;
1048 virtual void extract_results(
1049 summary_t& summary_a, summary_t& summary_b)
1054 prob_matrix::dump();
1061 unsigned a_slow_damage_;
1062 int a_drain_percent_;
1063 int a_drain_constant_;
1068 unsigned b_slow_damage_;
1069 int b_drain_percent_;
1070 int b_drain_constant_;
1087 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1088 unsigned int b_max_hp,
1091 const summary_t& a_summary,
1092 const summary_t& b_summary,
1095 unsigned int a_damage,
1096 unsigned int b_damage,
1097 unsigned int a_slow_damage,
1098 unsigned int b_slow_damage,
1099 int a_drain_percent,
1100 int b_drain_percent,
1101 int a_drain_constant,
1102 int b_drain_constant)
1104 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1105 , a_max_hp_(a_max_hp)
1107 , a_damage_(a_damage)
1108 , a_slow_damage_(a_slow_damage)
1109 , a_drain_percent_(a_drain_percent)
1110 , a_drain_constant_(a_drain_constant)
1111 , b_max_hp_(b_max_hp)
1113 , b_damage_(b_damage)
1114 , b_slow_damage_(b_slow_damage)
1115 , b_drain_percent_(b_drain_percent)
1116 , b_drain_constant_(b_drain_constant)
1121 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1123 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1124 if(!plane_used(
p)) {
1129 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1130 if(b_hp > actual_damage) {
1132 move_column(
p,
p, b_hp - actual_damage, 0);
1137 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1139 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1140 if(!plane_used(
p)) {
1145 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1146 if(a_hp > actual_damage) {
1148 move_row(
p,
p, a_hp - actual_damage, 0);
1153 void combat_matrix::forced_levelup_a()
1157 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1159 merge_cols(
p & -2,
p, a_max_hp_);
1164 void combat_matrix::forced_levelup_b()
1168 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1170 merge_rows(
p & -3,
p, b_max_hp_);
1175 void combat_matrix::conditional_levelup_a()
1179 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1181 merge_col(
p & -2,
p, 0, a_max_hp_);
1186 void combat_matrix::conditional_levelup_b()
1190 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1192 merge_row(
p & -3,
p, 0, b_max_hp_);
1202 class probability_combat_matrix :
public combat_matrix
1205 probability_combat_matrix(
unsigned int a_max_hp,
1206 unsigned int b_max_hp,
1209 const summary_t& a_summary,
1210 const summary_t& b_summary,
1213 unsigned int a_damage,
1214 unsigned int b_damage,
1215 unsigned int a_slow_damage,
1216 unsigned int b_slow_damage,
1217 int a_drain_percent,
1218 int b_drain_percent,
1219 int a_drain_constant,
1220 int b_drain_constant);
1223 void receive_blow_b(
double hit_chance);
1225 void receive_blow_a(
double hit_chance);
1228 double dead_prob()
const
1230 return prob_of_zero(
true,
true);
1234 double dead_prob_a()
const
1236 return prob_of_zero(
true,
false);
1240 double dead_prob_b()
const
1242 return prob_of_zero(
false,
true);
1245 void extract_results(
1246 summary_t& summary_a, summary_t& summary_b)
override;
1262 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1263 unsigned int b_max_hp,
1266 const summary_t& a_summary,
1267 const summary_t& b_summary,
1270 unsigned int a_damage,
1271 unsigned int b_damage,
1272 unsigned int a_slow_damage,
1273 unsigned int b_slow_damage,
1274 int a_drain_percent,
1275 int b_drain_percent,
1276 int a_drain_constant,
1277 int b_drain_constant)
1278 : combat_matrix(a_max_hp,
1299 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1302 unsigned src = NUM_PLANES;
1304 if(!plane_used(src)) {
1309 int dst = a_slows_ ? src | 2 : src;
1312 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1314 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1320 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1323 unsigned src = NUM_PLANES;
1325 if(!plane_used(src)) {
1330 int dst = b_slows_ ? src | 1 : src;
1333 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1335 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1339 void probability_combat_matrix::extract_results(
1340 summary_t& summary_a, summary_t& summary_b)
1343 summary_a[0] = std::vector<double>(num_rows());
1344 summary_b[0] = std::vector<double>(num_cols());
1346 if(plane_used(A_SLOWED)) {
1347 summary_a[1] = std::vector<double>(num_rows());
1350 if(plane_used(B_SLOWED)) {
1351 summary_b[1] = std::vector<double>(num_cols());
1354 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1355 if(!plane_used(
p)) {
1360 unsigned dst_a = (
p & 1) ? 1u : 0u;
1362 unsigned dst_b = (
p & 2) ? 1u : 0u;
1363 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1375 class monte_carlo_combat_matrix :
public combat_matrix
1378 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1379 unsigned int b_max_hp,
1382 const summary_t& a_summary,
1383 const summary_t& b_summary,
1386 unsigned int a_damage,
1387 unsigned int b_damage,
1388 unsigned int a_slow_damage,
1389 unsigned int b_slow_damage,
1390 int a_drain_percent,
1391 int b_drain_percent,
1392 int a_drain_constant,
1393 int b_drain_constant,
1394 unsigned int rounds,
1395 double a_hit_chance,
1396 double b_hit_chance,
1397 const std::vector<combat_slice>& a_split,
1398 const std::vector<combat_slice>& b_split,
1399 double a_initially_slowed_chance,
1400 double b_initially_slowed_chance);
1404 void extract_results(
1405 summary_t& summary_a, summary_t& summary_b)
override;
1407 double get_a_hit_probability()
const;
1408 double get_b_hit_probability()
const;
1411 static const unsigned int NUM_ITERATIONS = 5000u;
1413 std::vector<double> a_initial_;
1414 std::vector<double> b_initial_;
1415 std::vector<double> a_initial_slowed_;
1416 std::vector<double> b_initial_slowed_;
1417 std::vector<combat_slice> a_split_;
1418 std::vector<combat_slice> b_split_;
1419 unsigned int rounds_;
1420 double a_hit_chance_;
1421 double b_hit_chance_;
1422 double a_initially_slowed_chance_;
1423 double b_initially_slowed_chance_;
1424 unsigned int iterations_a_hit_ = 0u;
1425 unsigned int iterations_b_hit_ = 0u;
1427 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1428 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1429 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1430 static void scale_probabilities(
1431 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1434 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1435 unsigned int b_max_hp,
1438 const summary_t& a_summary,
1439 const summary_t& b_summary,
1442 unsigned int a_damage,
1443 unsigned int b_damage,
1444 unsigned int a_slow_damage,
1445 unsigned int b_slow_damage,
1446 int a_drain_percent,
1447 int b_drain_percent,
1448 int a_drain_constant,
1449 int b_drain_constant,
1450 unsigned int rounds,
1451 double a_hit_chance,
1452 double b_hit_chance,
1453 const std::vector<combat_slice>& a_split,
1454 const std::vector<combat_slice>& b_split,
1455 double a_initially_slowed_chance,
1456 double b_initially_slowed_chance)
1457 : combat_matrix(a_max_hp,
1476 , a_hit_chance_(a_hit_chance)
1477 , b_hit_chance_(b_hit_chance)
1478 , a_initially_slowed_chance_(a_initially_slowed_chance)
1479 , b_initially_slowed_chance_(b_initially_slowed_chance)
1481 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1482 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1483 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1484 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1489 void monte_carlo_combat_matrix::simulate()
1493 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1498 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1499 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1502 auto a_hp =
static_cast<unsigned int>(rng.
get_random_element(a_initial.begin(), a_initial.end()));
1503 auto b_hp =
static_cast<unsigned int>(rng.
get_random_element(b_initial.begin(), b_initial.end()));
1504 unsigned int a_strikes = calc_blows_a(a_hp);
1505 unsigned int b_strikes = calc_blows_b(b_hp);
1507 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1508 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1512 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1513 damage = std::min(damage, b_hp);
1515 b_slowed |= a_slows_;
1517 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1518 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1532 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1533 damage = std::min(damage, a_hp);
1535 a_slowed |= b_slows_;
1537 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1538 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1551 iterations_a_hit_ += a_hit ? 1 : 0;
1552 iterations_b_hit_ += b_hit ? 1 : 0;
1554 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1562 void monte_carlo_combat_matrix::extract_results(
1563 summary_t& summary_a, summary_t& summary_b)
1566 summary_a[0] = std::vector<double>(num_rows());
1567 summary_b[0] = std::vector<double>(num_cols());
1569 if(plane_used(A_SLOWED)) {
1570 summary_a[1] = std::vector<double>(num_rows());
1573 if(plane_used(B_SLOWED)) {
1574 summary_b[1] = std::vector<double>(num_cols());
1577 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1578 if(!plane_used(
p)) {
1583 unsigned dst_a = (
p & 1) ? 1u : 0u;
1585 unsigned dst_b = (
p & 2) ? 1u : 0u;
1586 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1589 divide_all_elements(summary_a[0],
static_cast<double>(NUM_ITERATIONS));
1590 divide_all_elements(summary_b[0],
static_cast<double>(NUM_ITERATIONS));
1592 if(plane_used(A_SLOWED)) {
1593 divide_all_elements(summary_a[1],
static_cast<double>(NUM_ITERATIONS));
1596 if(plane_used(B_SLOWED)) {
1597 divide_all_elements(summary_b[1],
static_cast<double>(NUM_ITERATIONS));
1601 double monte_carlo_combat_matrix::get_a_hit_probability()
const
1603 return static_cast<double>(iterations_a_hit_) /
static_cast<double>(NUM_ITERATIONS);
1606 double monte_carlo_combat_matrix::get_b_hit_probability()
const
1608 return static_cast<double>(iterations_b_hit_) /
static_cast<double>(NUM_ITERATIONS);
1611 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const
1613 auto it = a_split_.begin();
1614 while(it != a_split_.end() && it->end_hp <= a_hp) {
1618 if(it == a_split_.end()) {
1625 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const
1627 auto it = b_split_.begin();
1628 while(it != b_split_.end() && it->end_hp <= b_hp) {
1632 if(it == b_split_.end()) {
1639 void monte_carlo_combat_matrix::scale_probabilities(
1640 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1642 if(divisor == 0.0) {
1648 if(source.empty()) {
1649 target.resize(singular_hp + 1u, 0.0);
1650 target[singular_hp] = 1.0;
1653 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) { return prob / divisor; });
1656 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1659 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1661 for(
double&
e : vec) {
1669 : hp_dist(u.max_hp + 1, 0.0)
1700 : hp_dist(that.hp_dist)
1701 , untouched(that.untouched)
1712 enum class attack_prediction_mode { probability_calculation, monte_carlo_simulation };
1714 void forced_levelup(std::vector<double>& hp_dist)
1719 auto i = hp_dist.begin();
1721 for(++
i;
i != hp_dist.end(); ++
i) {
1726 hp_dist.back() = 1 - hp_dist.front();
1729 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1734 double scalefactor = 0;
1735 const double chance_to_survive = 1 - hp_dist.front();
1736 if(chance_to_survive > DBL_MIN) {
1737 scalefactor = 1 - kill_prob / chance_to_survive;
1740 auto i = hp_dist.begin();
1742 for(++
i;
i != hp_dist.end(); ++
i) {
1747 hp_dist.back() += kill_prob;
1758 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1760 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1764 prob_touched = std::max(prob_touched, 0.0);
1766 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1770 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1773 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1775 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1779 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1782 const double prob_survive_if_not_hit = 1.0;
1784 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1789 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1792 double prob_debuff = 0.0;
1795 prob_debuff += prob_already_debuffed_not_touched;
1797 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1801 prob_debuff += prob_already_debuffed_touched;
1803 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1810 }
else if(!kill_heals) {
1811 prob_debuff += prob_initially_healthy_touched;
1813 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1820 void round_prob_if_close_to_sure(
double& prob)
1824 }
else if(prob > 1.0 - 1.0e-9) {
1833 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1835 const unsigned size = hp_dist.size();
1838 for(
unsigned i = 0;
i !=
size; ++
i) {
1839 if(hp_dist[
i] != 0.0) {
1856 unsigned int fight_complexity(
unsigned int num_slices,
1857 unsigned int opp_num_slices,
1861 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1862 * ((opp_stats.slows || stats.
is_slowed) ? 2 : 1) * stats.
max_hp * opp_stats.max_hp;
1878 unsigned opp_strikes,
1879 std::vector<double>& hp_dist,
1880 std::vector<double>& opp_hp_dist,
1881 double& self_not_hit,
1882 double& opp_not_hit,
1883 bool levelup_considered)
1889 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1890 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1892 if(opp_hp_dist.empty()) {
1894 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1895 opp_hp_dist[opp_stats.
hp] = 1.0;
1897 for(
unsigned int i = 0;
i < strikes; ++
i) {
1898 for(
int j =
i; j >= 0; j--) {
1899 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1900 double move = opp_hp_dist[src_index] * hit_chance;
1901 opp_hp_dist[src_index] -= move;
1902 opp_hp_dist[src_index - stats.
damage] += move;
1905 opp_not_hit *= 1.0 - hit_chance;
1909 for(
unsigned int i = 0;
i < strikes; ++
i) {
1910 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1911 double move = opp_hp_dist[j] * hit_chance;
1912 opp_hp_dist[j] -= move;
1913 opp_hp_dist[j - stats.
damage] += move;
1915 opp_not_hit *= 1.0 - hit_chance;
1921 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1922 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1924 if(hp_dist.empty()) {
1926 hp_dist = std::vector<double>(stats.
max_hp + 1);
1927 hp_dist[stats.
hp] = 1.0;
1928 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1929 for(
int j =
i; j >= 0; j--) {
1930 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1931 double move = hp_dist[src_index] * opp_hit_chance;
1932 hp_dist[src_index] -= move;
1933 hp_dist[src_index - opp_stats.
damage] += move;
1936 self_not_hit *= 1.0 - opp_hit_chance;
1940 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1941 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1942 double move = hp_dist[j] * opp_hit_chance;
1944 hp_dist[j - opp_stats.
damage] += move;
1947 self_not_hit *= 1.0 - opp_hit_chance;
1951 if(!levelup_considered) {
1956 forced_levelup(hp_dist);
1960 forced_levelup(opp_hp_dist);
1968 unsigned opp_strikes,
1969 std::vector<double>& hp_dist,
1970 std::vector<double>& opp_hp_dist,
1971 double& self_not_hit,
1972 double& opp_not_hit,
1973 bool levelup_considered)
1978 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1982 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1984 if(opp_hp_dist.empty()) {
1985 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1986 if(strikes == 1 && opp_stats.
hp > 0) {
1987 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1988 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
1989 opp_not_hit *= 1.0 - hit_chance;
1991 opp_hp_dist[opp_stats.
hp] = 1.0;
1995 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
1996 double move = opp_hp_dist[
i] * hit_chance;
1997 opp_hp_dist[
i] -= move;
1998 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2001 opp_not_hit *= 1.0 - hit_chance;
2006 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2007 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2009 if(hp_dist.empty()) {
2010 hp_dist = std::vector<double>(stats.
max_hp + 1);
2011 if(opp_strikes == 1 && stats.
hp > 0) {
2012 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2013 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2014 self_not_hit *= 1.0 - opp_hit_chance;
2016 hp_dist[stats.
hp] = 1.0;
2019 if(opp_strikes == 1) {
2020 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2021 double move = hp_dist[
i] * opp_hit_chance;
2023 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2026 self_not_hit *= 1.0 - opp_hit_chance;
2030 if(!levelup_considered) {
2035 forced_levelup(hp_dist);
2037 conditional_levelup(hp_dist, opp_hp_dist[0]);
2041 forced_levelup(opp_hp_dist);
2043 conditional_levelup(opp_hp_dist, hp_dist[0]);
2049 void complex_fight(attack_prediction_mode mode,
2053 unsigned opp_strikes,
2055 summary_t& opp_summary,
2056 double& self_not_hit,
2057 double& opp_not_hit,
2058 bool levelup_considered,
2059 std::vector<combat_slice>
split,
2060 std::vector<combat_slice> opp_split,
2061 double initially_slowed_chance,
2062 double opp_initially_slowed_chance)
2064 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2065 unsigned max_attacks = std::max(strikes, opp_strikes);
2067 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2070 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2076 a_damage = a_slow_damage = opp_stats.
max_hp;
2080 b_damage = b_slow_damage = stats.
max_hp;
2083 const double original_self_not_hit = self_not_hit;
2084 const double original_opp_not_hit = opp_not_hit;
2086 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2087 double self_hit = 0.0;
2088 double opp_hit = 0.0;
2089 double self_hit_unknown = 1.0;
2090 double opp_hit_unknown = 1.0;
2093 std::unique_ptr<combat_matrix> m;
2094 if(mode == attack_prediction_mode::probability_calculation) {
2095 debug((
"Using exact probability calculations.\n"));
2097 probability_combat_matrix* pm =
new probability_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2098 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2099 a_damage, b_damage, a_slow_damage, b_slow_damage,
2104 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2106 debug((
"A strikes\n"));
2107 double b_already_dead = pm->dead_prob_b();
2108 pm->receive_blow_b(hit_chance);
2111 double first_hit = hit_chance * opp_hit_unknown;
2112 opp_hit += first_hit;
2113 opp_hit_unknown -= first_hit;
2114 double both_were_alive = 1.0 - b_already_dead - pm->dead_prob_a();
2115 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2116 self_hit_unknown *= (1.0 - this_hit_killed_b);
2118 if(
i < opp_strikes) {
2119 debug((
"B strikes\n"));
2120 double a_already_dead = pm->dead_prob_a();
2121 pm->receive_blow_a(opp_hit_chance);
2124 double first_hit = opp_hit_chance * self_hit_unknown;
2125 self_hit += first_hit;
2126 self_hit_unknown -= first_hit;
2127 double both_were_alive = 1.0 - a_already_dead - pm->dead_prob_b();
2128 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2129 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2133 debug((
"Combat ends:\n"));
2135 }
while(--rounds && pm->dead_prob() < 0.99);
2137 self_hit = std::min(self_hit, 1.0);
2138 opp_hit = std::min(opp_hit, 1.0);
2140 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2141 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2150 unsigned int plane = plane_index(stats, opp_stats);
2151 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2152 opp_not_hit = original_opp_not_hit * not_hit;
2154 if(opp_stats.
slows) {
2155 unsigned int plane = plane_index(stats, opp_stats);
2156 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2157 self_not_hit = original_self_not_hit * not_hit;
2160 debug((
"Using Monte Carlo simulation.\n"));
2162 monte_carlo_combat_matrix* mcm =
new monte_carlo_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2163 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2164 a_damage, b_damage, a_slow_damage, b_slow_damage,
2166 hit_chance, opp_hit_chance,
split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2170 debug((
"Combat ends:\n"));
2173 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2174 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2185 if(levelup_considered) {
2187 m->forced_levelup_a();
2189 m->conditional_levelup_a();
2193 m->forced_levelup_b();
2195 m->conditional_levelup_b();
2200 m->extract_results(summary, opp_summary);
2210 unsigned opp_strikes,
2212 summary_t& opp_summary,
2213 double& self_not_hit,
2214 double& opp_not_hit,
2215 bool levelup_considered)
2221 && opp_summary[1].empty())
2223 if(strikes <= 1 && opp_strikes <= 1) {
2224 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2225 opp_not_hit, levelup_considered);
2226 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2227 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2228 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2229 opp_not_hit, levelup_considered);
2231 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2232 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2233 std::vector<combat_slice>(), 0.0, 0.0);
2236 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2237 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2238 std::vector<combat_slice>(), 0.0, 0.0);
2247 void init_slice_summary(
2248 std::vector<double>& dst,
const std::vector<double>& src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2255 const unsigned size = src.size();
2262 dst.resize(
size, 0.0);
2263 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2264 dst[
i] = src[
i] / prob;
2272 void merge_slice_summary(std::vector<double>& dst,
const std::vector<double>& src,
double prob)
2274 const unsigned size = src.size();
2277 if(dst.size() <
size) {
2278 dst.resize(
size, 0.0);
2282 for(
unsigned i = 0;
i !=
size; ++
i) {
2283 dst[
i] += src[
i] * prob;
2298 opponent.
fight(*
this, levelup_considered);
2302 #ifdef ATTACK_PREDICTION_DEBUG
2311 complex_fight(opponent, 1);
2312 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2314 opponent.
summary[0] = opp_prev;
2318 double self_not_hit = 1.0;
2319 double opp_not_hit = 1.0;
2322 double self_already_dead =
hp_dist[0];
2323 double opp_already_dead = opponent.
hp_dist[0];
2326 round_prob_if_close_to_sure(
slowed);
2327 round_prob_if_close_to_sure(opponent.
slowed);
2331 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2332 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2334 bool use_monte_carlo_simulation =
2338 if(use_monte_carlo_simulation) {
2341 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2344 }
else if(
split.size() == 1 && opp_split.size() == 1) {
2347 opp_not_hit, levelup_considered);
2350 summary_t summary_result, opp_summary_result;
2356 for(
unsigned s = 0;
s !=
split.size(); ++
s) {
2357 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2358 const double sit_prob =
split[
s].prob * opp_split[
t].prob;
2361 summary_t sit_summary, sit_opp_summary;
2364 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2366 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2371 double sit_self_not_hit = sit_prob;
2372 double sit_opp_not_hit = sit_prob;
2374 do_fight(
u_, opponent.
u_,
split[
s].strikes, opp_split[
t].strikes, sit_summary, sit_opp_summary,
2375 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2378 self_not_hit += sit_self_not_hit;
2379 opp_not_hit += sit_opp_not_hit;
2380 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2381 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2382 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2383 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2388 summary[0].swap(summary_result[0]);
2389 summary[1].swap(summary_result[1]);
2390 opponent.
summary[0].swap(opp_summary_result[0]);
2391 opponent.
summary[1].swap(opp_summary_result[1]);
2396 assert(opponent.
summary[0].size() == opp_res.size());
2397 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2398 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2399 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i];
2403 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2404 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2405 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i];
2417 for(
unsigned int i = 0;
i <
size; ++
i)
2421 if(opponent.
summary[1].empty()) {
2426 for(
unsigned int i = 0;
i <
size; ++
i)
2431 double touched = 1.0 - self_not_hit;
2432 double opp_touched = 1.0 - opp_not_hit;
2444 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2465 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2474 #if defined(BENCHMARK) || defined(CHECK)
2477 static const unsigned int NUM_UNITS = 50;
2479 #ifdef ATTACK_PREDICTION_DEBUG
2482 std::ostringstream ss;
2485 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; "
2501 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2505 ss <<
"firststrike, ";
2508 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2510 std::cout << ss.rdbuf() << std::endl;
2518 #ifdef HUMAN_READABLE
2521 std::ostringstream ss;
2524 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter,
label,
2544 ss <<
"firststrike, ";
2547 std::cout << ss.rdbuf() << std::endl;
2550 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2552 int num_outputs = 0;
2553 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2555 if(num_outputs++ % 6 == 0) {
2561 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2567 #elif defined(CHECK)
2570 std::ostringstream ss;
2592 ss <<
"firststrike, ";
2595 std::cout << ss.rdbuf() << std::endl;
2598 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2600 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2612 void combatant::reset()
2614 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2621 summary[0] = std::vector<double>();
2622 summary[1] = std::vector<double>();
2625 static void run(
unsigned specific_battle)
2627 using std::chrono::duration_cast;
2628 using std::chrono::microseconds;
2633 unsigned int i, j, k, battle = 0;
2634 std::chrono::high_resolution_clock::time_point
start,
end;
2636 for(
i = 0;
i < NUM_UNITS; ++
i) {
2637 unsigned alt =
i + 74;
2640 unsigned max_hp = (
i * 2) % 23 + (
i * 3) % 14 + 25;
2641 unsigned hp = (alt * 5) % max_hp + 1;
2653 list_combatant(*stats[
i],
i + 1);
2656 start = std::chrono::high_resolution_clock::now();
2658 for(
i = 0;
i < NUM_UNITS; ++
i) {
2659 for(j = 0; j < NUM_UNITS; ++j) {
2664 for(k = 0; k < NUM_UNITS; ++k) {
2665 if(
i == k || j == k) {
2670 if(specific_battle && battle != specific_battle) {
2678 u[
i]->print(
"Defender", battle,
i + 1);
2679 u[j]->print(
"Attacker #1", battle, j + 1);
2680 u[k]->print(
"Attacker #2", battle, k + 1);
2689 end = std::chrono::high_resolution_clock::now();
2694 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2695 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2696 printf(
"Time per calc = %li us\n",
static_cast<long>(duration_cast<microseconds>(total).count())
2697 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2699 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2702 for(
i = 0;
i < NUM_UNITS; ++
i) {
2713 int add_to_argv = 4;
2714 int damage = atoi((*argv)[1]);
2715 int num_attacks = atoi((*argv)[2]);
2716 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2717 int hit_chance = atoi((*argv)[4]);
2720 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2721 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2725 char* max = strstr((*argv)[5],
"maxhp=");
2727 max_hp = atoi(max + strlen(
"maxhp="));
2728 if(max_hp < hitpoints) {
2729 PLAIN_LOG <<
"maxhp must be at least hitpoints.";
2734 if(strstr((*argv)[5],
"drain")) {
2736 PLAIN_LOG <<
"WARNING: drain specified without maxhp; assuming uninjured.";
2742 if(strstr((*argv)[5],
"slows")) {
2746 if(strstr((*argv)[5],
"slowed")) {
2750 if(strstr((*argv)[5],
"berserk")) {
2754 if(strstr((*argv)[5],
"firststrike")) {
2758 if(strstr((*argv)[5],
"swarm")) {
2760 PLAIN_LOG <<
"WARNING: swarm specified without maxhp; assuming uninjured.";
2768 *argv += add_to_argv;
2772 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2775 int main(
int argc,
char* argv[])
2782 run(argv[1] ? atoi(argv[1]) : 0);
2786 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" "
2787 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] "
2788 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...";
2792 def_stats = parse_unit(&argv);
2794 for(
i = 0; argv[1] &&
i < 19; ++
i) {
2795 att_stats[
i] = parse_unit(&argv);
2801 for(
i = 0; att[
i]; ++
i) {
2802 debug((
"Fighting next attacker\n"));
2806 def->print(
"Defender", 0, 0);
2807 for(
i = 0; att[
i]; ++
i) {
2808 att[
i]->print(
"Attacker", 0,
i + 1);
2811 for(
i = 0; att[
i]; ++
i) {
2813 delete att_stats[
i];
Various functions that implement attacks and attack calculations.
std::vector< std::string > names
this class does not give synced random results derived classes might do.
static rng & default_instance()
T::difference_type get_random_element(T first, T last)
This helper method selects a random element from a container of floating-point numbers.
bool get_random_bool(double probability)
This helper method returns true with the probability supplied as a parameter.
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
std::string label
What to show in the filter's drop-down list.
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
void clear()
Clear the current render target.
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
bool damage_prediction_allow_monte_carlo_simulation()
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
std::vector< std::string > split(const config_attribute_value &val)
int main(int, char **argv)
Structure describing the statistics of a unit involved in the battle.
bool slows
Attack slows opponent when it hits.
unsigned int num_blows
Effective number of blows, takes swarm into account.
bool petrifies
Attack petrifies opponent when it hits.
int drain_percent
Percentage of damage recovered as health.
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
unsigned int max_experience
bool drains
Attack drains opponent when it hits.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
bool swarm
Attack has swarm special.
bool is_attacker
True if the unit is the attacker.
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
bool is_slowed
True if the unit is slowed at the beginning of the battle.
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp instead of the recorded hp.
unsigned int max_hp
Maximum hitpoints of the unit.
int damage
Effective damage of the weapon (all factors accounted for).
bool poisons
Attack poisons opponent when it hits.
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
int drain_constant
Base HP drained regardless of damage dealt.
bool firststrike
Attack has firststrike special.
double slowed
Resulting chance we are slowed.
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
double poisoned
Resulting chance we are poisoned.
static const unsigned int MONTE_CARLO_SIMULATION_THRESHOLD
const battle_context_unit_stats & u_
std::array< std::vector< double >, 2 > summary
Summary of matrix used to calculate last battle (unslowed & slowed).
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
combatant(const battle_context_unit_stats &u, const combatant *prev=nullptr)
Construct a combatant.
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
static map_location::DIRECTION s