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 return std::make_unique<double[]>(std::size_t{rows_ * cols_});
453 void prob_matrix::initialize_plane(
unsigned plane,
456 const std::vector<double>& a_initial,
457 const std::vector<double>& b_initial)
459 if(!a_initial.empty()) {
460 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
462 for(
unsigned row = 0; row < row_count; ++row) {
463 if(a_initial[row] != 0.0) {
464 used_rows_[plane].insert(row);
465 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
469 used_rows_[plane].insert(a_cur);
471 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
484 void prob_matrix::initialize_row(
485 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
487 if(!b_initial.empty()) {
488 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
490 for(
unsigned col = 0; col < col_count; ++col) {
491 if(b_initial[col] != 0.0) {
492 used_cols_[plane].insert(col);
493 val(plane, row, col) = row_prob * b_initial[col];
498 used_cols_[plane].insert(b_cur);
499 val(plane, row, b_cur) = row_prob;
503 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
507 return plane_[plane][row * cols_ + col];
510 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const
514 return plane_[plane][row * cols_ + col];
521 void prob_matrix::xfer(
unsigned dst_plane,
529 double&
src = val(src_plane, row_src, col_src);
531 double diff =
src * prob;
534 double&
dst = val(dst_plane, row_dst, col_dst);
537 used_rows_[dst_plane].insert(row_dst);
538 used_cols_[dst_plane].insert(col_dst);
542 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
543 src_plane == NEITHER_SLOWED
545 : src_plane == A_SLOWED
547 : src_plane == B_SLOWED
549 : src_plane == BOTH_SLOWED
554 dst_plane == NEITHER_SLOWED
556 : dst_plane == A_SLOWED
558 : dst_plane == B_SLOWED
560 : dst_plane == BOTH_SLOWED
571 void prob_matrix::xfer(
572 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
574 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
578 double&
src = val(src_plane, row_src, col_src);
580 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n",
src,
581 src_plane == NEITHER_SLOWED
583 : src_plane == A_SLOWED
585 : src_plane == B_SLOWED
587 : src_plane == BOTH_SLOWED
591 dst_plane == NEITHER_SLOWED
593 : dst_plane == A_SLOWED
595 : dst_plane == B_SLOWED
597 : dst_plane == BOTH_SLOWED
603 double&
dst = val(dst_plane, row_dst, col_dst);
606 used_rows_[dst_plane].insert(row_dst);
607 used_cols_[dst_plane].insert(col_dst);
619 void prob_matrix::shift_cols_in_row(
unsigned dst,
622 const std::vector<unsigned>& cols,
630 int row_i =
static_cast<int>(row);
631 int max_row =
static_cast<int>(rows_) - 1;
637 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
640 int col_i =
static_cast<int>(cols[col_x]);
641 int drain_amount = col_i * drain_percent / 100 + drain_constant;
642 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
643 xfer(
dst,
src, newrow, 0, row, cols[col_x], prob);
647 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
648 for(; col_x < cols.size(); ++col_x) {
649 xfer(
dst,
src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
659 void prob_matrix::shift_cols(
660 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
662 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
664 if(drain_constant || drain_percent) {
665 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
670 const std::vector<unsigned> rows(used_rows_[
src].
begin(), used_rows_[
src].
end());
671 const std::vector<unsigned> cols(used_cols_[
src].
begin(), used_cols_[
src].
end());
677 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
678 shift_cols_in_row(
dst,
src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
682 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
683 shift_cols_in_row(
dst,
src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
692 void prob_matrix::shift_rows_in_col(
unsigned dst,
695 const std::vector<unsigned>& rows,
703 int col_i =
static_cast<int>(col);
704 int max_col =
static_cast<int>(cols_) - 1;
710 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
713 int row_i =
static_cast<int>(rows[row_x]);
714 int drain_amount = row_i * drain_percent / 100 + drain_constant;
715 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
716 xfer(
dst,
src, 0, newcol, rows[row_x], col, prob);
720 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
721 for(; row_x < rows.size(); ++row_x) {
722 xfer(
dst,
src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
732 void prob_matrix::shift_rows(
733 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
735 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
737 if(drain_constant || drain_percent) {
738 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
743 const std::vector<unsigned> rows(used_rows_[
src].
begin(), used_rows_[
src].
end());
744 const std::vector<unsigned> cols(used_cols_[
src].
begin(), used_cols_[
src].
end());
750 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
751 shift_rows_in_col(
dst,
src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
754 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
755 shift_rows_in_col(
dst,
src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
763 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
766 for(
const unsigned& row : used_rows_[s_plane]) {
767 xfer(d_plane, s_plane, row, d_col, row, s_col);
774 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
777 for(
const unsigned& col : used_cols_[s_plane]) {
778 xfer(d_plane, s_plane, d_row, col, s_row, col);
786 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
788 auto rows_end = used_rows_[s_plane].end();
789 auto row_it = used_rows_[s_plane].begin();
792 for(++row_it; row_it != rows_end; ++row_it) {
793 xfer(d_plane, s_plane, d_row, col, *row_it, col);
801 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
803 auto rows_end = used_rows_[s_plane].end();
804 auto row_it = used_rows_[s_plane].begin();
807 for(++row_it; row_it != rows_end; ++row_it) {
808 for(
const unsigned& col : used_cols_[s_plane]) {
809 xfer(d_plane, s_plane, d_row, col, *row_it, col);
818 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
820 auto cols_end = used_cols_[s_plane].end();
821 auto col_it = used_cols_[s_plane].begin();
824 for(++col_it; col_it != cols_end; ++col_it) {
825 xfer(d_plane, s_plane, row, d_col, row, *col_it);
833 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
835 auto cols_end = used_cols_[s_plane].end();
837 auto cols_begin = std::next(used_cols_[s_plane].
begin());
840 for(
const unsigned row : used_rows_[s_plane]) {
841 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
842 xfer(d_plane, s_plane, row, d_col, row, *col_it);
852 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
857 if(used_rows_[
p].empty()) {
862 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].
begin(), used_rows_[
p].
end());
863 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
864 for(
unsigned int c = 0u;
c < cols_; ++
c) {
865 plane_[
p][r * cols_ +
c] = 0.0;
869 used_rows_[
p].clear();
870 used_cols_[
p].clear();
876 used_rows_[
p].insert(0u);
877 used_cols_[
p].insert(0u);
884 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
886 assert(a_hp <= rows_);
887 assert(b_hp <= cols_);
888 unsigned int plane = plane_index(a_slowed, b_slowed);
889 ++val(plane, a_hp, b_hp);
890 used_rows_[plane].insert(a_hp);
891 used_cols_[plane].insert(b_hp);
897 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const
901 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
908 for(
const unsigned& row : used_rows_[
p]) {
909 prob += val(
p, row, 0);
915 for(
const unsigned& col : used_cols_[
p]) {
916 prob += val(
p, 0, col);
929 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const
931 if(!plane_used(plane)) {
936 for(
unsigned col : used_cols_[plane]) {
937 sum += val(plane, row, col);
945 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const
947 if(!plane_used(plane)) {
952 for(
unsigned row : used_rows_[plane]) {
953 sum += val(plane, row, column);
963 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const
965 for(
const unsigned& row : used_rows_[plane]) {
966 for(
const unsigned& col : used_cols_[plane]) {
967 const double& prob = val(plane, row, col);
968 row_sums[row] += prob;
969 col_sums[col] += prob;
974 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG)
975 void prob_matrix::dump()
const
977 unsigned int row, col, m;
978 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
980 for(m = 0; m < NUM_PLANES; ++m) {
986 for(row = 0; row < rows_; ++row) {
988 for(col = 0; col < cols_; ++col) {
989 debug((
"%4.3g ", val(m, row, col) * 100));
997 void prob_matrix::dump()
const
1007 class combat_matrix :
protected prob_matrix
1010 combat_matrix(
unsigned int a_max_hp,
1011 unsigned int b_max_hp,
1014 const summary_t& a_summary,
1015 const summary_t& b_summary,
1018 unsigned int a_damage,
1019 unsigned int b_damage,
1020 unsigned int a_slow_damage,
1021 unsigned int b_slow_damage,
1022 int a_drain_percent,
1023 int b_drain_percent,
1024 int a_drain_constant,
1025 int b_drain_constant);
1027 virtual ~combat_matrix()
1032 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1033 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1035 void forced_levelup_a();
1036 void conditional_levelup_a();
1038 void forced_levelup_b();
1039 void conditional_levelup_b();
1041 using prob_matrix::row_sum;
1042 using prob_matrix::col_sum;
1045 virtual void extract_results(
1046 summary_t& summary_a, summary_t& summary_b)
1051 prob_matrix::dump();
1058 unsigned a_slow_damage_;
1059 int a_drain_percent_;
1060 int a_drain_constant_;
1065 unsigned b_slow_damage_;
1066 int b_drain_percent_;
1067 int b_drain_constant_;
1084 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1085 unsigned int b_max_hp,
1088 const summary_t& a_summary,
1089 const summary_t& b_summary,
1092 unsigned int a_damage,
1093 unsigned int b_damage,
1094 unsigned int a_slow_damage,
1095 unsigned int b_slow_damage,
1096 int a_drain_percent,
1097 int b_drain_percent,
1098 int a_drain_constant,
1099 int b_drain_constant)
1101 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1102 , a_max_hp_(a_max_hp)
1104 , a_damage_(a_damage)
1105 , a_slow_damage_(a_slow_damage)
1106 , a_drain_percent_(a_drain_percent)
1107 , a_drain_constant_(a_drain_constant)
1108 , b_max_hp_(b_max_hp)
1110 , b_damage_(b_damage)
1111 , b_slow_damage_(b_slow_damage)
1112 , b_drain_percent_(b_drain_percent)
1113 , b_drain_constant_(b_drain_constant)
1118 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1120 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1121 if(!plane_used(
p)) {
1126 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1127 if(b_hp > actual_damage) {
1129 move_column(
p,
p, b_hp - actual_damage, 0);
1134 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1136 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1137 if(!plane_used(
p)) {
1142 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1143 if(a_hp > actual_damage) {
1145 move_row(
p,
p, a_hp - actual_damage, 0);
1150 void combat_matrix::forced_levelup_a()
1154 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1156 merge_cols(
p & -2,
p, a_max_hp_);
1161 void combat_matrix::forced_levelup_b()
1165 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1167 merge_rows(
p & -3,
p, b_max_hp_);
1172 void combat_matrix::conditional_levelup_a()
1176 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1178 merge_col(
p & -2,
p, 0, a_max_hp_);
1183 void combat_matrix::conditional_levelup_b()
1187 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1189 merge_row(
p & -3,
p, 0, b_max_hp_);
1199 class probability_combat_matrix :
public combat_matrix
1202 probability_combat_matrix(
unsigned int a_max_hp,
1203 unsigned int b_max_hp,
1206 const summary_t& a_summary,
1207 const summary_t& b_summary,
1210 unsigned int a_damage,
1211 unsigned int b_damage,
1212 unsigned int a_slow_damage,
1213 unsigned int b_slow_damage,
1214 int a_drain_percent,
1215 int b_drain_percent,
1216 int a_drain_constant,
1217 int b_drain_constant);
1220 void receive_blow_b(
double hit_chance);
1222 void receive_blow_a(
double hit_chance);
1225 double dead_prob()
const
1227 return prob_of_zero(
true,
true);
1231 double dead_prob_a()
const
1233 return prob_of_zero(
true,
false);
1237 double dead_prob_b()
const
1239 return prob_of_zero(
false,
true);
1242 void extract_results(
1243 summary_t& summary_a, summary_t& summary_b)
override;
1259 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1260 unsigned int b_max_hp,
1263 const summary_t& a_summary,
1264 const summary_t& b_summary,
1267 unsigned int a_damage,
1268 unsigned int b_damage,
1269 unsigned int a_slow_damage,
1270 unsigned int b_slow_damage,
1271 int a_drain_percent,
1272 int b_drain_percent,
1273 int a_drain_constant,
1274 int b_drain_constant)
1275 : combat_matrix(a_max_hp,
1296 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1299 unsigned src = NUM_PLANES;
1301 if(!plane_used(
src)) {
1309 unsigned damage = (
src & 1) ? a_slow_damage_ : a_damage_;
1311 shift_cols(
dst,
src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1317 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1320 unsigned src = NUM_PLANES;
1322 if(!plane_used(
src)) {
1330 unsigned damage = (
src & 2) ? b_slow_damage_ : b_damage_;
1332 shift_rows(
dst,
src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1336 void probability_combat_matrix::extract_results(
1337 summary_t& summary_a, summary_t& summary_b)
1340 summary_a[0] = std::vector<double>(num_rows());
1341 summary_b[0] = std::vector<double>(num_cols());
1343 if(plane_used(A_SLOWED)) {
1344 summary_a[1] = std::vector<double>(num_rows());
1347 if(plane_used(B_SLOWED)) {
1348 summary_b[1] = std::vector<double>(num_cols());
1351 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1352 if(!plane_used(
p)) {
1357 unsigned dst_a = (
p & 1) ? 1u : 0u;
1359 unsigned dst_b = (
p & 2) ? 1u : 0u;
1360 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1372 class monte_carlo_combat_matrix :
public combat_matrix
1375 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1376 unsigned int b_max_hp,
1379 const summary_t& a_summary,
1380 const summary_t& b_summary,
1383 unsigned int a_damage,
1384 unsigned int b_damage,
1385 unsigned int a_slow_damage,
1386 unsigned int b_slow_damage,
1387 int a_drain_percent,
1388 int b_drain_percent,
1389 int a_drain_constant,
1390 int b_drain_constant,
1391 unsigned int rounds,
1392 double a_hit_chance,
1393 double b_hit_chance,
1394 const std::vector<combat_slice>& a_split,
1395 const std::vector<combat_slice>& b_split,
1396 double a_initially_slowed_chance,
1397 double b_initially_slowed_chance);
1401 void extract_results(
1402 summary_t& summary_a, summary_t& summary_b)
override;
1404 double get_a_hit_probability()
const;
1405 double get_b_hit_probability()
const;
1408 static const unsigned int NUM_ITERATIONS = 5000u;
1410 std::vector<double> a_initial_;
1411 std::vector<double> b_initial_;
1412 std::vector<double> a_initial_slowed_;
1413 std::vector<double> b_initial_slowed_;
1414 std::vector<combat_slice> a_split_;
1415 std::vector<combat_slice> b_split_;
1416 unsigned int rounds_;
1417 double a_hit_chance_;
1418 double b_hit_chance_;
1419 double a_initially_slowed_chance_;
1420 double b_initially_slowed_chance_;
1421 unsigned int iterations_a_hit_ = 0u;
1422 unsigned int iterations_b_hit_ = 0u;
1424 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1425 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1426 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1427 static void scale_probabilities(
1428 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1431 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1432 unsigned int b_max_hp,
1435 const summary_t& a_summary,
1436 const summary_t& b_summary,
1439 unsigned int a_damage,
1440 unsigned int b_damage,
1441 unsigned int a_slow_damage,
1442 unsigned int b_slow_damage,
1443 int a_drain_percent,
1444 int b_drain_percent,
1445 int a_drain_constant,
1446 int b_drain_constant,
1447 unsigned int rounds,
1448 double a_hit_chance,
1449 double b_hit_chance,
1450 const std::vector<combat_slice>& a_split,
1451 const std::vector<combat_slice>& b_split,
1452 double a_initially_slowed_chance,
1453 double b_initially_slowed_chance)
1454 : combat_matrix(a_max_hp,
1473 , a_hit_chance_(a_hit_chance)
1474 , b_hit_chance_(b_hit_chance)
1475 , a_initially_slowed_chance_(a_initially_slowed_chance)
1476 , b_initially_slowed_chance_(b_initially_slowed_chance)
1478 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1479 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1480 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1481 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1486 void monte_carlo_combat_matrix::simulate()
1490 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1495 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1496 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1499 auto a_hp =
static_cast<unsigned int>(rng.
get_random_element(a_initial.begin(), a_initial.end()));
1500 auto b_hp =
static_cast<unsigned int>(rng.
get_random_element(b_initial.begin(), b_initial.end()));
1501 unsigned int a_strikes = calc_blows_a(a_hp);
1502 unsigned int b_strikes = calc_blows_b(b_hp);
1504 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1505 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1509 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1510 damage = std::min(damage, b_hp);
1512 b_slowed |= a_slows_;
1514 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1515 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1529 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1530 damage = std::min(damage, a_hp);
1532 a_slowed |= b_slows_;
1534 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1535 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1548 iterations_a_hit_ += a_hit ? 1 : 0;
1549 iterations_b_hit_ += b_hit ? 1 : 0;
1551 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1559 void monte_carlo_combat_matrix::extract_results(
1560 summary_t& summary_a, summary_t& summary_b)
1563 summary_a[0] = std::vector<double>(num_rows());
1564 summary_b[0] = std::vector<double>(num_cols());
1566 if(plane_used(A_SLOWED)) {
1567 summary_a[1] = std::vector<double>(num_rows());
1570 if(plane_used(B_SLOWED)) {
1571 summary_b[1] = std::vector<double>(num_cols());
1574 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1575 if(!plane_used(
p)) {
1580 unsigned dst_a = (
p & 1) ? 1u : 0u;
1582 unsigned dst_b = (
p & 2) ? 1u : 0u;
1583 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1586 divide_all_elements(summary_a[0],
static_cast<double>(NUM_ITERATIONS));
1587 divide_all_elements(summary_b[0],
static_cast<double>(NUM_ITERATIONS));
1589 if(plane_used(A_SLOWED)) {
1590 divide_all_elements(summary_a[1],
static_cast<double>(NUM_ITERATIONS));
1593 if(plane_used(B_SLOWED)) {
1594 divide_all_elements(summary_b[1],
static_cast<double>(NUM_ITERATIONS));
1598 double monte_carlo_combat_matrix::get_a_hit_probability()
const
1600 return static_cast<double>(iterations_a_hit_) /
static_cast<double>(NUM_ITERATIONS);
1603 double monte_carlo_combat_matrix::get_b_hit_probability()
const
1605 return static_cast<double>(iterations_b_hit_) /
static_cast<double>(NUM_ITERATIONS);
1608 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const
1610 auto it = a_split_.begin();
1611 while(it != a_split_.end() && it->end_hp <= a_hp) {
1615 if(it == a_split_.end()) {
1622 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const
1624 auto it = b_split_.begin();
1625 while(it != b_split_.end() && it->end_hp <= b_hp) {
1629 if(it == b_split_.end()) {
1636 void monte_carlo_combat_matrix::scale_probabilities(
1637 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1639 if(divisor == 0.0) {
1645 if(source.empty()) {
1646 target.resize(singular_hp + 1u, 0.0);
1647 target[singular_hp] = 1.0;
1650 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) { return prob / divisor; });
1653 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1656 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1658 for(
double&
e : vec) {
1666 : hp_dist(u.max_hp + 1, 0.0)
1697 : hp_dist(that.hp_dist)
1698 , untouched(that.untouched)
1709 enum class attack_prediction_mode { probability_calculation, monte_carlo_simulation };
1711 void forced_levelup(std::vector<double>& hp_dist)
1716 auto i = hp_dist.begin();
1718 for(++
i;
i != hp_dist.end(); ++
i) {
1723 hp_dist.back() = 1 - hp_dist.front();
1726 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1731 double scalefactor = 0;
1732 const double chance_to_survive = 1 - hp_dist.front();
1733 if(chance_to_survive > DBL_MIN) {
1734 scalefactor = 1 - kill_prob / chance_to_survive;
1737 auto i = hp_dist.begin();
1739 for(++
i;
i != hp_dist.end(); ++
i) {
1744 hp_dist.back() += kill_prob;
1755 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1757 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1761 prob_touched = std::max(prob_touched, 0.0);
1763 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1767 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1770 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1772 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1776 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1779 const double prob_survive_if_not_hit = 1.0;
1781 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1786 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1789 double prob_debuff = 0.0;
1792 prob_debuff += prob_already_debuffed_not_touched;
1794 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1798 prob_debuff += prob_already_debuffed_touched;
1800 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1807 }
else if(!kill_heals) {
1808 prob_debuff += prob_initially_healthy_touched;
1810 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1817 void round_prob_if_close_to_sure(
double& prob)
1821 }
else if(prob > 1.0 - 1.0e-9) {
1830 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1832 const unsigned size = hp_dist.size();
1835 for(
unsigned i = 0;
i !=
size; ++
i) {
1836 if(hp_dist[
i] != 0.0) {
1853 unsigned int fight_complexity(
unsigned int num_slices,
1854 unsigned int opp_num_slices,
1858 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1859 * ((opp_stats.slows || stats.
is_slowed) ? 2 : 1) * stats.
max_hp * opp_stats.max_hp;
1875 unsigned opp_strikes,
1876 std::vector<double>& hp_dist,
1877 std::vector<double>& opp_hp_dist,
1878 double& self_not_hit,
1879 double& opp_not_hit,
1880 bool levelup_considered)
1886 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1887 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1889 if(opp_hp_dist.empty()) {
1891 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1892 opp_hp_dist[opp_stats.
hp] = 1.0;
1894 for(
unsigned int i = 0;
i < strikes; ++
i) {
1895 for(
int j =
i; j >= 0; j--) {
1896 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1897 double move = opp_hp_dist[src_index] * hit_chance;
1898 opp_hp_dist[src_index] -= move;
1899 opp_hp_dist[src_index - stats.
damage] += move;
1902 opp_not_hit *= 1.0 - hit_chance;
1906 for(
unsigned int i = 0;
i < strikes; ++
i) {
1907 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1908 double move = opp_hp_dist[j] * hit_chance;
1909 opp_hp_dist[j] -= move;
1910 opp_hp_dist[j - stats.
damage] += move;
1912 opp_not_hit *= 1.0 - hit_chance;
1918 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1919 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1921 if(hp_dist.empty()) {
1923 hp_dist = std::vector<double>(stats.
max_hp + 1);
1924 hp_dist[stats.
hp] = 1.0;
1925 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1926 for(
int j =
i; j >= 0; j--) {
1927 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1928 double move = hp_dist[src_index] * opp_hit_chance;
1929 hp_dist[src_index] -= move;
1930 hp_dist[src_index - opp_stats.
damage] += move;
1933 self_not_hit *= 1.0 - opp_hit_chance;
1937 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1938 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1939 double move = hp_dist[j] * opp_hit_chance;
1941 hp_dist[j - opp_stats.
damage] += move;
1944 self_not_hit *= 1.0 - opp_hit_chance;
1948 if(!levelup_considered) {
1953 forced_levelup(hp_dist);
1957 forced_levelup(opp_hp_dist);
1965 unsigned opp_strikes,
1966 std::vector<double>& hp_dist,
1967 std::vector<double>& opp_hp_dist,
1968 double& self_not_hit,
1969 double& opp_not_hit,
1970 bool levelup_considered)
1975 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1979 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1981 if(opp_hp_dist.empty()) {
1982 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1983 if(strikes == 1 && opp_stats.
hp > 0) {
1984 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1985 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
1986 opp_not_hit *= 1.0 - hit_chance;
1988 opp_hp_dist[opp_stats.
hp] = 1.0;
1992 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
1993 double move = opp_hp_dist[
i] * hit_chance;
1994 opp_hp_dist[
i] -= move;
1995 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
1998 opp_not_hit *= 1.0 - hit_chance;
2003 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2004 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2006 if(hp_dist.empty()) {
2007 hp_dist = std::vector<double>(stats.
max_hp + 1);
2008 if(opp_strikes == 1 && stats.
hp > 0) {
2009 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2010 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2011 self_not_hit *= 1.0 - opp_hit_chance;
2013 hp_dist[stats.
hp] = 1.0;
2016 if(opp_strikes == 1) {
2017 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2018 double move = hp_dist[
i] * opp_hit_chance;
2020 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2023 self_not_hit *= 1.0 - opp_hit_chance;
2027 if(!levelup_considered) {
2032 forced_levelup(hp_dist);
2034 conditional_levelup(hp_dist, opp_hp_dist[0]);
2038 forced_levelup(opp_hp_dist);
2040 conditional_levelup(opp_hp_dist, hp_dist[0]);
2046 void complex_fight(attack_prediction_mode mode,
2050 unsigned opp_strikes,
2052 summary_t& opp_summary,
2053 double& self_not_hit,
2054 double& opp_not_hit,
2055 bool levelup_considered,
2056 std::vector<combat_slice>
split,
2057 std::vector<combat_slice> opp_split,
2058 double initially_slowed_chance,
2059 double opp_initially_slowed_chance)
2061 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2062 unsigned max_attacks = std::max(strikes, opp_strikes);
2064 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2067 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2073 a_damage = a_slow_damage = opp_stats.
max_hp;
2077 b_damage = b_slow_damage = stats.
max_hp;
2080 const double original_self_not_hit = self_not_hit;
2081 const double original_opp_not_hit = opp_not_hit;
2083 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2084 double self_hit = 0.0;
2085 double opp_hit = 0.0;
2086 double self_hit_unknown = 1.0;
2087 double opp_hit_unknown = 1.0;
2090 std::unique_ptr<combat_matrix> matrix;
2092 if(mode == attack_prediction_mode::probability_calculation) {
2093 debug((
"Using exact probability calculations.\n"));
2095 auto pm = std::make_unique<probability_combat_matrix>(
2115 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2117 debug((
"A strikes\n"));
2118 double b_already_dead = pm->dead_prob_b();
2119 pm->receive_blow_b(hit_chance);
2122 double first_hit = hit_chance * opp_hit_unknown;
2123 opp_hit += first_hit;
2124 opp_hit_unknown -= first_hit;
2125 double both_were_alive = 1.0 - b_already_dead - pm->dead_prob_a();
2126 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2127 self_hit_unknown *= (1.0 - this_hit_killed_b);
2129 if(
i < opp_strikes) {
2130 debug((
"B strikes\n"));
2131 double a_already_dead = pm->dead_prob_a();
2132 pm->receive_blow_a(opp_hit_chance);
2135 double first_hit = opp_hit_chance * self_hit_unknown;
2136 self_hit += first_hit;
2137 self_hit_unknown -= first_hit;
2138 double both_were_alive = 1.0 - a_already_dead - pm->dead_prob_b();
2139 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2140 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2144 debug((
"Combat ends:\n"));
2146 }
while(--rounds && pm->dead_prob() < 0.99);
2148 self_hit = std::min(self_hit, 1.0);
2149 opp_hit = std::min(opp_hit, 1.0);
2151 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2152 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2161 unsigned int plane = plane_index(stats, opp_stats);
2162 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2163 opp_not_hit = original_opp_not_hit * not_hit;
2165 if(opp_stats.
slows) {
2166 unsigned int plane = plane_index(stats, opp_stats);
2167 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2168 self_not_hit = original_self_not_hit * not_hit;
2172 matrix = std::move(pm);
2174 debug((
"Using Monte Carlo simulation.\n"));
2176 auto mcm = std::make_unique<monte_carlo_combat_matrix>(
2198 initially_slowed_chance,
2199 opp_initially_slowed_chance
2203 debug((
"Combat ends:\n"));
2206 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2207 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2210 matrix = std::move(mcm);
2221 if(levelup_considered) {
2223 matrix->forced_levelup_a();
2225 matrix->conditional_levelup_a();
2229 matrix->forced_levelup_b();
2231 matrix->conditional_levelup_b();
2236 matrix->extract_results(summary, opp_summary);
2246 unsigned opp_strikes,
2248 summary_t& opp_summary,
2249 double& self_not_hit,
2250 double& opp_not_hit,
2251 bool levelup_considered)
2257 && opp_summary[1].empty())
2259 if(strikes <= 1 && opp_strikes <= 1) {
2260 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2261 opp_not_hit, levelup_considered);
2262 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2263 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2264 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2265 opp_not_hit, levelup_considered);
2267 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2268 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2269 std::vector<combat_slice>(), 0.0, 0.0);
2272 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2273 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2274 std::vector<combat_slice>(), 0.0, 0.0);
2283 void init_slice_summary(
2284 std::vector<double>&
dst,
const std::vector<double>&
src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2299 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2308 void merge_slice_summary(std::vector<double>&
dst,
const std::vector<double>&
src,
double prob)
2318 for(
unsigned i = 0;
i !=
size; ++
i) {
2334 opponent.
fight(*
this, levelup_considered);
2338 #ifdef ATTACK_PREDICTION_DEBUG
2347 complex_fight(opponent, 1);
2348 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2350 opponent.
summary[0] = opp_prev;
2354 double self_not_hit = 1.0;
2355 double opp_not_hit = 1.0;
2358 double self_already_dead =
hp_dist[0];
2359 double opp_already_dead = opponent.
hp_dist[0];
2362 round_prob_if_close_to_sure(
slowed);
2363 round_prob_if_close_to_sure(opponent.
slowed);
2367 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2368 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2370 bool use_monte_carlo_simulation =
2372 &&
prefs::get().damage_prediction_allow_monte_carlo_simulation();
2374 if(use_monte_carlo_simulation) {
2377 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2380 }
else if(
split.size() == 1 && opp_split.size() == 1) {
2383 opp_not_hit, levelup_considered);
2386 summary_t summary_result, opp_summary_result;
2392 for(
unsigned s = 0;
s !=
split.size(); ++
s) {
2393 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2394 const double sit_prob =
split[
s].prob * opp_split[
t].prob;
2397 summary_t sit_summary, sit_opp_summary;
2400 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2402 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2407 double sit_self_not_hit = sit_prob;
2408 double sit_opp_not_hit = sit_prob;
2410 do_fight(
u_, opponent.
u_,
split[
s].strikes, opp_split[
t].strikes, sit_summary, sit_opp_summary,
2411 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2414 self_not_hit += sit_self_not_hit;
2415 opp_not_hit += sit_opp_not_hit;
2416 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2417 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2418 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2419 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2424 summary[0].swap(summary_result[0]);
2425 summary[1].swap(summary_result[1]);
2426 opponent.
summary[0].swap(opp_summary_result[0]);
2427 opponent.
summary[1].swap(opp_summary_result[1]);
2432 assert(opponent.
summary[0].size() == opp_res.size());
2433 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2434 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2435 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i];
2439 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2440 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2441 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i];
2453 for(
unsigned int i = 0;
i <
size; ++
i)
2457 if(opponent.
summary[1].empty()) {
2462 for(
unsigned int i = 0;
i <
size; ++
i)
2467 double touched = 1.0 - self_not_hit;
2468 double opp_touched = 1.0 - opp_not_hit;
2480 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2501 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2510 #if defined(BENCHMARK) || defined(CHECK)
2513 static const unsigned int NUM_UNITS = 50;
2515 #ifdef ATTACK_PREDICTION_DEBUG
2518 std::ostringstream ss;
2521 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; "
2537 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2541 ss <<
"firststrike, ";
2544 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2546 std::cout << ss.rdbuf() << std::endl;
2554 #ifdef HUMAN_READABLE
2557 std::ostringstream ss;
2560 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter,
label,
2580 ss <<
"firststrike, ";
2583 std::cout << ss.rdbuf() << std::endl;
2586 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2588 int num_outputs = 0;
2589 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2591 if(num_outputs++ % 6 == 0) {
2597 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2603 #elif defined(CHECK)
2606 std::ostringstream ss;
2628 ss <<
"firststrike, ";
2631 std::cout << ss.rdbuf() << std::endl;
2634 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2636 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2648 void combatant::reset()
2650 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2657 summary[0] = std::vector<double>();
2658 summary[1] = std::vector<double>();
2661 static void run(
unsigned specific_battle)
2663 using std::chrono::duration_cast;
2664 using std::chrono::microseconds;
2669 unsigned int i, j, k, battle = 0;
2670 std::chrono::high_resolution_clock::time_point
start,
end;
2672 for(
i = 0;
i < NUM_UNITS; ++
i) {
2673 unsigned alt =
i + 74;
2676 unsigned max_hp = (
i * 2) % 23 + (
i * 3) % 14 + 25;
2677 unsigned hp = (alt * 5) % max_hp + 1;
2689 list_combatant(*stats[
i],
i + 1);
2692 start = std::chrono::high_resolution_clock::now();
2694 for(
i = 0;
i < NUM_UNITS; ++
i) {
2695 for(j = 0; j < NUM_UNITS; ++j) {
2700 for(k = 0; k < NUM_UNITS; ++k) {
2701 if(
i == k || j == k) {
2706 if(specific_battle && battle != specific_battle) {
2714 u[
i]->print(
"Defender", battle,
i + 1);
2715 u[j]->print(
"Attacker #1", battle, j + 1);
2716 u[k]->print(
"Attacker #2", battle, k + 1);
2725 end = std::chrono::high_resolution_clock::now();
2730 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2731 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2732 printf(
"Time per calc = %li us\n",
static_cast<long>(duration_cast<microseconds>(total).count())
2733 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2735 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2738 for(
i = 0;
i < NUM_UNITS; ++
i) {
2749 int add_to_argv = 4;
2750 int damage = atoi((*argv)[1]);
2751 int num_attacks = atoi((*argv)[2]);
2752 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2753 int hit_chance = atoi((*argv)[4]);
2756 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2757 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2761 char* max = strstr((*argv)[5],
"maxhp=");
2763 max_hp = atoi(max + strlen(
"maxhp="));
2764 if(max_hp < hitpoints) {
2765 PLAIN_LOG <<
"maxhp must be at least hitpoints.";
2770 if(strstr((*argv)[5],
"drain")) {
2772 PLAIN_LOG <<
"WARNING: drain specified without maxhp; assuming uninjured.";
2778 if(strstr((*argv)[5],
"slows")) {
2782 if(strstr((*argv)[5],
"slowed")) {
2786 if(strstr((*argv)[5],
"berserk")) {
2790 if(strstr((*argv)[5],
"firststrike")) {
2794 if(strstr((*argv)[5],
"swarm")) {
2796 PLAIN_LOG <<
"WARNING: swarm specified without maxhp; assuming uninjured.";
2804 *argv += add_to_argv;
2808 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2811 int main(
int argc,
char* argv[])
2818 run(argv[1] ? atoi(argv[1]) : 0);
2822 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" "
2823 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] "
2824 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...";
2828 def_stats = parse_unit(&argv);
2830 for(
i = 0; argv[1] &&
i < 19; ++
i) {
2831 att_stats[
i] = parse_unit(&argv);
2837 for(
i = 0; att[
i]; ++
i) {
2838 debug((
"Fighting next attacker\n"));
2842 def->print(
"Defender", 0, 0);
2843 for(
i = 0; att[
i]; ++
i) {
2844 att[
i]->print(
"Attacker", 0,
i + 1);
2847 for(
i = 0; att[
i]; ++
i) {
2849 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.
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)
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
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)
bool empty() const
False if both w and h are > 0, true otherwise.
constexpr point size() const
static map_location::direction s