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;
96using 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);
122combat_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];
156combat_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) {
189std::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_;
377prob_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"));
439std::unique_ptr<double[]> prob_matrix::new_plane()
const
441 return std::make_unique<double[]>(std::size_t{rows_ * cols_});
453void 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);
484void 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;
503double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
507 return plane_[plane][row * cols_ + col];
510const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const
514 return plane_[plane][row * cols_ + col];
521void 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
571void 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);
619void 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);
659void 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);
692void 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);
732void 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);
763void 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);
774void 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);
786void 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);
801void 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);
818void 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);
833void 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);
850void prob_matrix::clear()
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);
884void 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);
897double 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);
929double 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);
945double 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);
963void 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)
975void 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));
997void prob_matrix::dump()
const
1007class 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_;
1084combat_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)
1118void 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);
1134void 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);
1150void combat_matrix::forced_levelup_a()
1154 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1156 merge_cols(
p & -2,
p, a_max_hp_);
1161void combat_matrix::forced_levelup_b()
1165 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1167 merge_rows(
p & -3,
p, b_max_hp_);
1172void combat_matrix::conditional_levelup_a()
1176 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1178 merge_col(
p & -2,
p, 0, a_max_hp_);
1183void combat_matrix::conditional_levelup_b()
1187 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1189 merge_row(
p & -3,
p, 0, b_max_hp_);
1199class 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;
1259probability_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,
1296void 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_);
1317void 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_);
1336void 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]);
1372class 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 unsigned int a_chance_to_hit,
1393 unsigned int b_chance_to_hit,
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,
1398 bool use_biased_rng);
1402 void extract_results(
1403 summary_t& summary_a, summary_t& summary_b)
override;
1405 double get_a_hit_probability()
const;
1406 double get_b_hit_probability()
const;
1409 static const unsigned int NUM_ITERATIONS = 5000u;
1411 std::vector<double> a_initial_;
1412 std::vector<double> b_initial_;
1413 std::vector<double> a_initial_slowed_;
1414 std::vector<double> b_initial_slowed_;
1415 std::vector<combat_slice> a_split_;
1416 std::vector<combat_slice> b_split_;
1417 unsigned int rounds_;
1418 double a_hit_chance_;
1419 double b_hit_chance_;
1420 unsigned int a_chance_to_hit_;
1421 unsigned int b_chance_to_hit_;
1422 bool use_biased_rng_;
1423 double a_initially_slowed_chance_;
1424 double b_initially_slowed_chance_;
1425 unsigned int iterations_a_hit_ = 0u;
1426 unsigned int iterations_b_hit_ = 0u;
1428 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1429 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1430 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1431 static void scale_probabilities(
1432 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1435monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1436 unsigned int b_max_hp,
1439 const summary_t& a_summary,
1440 const summary_t& b_summary,
1443 unsigned int a_damage,
1444 unsigned int b_damage,
1445 unsigned int a_slow_damage,
1446 unsigned int b_slow_damage,
1447 int a_drain_percent,
1448 int b_drain_percent,
1449 int a_drain_constant,
1450 int b_drain_constant,
1451 unsigned int rounds,
1452 unsigned int a_chance_to_hit,
1453 unsigned int b_chance_to_hit,
1454 const std::vector<combat_slice>& a_split,
1455 const std::vector<combat_slice>& b_split,
1456 double a_initially_slowed_chance,
1457 double b_initially_slowed_chance,
1458 bool use_biased_rng)
1459 : combat_matrix(a_max_hp,
1478 , a_hit_chance_(a_chance_to_hit / 100.0)
1479 , b_hit_chance_(b_chance_to_hit / 100.0)
1480 , a_chance_to_hit_(a_chance_to_hit)
1481 , b_chance_to_hit_(b_chance_to_hit)
1482 , use_biased_rng_(use_biased_rng)
1483 , a_initially_slowed_chance_(a_initially_slowed_chance)
1484 , b_initially_slowed_chance_(b_initially_slowed_chance)
1486 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1487 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1488 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1489 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1494void monte_carlo_combat_matrix::simulate()
1498 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1503 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1504 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1507 auto a_hp =
static_cast<unsigned int>(rng.
get_random_element(a_initial.begin(), a_initial.end()));
1508 auto b_hp =
static_cast<unsigned int>(rng.
get_random_element(b_initial.begin(), b_initial.end()));
1509 unsigned int a_strikes = calc_blows_a(a_hp);
1510 unsigned int b_strikes = calc_blows_b(b_hp);
1515 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1516 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1518 const bool a_hits = use_biased_rng_
1520 : rng.get_random_bool(a_hit_chance_);
1523 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1524 damage = std::min(damage, b_hp);
1526 b_slowed |= a_slows_;
1528 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1529 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1541 const bool b_hits = use_biased_rng_
1543 : rng.get_random_bool(b_hit_chance_);
1546 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1547 damage = std::min(damage, a_hp);
1549 a_slowed |= b_slows_;
1551 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1552 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1565 iterations_a_hit_ += a_hit ? 1 : 0;
1566 iterations_b_hit_ += b_hit ? 1 : 0;
1568 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1576void monte_carlo_combat_matrix::extract_results(
1577 summary_t& summary_a, summary_t& summary_b)
1580 summary_a[0] = std::vector<double>(num_rows());
1581 summary_b[0] = std::vector<double>(num_cols());
1583 if(plane_used(A_SLOWED)) {
1584 summary_a[1] = std::vector<double>(num_rows());
1587 if(plane_used(B_SLOWED)) {
1588 summary_b[1] = std::vector<double>(num_cols());
1591 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1592 if(!plane_used(
p)) {
1597 unsigned dst_a = (
p & 1) ? 1u : 0u;
1599 unsigned dst_b = (
p & 2) ? 1u : 0u;
1600 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1603 divide_all_elements(summary_a[0],
static_cast<double>(NUM_ITERATIONS));
1604 divide_all_elements(summary_b[0],
static_cast<double>(NUM_ITERATIONS));
1606 if(plane_used(A_SLOWED)) {
1607 divide_all_elements(summary_a[1],
static_cast<double>(NUM_ITERATIONS));
1610 if(plane_used(B_SLOWED)) {
1611 divide_all_elements(summary_b[1],
static_cast<double>(NUM_ITERATIONS));
1615double monte_carlo_combat_matrix::get_a_hit_probability()
const
1617 return static_cast<double>(iterations_a_hit_) /
static_cast<double>(NUM_ITERATIONS);
1620double monte_carlo_combat_matrix::get_b_hit_probability()
const
1622 return static_cast<double>(iterations_b_hit_) /
static_cast<double>(NUM_ITERATIONS);
1625unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const
1627 auto it = a_split_.begin();
1628 while(it != a_split_.end() && it->end_hp <= a_hp) {
1632 if(it == a_split_.end()) {
1639unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const
1641 auto it = b_split_.begin();
1642 while(it != b_split_.end() && it->end_hp <= b_hp) {
1646 if(it == b_split_.end()) {
1653void monte_carlo_combat_matrix::scale_probabilities(
1654 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1656 if(divisor == 0.0) {
1662 if(source.empty()) {
1663 target.resize(singular_hp + 1u, 0.0);
1664 target[singular_hp] = 1.0;
1667 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) { return prob / divisor; });
1670 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1673void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1675 for(
double&
e : vec) {
1683 : hp_dist(u.max_hp + 1, 0.0)
1714 : hp_dist(that.hp_dist)
1715 , untouched(that.untouched)
1716 , poisoned(that.poisoned)
1717 , slowed(that.slowed)
1726enum class attack_prediction_mode { probability_calculation, monte_carlo_simulation, biased_rng_simulation };
1728void forced_levelup(std::vector<double>& hp_dist)
1733 auto i = hp_dist.begin();
1735 for(++
i;
i != hp_dist.end(); ++
i) {
1740 hp_dist.back() = 1 - hp_dist.front();
1743void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1748 double scalefactor = 0;
1749 const double chance_to_survive = 1 - hp_dist.front();
1750 if(chance_to_survive > DBL_MIN) {
1751 scalefactor = 1 - kill_prob / chance_to_survive;
1754 auto i = hp_dist.begin();
1756 for(++
i;
i != hp_dist.end(); ++
i) {
1761 hp_dist.back() += kill_prob;
1772double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1774 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1778 prob_touched = std::max(prob_touched, 0.0);
1780 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1784 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1787 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1789 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1793 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1796 const double prob_survive_if_not_hit = 1.0;
1798 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1803 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1806 double prob_debuff = 0.0;
1809 prob_debuff += prob_already_debuffed_not_touched;
1811 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1815 prob_debuff += prob_already_debuffed_touched;
1817 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1824 }
else if(!kill_heals) {
1825 prob_debuff += prob_initially_healthy_touched;
1827 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1834void round_prob_if_close_to_sure(
double& prob)
1838 }
else if(prob > 1.0 - 1.0e-9) {
1847unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1849 const unsigned size = hp_dist.size();
1852 for(
unsigned i = 0;
i !=
size; ++
i) {
1853 if(hp_dist[
i] != 0.0) {
1870unsigned int fight_complexity(
unsigned int num_slices,
1871 unsigned int opp_num_slices,
1875 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1876 * ((opp_stats.slows || stats.is_slowed) ? 2 : 1) * stats.max_hp * opp_stats.max_hp;
1885 return (stats.
is_slowed ? 1 : 0) | (opp_stats.is_slowed ? 2 : 0);
1892 unsigned opp_strikes,
1893 std::vector<double>& hp_dist,
1894 std::vector<double>& opp_hp_dist,
1895 double& self_not_hit,
1896 double& opp_not_hit,
1897 bool levelup_considered)
1903 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1904 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1906 if(opp_hp_dist.empty()) {
1908 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1909 opp_hp_dist[opp_stats.
hp] = 1.0;
1911 for(
unsigned int i = 0;
i < strikes; ++
i) {
1912 for(
int j =
i; j >= 0; j--) {
1913 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1914 double move = opp_hp_dist[src_index] * hit_chance;
1915 opp_hp_dist[src_index] -= move;
1916 opp_hp_dist[src_index - stats.
damage] += move;
1919 opp_not_hit *= 1.0 - hit_chance;
1923 for(
unsigned int i = 0;
i < strikes; ++
i) {
1924 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1925 double move = opp_hp_dist[j] * hit_chance;
1926 opp_hp_dist[j] -= move;
1927 opp_hp_dist[j - stats.
damage] += move;
1929 opp_not_hit *= 1.0 - hit_chance;
1935 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1936 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1938 if(hp_dist.empty()) {
1940 hp_dist = std::vector<double>(stats.
max_hp + 1);
1941 hp_dist[stats.
hp] = 1.0;
1942 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1943 for(
int j =
i; j >= 0; j--) {
1944 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1945 double move = hp_dist[src_index] * opp_hit_chance;
1946 hp_dist[src_index] -= move;
1947 hp_dist[src_index - opp_stats.
damage] += move;
1950 self_not_hit *= 1.0 - opp_hit_chance;
1954 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1955 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1956 double move = hp_dist[j] * opp_hit_chance;
1958 hp_dist[j - opp_stats.
damage] += move;
1961 self_not_hit *= 1.0 - opp_hit_chance;
1970 forced_levelup(hp_dist);
1974 forced_levelup(opp_hp_dist);
1982 unsigned opp_strikes,
1983 std::vector<double>& hp_dist,
1984 std::vector<double>& opp_hp_dist,
1985 double& self_not_hit,
1986 double& opp_not_hit,
1987 bool levelup_considered)
1992 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1996 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1998 if(opp_hp_dist.empty()) {
1999 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
2000 if(strikes == 1 && opp_stats.
hp > 0) {
2001 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
2002 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
2003 opp_not_hit *= 1.0 - hit_chance;
2005 opp_hp_dist[opp_stats.
hp] = 1.0;
2009 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
2010 double move = opp_hp_dist[
i] * hit_chance;
2011 opp_hp_dist[
i] -= move;
2012 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2015 opp_not_hit *= 1.0 - hit_chance;
2020 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2021 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2023 if(hp_dist.empty()) {
2024 hp_dist = std::vector<double>(stats.
max_hp + 1);
2025 if(opp_strikes == 1 && stats.
hp > 0) {
2026 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2027 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2028 self_not_hit *= 1.0 - opp_hit_chance;
2030 hp_dist[stats.
hp] = 1.0;
2033 if(opp_strikes == 1) {
2034 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2035 double move = hp_dist[
i] * opp_hit_chance;
2037 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2040 self_not_hit *= 1.0 - opp_hit_chance;
2049 forced_levelup(hp_dist);
2051 conditional_levelup(hp_dist, opp_hp_dist[0]);
2055 forced_levelup(opp_hp_dist);
2057 conditional_levelup(opp_hp_dist, hp_dist[0]);
2063void complex_fight(attack_prediction_mode mode,
2067 unsigned opp_strikes,
2069 summary_t& opp_summary,
2070 double& self_not_hit,
2071 double& opp_not_hit,
2072 bool levelup_considered,
2073 const std::vector<combat_slice>& split,
2074 const std::vector<combat_slice>& opp_split,
2075 double initially_slowed_chance,
2076 double opp_initially_slowed_chance)
2078 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2079 unsigned max_attacks = std::max(strikes, opp_strikes);
2081 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2084 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2090 a_damage = a_slow_damage = opp_stats.
max_hp;
2094 b_damage = b_slow_damage = stats.
max_hp;
2097 const double original_self_not_hit = self_not_hit;
2098 const double original_opp_not_hit = opp_not_hit;
2100 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2101 double self_hit = 0.0;
2102 double opp_hit = 0.0;
2103 double self_hit_unknown = 1.0;
2104 double opp_hit_unknown = 1.0;
2107 std::unique_ptr<combat_matrix> matrix;
2109 if(mode == attack_prediction_mode::probability_calculation) {
2110 debug((
"Using exact probability calculations.\n"));
2112 auto pm = std::make_unique<probability_combat_matrix>(
2132 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2134 debug((
"A strikes\n"));
2135 double b_already_dead = pm->dead_prob_b();
2136 pm->receive_blow_b(hit_chance);
2139 double first_hit = hit_chance * opp_hit_unknown;
2140 opp_hit += first_hit;
2141 opp_hit_unknown -= first_hit;
2142 double both_were_alive = 1.0 - b_already_dead - pm->dead_prob_a();
2143 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2144 self_hit_unknown *= (1.0 - this_hit_killed_b);
2146 if(
i < opp_strikes) {
2147 debug((
"B strikes\n"));
2148 double a_already_dead = pm->dead_prob_a();
2149 pm->receive_blow_a(opp_hit_chance);
2152 double first_hit = opp_hit_chance * self_hit_unknown;
2153 self_hit += first_hit;
2154 self_hit_unknown -= first_hit;
2155 double both_were_alive = 1.0 - a_already_dead - pm->dead_prob_b();
2156 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2157 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2161 debug((
"Combat ends:\n"));
2163 }
while(--rounds && pm->dead_prob() < 0.99);
2165 self_hit = std::min(self_hit, 1.0);
2166 opp_hit = std::min(opp_hit, 1.0);
2168 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2169 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2178 unsigned int plane = plane_index(stats, opp_stats);
2179 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.hp));
2180 opp_not_hit = original_opp_not_hit * not_hit;
2182 if(opp_stats.
slows) {
2183 unsigned int plane = plane_index(stats, opp_stats);
2184 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.hp));
2185 self_not_hit = original_self_not_hit * not_hit;
2189 matrix = std::move(pm);
2191 debug((
"Using Monte Carlo simulation.\n"));
2193 auto mcm = std::make_unique<monte_carlo_combat_matrix>(
2215 initially_slowed_chance,
2216 opp_initially_slowed_chance,
2217 mode == attack_prediction_mode::biased_rng_simulation
2221 debug((
"Combat ends:\n"));
2224 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2225 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2228 matrix = std::move(mcm);
2241 matrix->forced_levelup_a();
2243 matrix->conditional_levelup_a();
2247 matrix->forced_levelup_b();
2249 matrix->conditional_levelup_b();
2254 matrix->extract_results(summary, opp_summary);
2264 unsigned opp_strikes,
2266 summary_t& opp_summary,
2267 double& self_not_hit,
2268 double& opp_not_hit,
2269 bool levelup_considered)
2275 && opp_summary[1].empty())
2277 if(strikes <= 1 && opp_strikes <= 1) {
2278 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2279 opp_not_hit, levelup_considered);
2280 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2281 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2282 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2283 opp_not_hit, levelup_considered);
2285 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2286 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2287 std::vector<combat_slice>(), 0.0, 0.0);
2290 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2291 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2292 std::vector<combat_slice>(), 0.0, 0.0);
2301void init_slice_summary(
2302 std::vector<double>&
dst,
const std::vector<double>&
src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2316 dst.resize(size, 0.0);
2317 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2326void merge_slice_summary(std::vector<double>&
dst,
const std::vector<double>&
src,
double prob)
2332 dst.resize(size, 0.0);
2336 for(
unsigned i = 0;
i !=
size; ++
i) {
2352 opponent.
fight(*
this, levelup_considered, use_biased_rng);
2356#ifdef ATTACK_PREDICTION_DEBUG
2365 complex_fight(opponent, 1);
2366 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2368 opponent.
summary[0] = opp_prev;
2372 double self_not_hit = 1.0;
2373 double opp_not_hit = 1.0;
2376 double self_already_dead =
hp_dist[0];
2377 double opp_already_dead = opponent.
hp_dist[0];
2380 round_prob_if_close_to_sure(
slowed);
2381 round_prob_if_close_to_sure(opponent.
slowed);
2385 const std::vector<combat_slice> split = split_summary(
u_,
summary);
2386 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2390 bool use_monte_carlo_simulation = use_biased_rng
2392 &&
prefs::get().damage_prediction_allow_monte_carlo_simulation());
2394 if(use_monte_carlo_simulation) {
2395 complex_fight(use_biased_rng ? attack_prediction_mode::biased_rng_simulation : attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2398 }
else if(split.size() == 1 && opp_split.size() == 1) {
2401 opp_not_hit, levelup_considered);
2404 summary_t summary_result, opp_summary_result;
2410 for(
unsigned s = 0;
s != split.size(); ++
s) {
2411 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2412 const double sit_prob = split[
s].prob * opp_split[
t].prob;
2415 summary_t sit_summary, sit_opp_summary;
2416 init_slice_summary(sit_summary[0],
summary[0], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2417 init_slice_summary(sit_summary[1],
summary[1], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2418 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2420 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2425 double sit_self_not_hit = sit_prob;
2426 double sit_opp_not_hit = sit_prob;
2428 do_fight(
u_, opponent.
u_, split[
s].strikes, opp_split[
t].strikes, sit_summary, sit_opp_summary,
2429 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2432 self_not_hit += sit_self_not_hit;
2433 opp_not_hit += sit_opp_not_hit;
2434 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2435 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2436 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2437 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2442 summary[0].swap(summary_result[0]);
2443 summary[1].swap(summary_result[1]);
2444 opponent.
summary[0].swap(opp_summary_result[0]);
2445 opponent.
summary[1].swap(opp_summary_result[1]);
2449 assert(
summary[0].size() == res.size());
2450 assert(opponent.
summary[0].size() == opp_res.size());
2451 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2452 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2453 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i];
2457 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2458 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2459 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i];
2469 const unsigned size =
summary[0].size();
2471 for(
unsigned int i = 0;
i < size; ++
i)
2475 if(opponent.
summary[1].empty()) {
2478 const unsigned size = opponent.
summary[0].size();
2479 opponent.
hp_dist.resize(size);
2480 for(
unsigned int i = 0;
i < size; ++
i)
2485 double touched = 1.0 - self_not_hit;
2486 double opp_touched = 1.0 - opp_not_hit;
2498 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2519 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2528#if defined(BENCHMARK) || defined(CHECK)
2531static const unsigned int NUM_UNITS = 50;
2533#ifdef ATTACK_PREDICTION_DEBUG
2536 std::ostringstream ss;
2539 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; "
2555 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2559 ss <<
"firststrike, ";
2562 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2564 std::cout << ss.rdbuf() << std::endl;
2572#ifdef HUMAN_READABLE
2573void combatant::print(
const char label[],
unsigned int battle,
unsigned int fighter)
const
2575 std::ostringstream ss;
2578 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter,
label,
2598 ss <<
"firststrike, ";
2601 std::cout << ss.rdbuf() << std::endl;
2604 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2606 int num_outputs = 0;
2607 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2609 if(num_outputs++ % 6 == 0) {
2615 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2622void combatant::print(
const char label[],
unsigned int battle,
unsigned int )
const
2624 std::ostringstream ss;
2646 ss <<
"firststrike, ";
2649 std::cout << ss.rdbuf() << std::endl;
2652 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2654 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2661void combatant::print(
const char [],
unsigned int ,
unsigned int )
const
2666void combatant::reset()
2668 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2675 summary[0] = std::vector<double>();
2676 summary[1] = std::vector<double>();
2679static void run(
unsigned specific_battle)
2681 using std::chrono::duration_cast;
2682 using std::chrono::microseconds;
2687 unsigned int i, j, k, battle = 0;
2688 std::chrono::high_resolution_clock::time_point
start,
end;
2690 for(
i = 0;
i < NUM_UNITS; ++
i) {
2691 unsigned alt =
i + 74;
2694 unsigned max_hp = (
i * 2) % 23 + (
i * 3) % 14 + 25;
2695 unsigned hp = (alt * 5) % max_hp + 1;
2707 list_combatant(*stats[
i],
i + 1);
2710 start = std::chrono::high_resolution_clock::now();
2712 for(
i = 0;
i < NUM_UNITS; ++
i) {
2713 for(j = 0; j < NUM_UNITS; ++j) {
2718 for(k = 0; k < NUM_UNITS; ++k) {
2719 if(
i == k || j == k) {
2724 if(specific_battle && battle != specific_battle) {
2732 u[
i]->print(
"Defender", battle,
i + 1);
2733 u[j]->print(
"Attacker #1", battle, j + 1);
2734 u[k]->print(
"Attacker #2", battle, k + 1);
2743 end = std::chrono::high_resolution_clock::now();
2748 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2749 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2750 printf(
"Time per calc = %li us\n",
static_cast<long>(duration_cast<microseconds>(total).count())
2751 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2753 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2756 for(
i = 0;
i < NUM_UNITS; ++
i) {
2767 int add_to_argv = 4;
2768 int damage = atoi((*argv)[1]);
2769 int num_attacks = atoi((*argv)[2]);
2770 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2771 int hit_chance = atoi((*argv)[4]);
2774 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2775 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2779 char* max = strstr((*argv)[5],
"maxhp=");
2781 max_hp = atoi(max + strlen(
"maxhp="));
2782 if(max_hp < hitpoints) {
2783 PLAIN_LOG <<
"maxhp must be at least hitpoints.";
2788 if(strstr((*argv)[5],
"drain")) {
2790 PLAIN_LOG <<
"WARNING: drain specified without maxhp; assuming uninjured.";
2796 if(strstr((*argv)[5],
"slows")) {
2800 if(strstr((*argv)[5],
"slowed")) {
2804 if(strstr((*argv)[5],
"berserk")) {
2808 if(strstr((*argv)[5],
"firststrike")) {
2812 if(strstr((*argv)[5],
"swarm")) {
2814 PLAIN_LOG <<
"WARNING: swarm specified without maxhp; assuming uninjured.";
2822 *argv += add_to_argv;
2826 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2829int main(
int argc,
char* argv[])
2836 run(argv[1] ? atoi(argv[1]) : 0);
2840 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" "
2841 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] "
2842 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...";
2846 def_stats = parse_unit(&argv);
2848 for(
i = 0; argv[1] &&
i < 19; ++
i) {
2849 att_stats[
i] = parse_unit(&argv);
2855 for(
i = 0; att[
i]; ++
i) {
2856 debug((
"Fighting next attacker\n"));
2860 def->print(
"Defender", 0, 0);
2861 for(
i = 0; att[
i]; ++
i) {
2862 att[
i]->print(
"Attacker", 0,
i + 1);
2865 for(
i = 0; att[
i]; ++
i) {
2867 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.
std::string label
What to show in the filter's drop-down list.
int roll_credit(randomness::rng &rng)
Rolls the credit a combatant starts a fight with.
bool roll_hit(randomness::rng &rng, int strikes_left, int chance_to_hit, int &credit)
Rolls one strike.
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.
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
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 can_advance
The unit can advance.
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.
void fight(combatant &opponent, bool levelup_considered=true, bool use_biased_rng=false)
Simulate a fight! Can be called multiple times for cumulative calculations.
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).
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