47 #if defined(BENCHMARK) || defined(CHECK) 53 #ifdef ATTACK_PREDICTION_DEBUG 54 #define debug(x) printf x 59 #ifdef ATTACK_PREDICTION_DEBUG 65 std::ostringstream ss;
67 ss <<
"==================================";
71 <<
"\n" <<
"is_slowed: " << stats.
is_slowed 72 <<
"\n" <<
"slows: " << stats.
slows 73 <<
"\n" <<
"drains: " << stats.
drains 74 <<
"\n" <<
"petrifies: " << stats.
petrifies 75 <<
"\n" <<
"poisons: " << stats.
poisons 76 <<
"\n" <<
"swarm: " << stats.
swarm 79 <<
"\n" <<
"rounds: " << stats.
rounds 81 <<
"\n" <<
"hp: " << stats.
hp 82 <<
"\n" <<
"max_hp: " << stats.
max_hp 84 <<
"\n" <<
"damage: " << stats.
damage 88 <<
"\n" <<
"num_blows: " << stats.
num_blows 89 <<
"\n" <<
"swarm_min: " << stats.
swarm_min 90 <<
"\n" <<
"swarm_max: " << stats.
swarm_max 93 std::cout << ss.rdbuf() << std::endl;
100 using summary_t = std::array<std::vector<double>, 2>;
119 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes);
120 combat_slice(
const summary_t& src_summary,
unsigned num_strikes);
126 combat_slice::combat_slice(
127 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes)
131 , strikes(num_strikes)
133 if(src_summary[0].empty()) {
140 if(end > src_summary[0].
size()) {
141 end = src_summary[0].size();
145 for(
unsigned i = begin;
i < end; ++
i) {
146 prob += src_summary[0][
i];
149 if(!src_summary[1].empty()) {
150 for(
unsigned i = begin;
i < end; ++
i) {
151 prob += src_summary[1][
i];
160 combat_slice::combat_slice(
const summary_t& src_summary,
unsigned num_strikes)
162 , end_hp(src_summary[0].
size())
164 , strikes(num_strikes)
176 unsigned old_strikes = stats.
calc_blows(cur_hp);
180 while(++cur_hp <= stats.
max_hp) {
193 std::vector<combat_slice> split_summary(
196 std::vector<combat_slice> result;
200 result.emplace_back(summary, unit_stats.
num_blows);
204 debug((
"Slicing:\n"));
206 unsigned cur_end = 0;
209 const unsigned cur_begin = cur_end;
210 cur_end = hp_for_next_attack(cur_begin, unit_stats);
213 combat_slice slice(summary, cur_begin, cur_end, unit_stats.
calc_blows(cur_begin));
214 if(slice.prob != 0.0) {
215 result.push_back(slice);
216 debug((
"\t%2u-%2u hp; strikes: %u; probability: %6.2f\n", cur_begin, cur_end, slice.strikes,
217 slice.prob * 100.0));
219 }
while(cur_end <= unit_stats.
max_hp);
235 prob_matrix(
unsigned int a_max,
241 const summary_t& a_initial,
242 const summary_t& b_initial);
245 void shift_cols(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
247 void shift_rows(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
250 void move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col);
252 void move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row);
255 void merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row);
256 void merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row);
257 void merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col);
258 void merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col);
264 void record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed);
267 static unsigned int plane_index(
bool a_slowed,
bool b_slowed)
269 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
273 double prob_of_zero(
bool check_a,
bool check_b)
const;
275 double row_sum(
unsigned plane,
unsigned row)
const;
277 double col_sum(
unsigned plane,
unsigned column)
const;
279 void sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const;
282 bool plane_used(
unsigned p)
const 284 return p < NUM_PLANES && plane_[
p] !=
nullptr;
287 unsigned int num_rows()
const 291 unsigned int num_cols()
const 311 std::unique_ptr<double[]> new_plane()
const;
313 void initialize_plane(
unsigned plane,
316 const std::vector<double>& a_initial,
317 const std::vector<double>& b_initial);
319 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial);
321 double& val(
unsigned plane,
unsigned row,
unsigned col);
322 const double& val(
unsigned plane,
unsigned row,
unsigned col)
const;
325 void xfer(
unsigned dst_plane,
333 void xfer(
unsigned dst_plane,
340 void shift_cols_in_row(
unsigned dst,
343 const std::vector<unsigned>& cols,
349 void shift_rows_in_col(
unsigned dst,
352 const std::vector<unsigned>& rows,
360 const unsigned int rows_, cols_;
361 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
365 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
381 prob_matrix::prob_matrix(
unsigned int a_max,
387 const summary_t& a_initial,
388 const summary_t& b_initial)
396 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
397 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
400 for(
unsigned plane = 0; plane != NUM_PLANES; ++plane) {
401 used_rows_[plane].insert(0u);
402 used_cols_[plane].insert(0u);
406 need_a_slowed = need_a_slowed || !a_initial[1].empty();
407 need_b_slowed = need_b_slowed || !b_initial[1].empty();
410 plane_[NEITHER_SLOWED] = new_plane();
411 plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
412 plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
413 plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ?
nullptr : new_plane();
416 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
418 if(!a_initial[1].empty()) {
419 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
422 if(!b_initial[1].empty()) {
423 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
426 if(!a_initial[1].empty() && !b_initial[1].empty()) {
427 initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
431 if(!a_initial[0].empty()) {
432 debug((
"A has fought before (or is slowed).\n"));
436 if(!b_initial[0].empty()) {
437 debug((
"B has fought before (or is slowed).\n"));
443 std::unique_ptr<double[]> prob_matrix::new_plane()
const 445 const unsigned int size = rows_ * cols_;
446 std::unique_ptr<double[]> res(
new double[size]);
447 std::fill_n(res.get(),
size, 0);
460 void prob_matrix::initialize_plane(
unsigned plane,
463 const std::vector<double>& a_initial,
464 const std::vector<double>& b_initial)
466 if(!a_initial.empty()) {
467 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
469 for(
unsigned row = 0; row < row_count; ++row) {
470 if(a_initial[row] != 0.0) {
471 used_rows_[plane].insert(row);
472 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
476 used_rows_[plane].insert(a_cur);
478 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
491 void prob_matrix::initialize_row(
492 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
494 if(!b_initial.empty()) {
495 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
497 for(
unsigned col = 0; col < col_count; ++col) {
498 if(b_initial[col] != 0.0) {
499 used_cols_[plane].insert(col);
500 val(plane, row, col) = row_prob * b_initial[col];
505 used_cols_[plane].insert(b_cur);
506 val(plane, row, b_cur) = row_prob;
510 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
514 return plane_[plane][row * cols_ + col];
517 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const 521 return plane_[plane][row * cols_ + col];
528 void prob_matrix::xfer(
unsigned dst_plane,
536 double& src = val(src_plane, row_src, col_src);
538 double diff = src * prob;
541 double& dst = val(dst_plane, row_dst, col_dst);
544 used_rows_[dst_plane].insert(row_dst);
545 used_cols_[dst_plane].insert(col_dst);
549 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
550 src_plane == NEITHER_SLOWED
552 : src_plane == A_SLOWED
554 : src_plane == B_SLOWED
556 : src_plane == BOTH_SLOWED
561 dst_plane == NEITHER_SLOWED
563 : dst_plane == A_SLOWED
565 : dst_plane == B_SLOWED
567 : dst_plane == BOTH_SLOWED
578 void prob_matrix::xfer(
579 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
581 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
585 double& src = val(src_plane, row_src, col_src);
587 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
588 src_plane == NEITHER_SLOWED
590 : src_plane == A_SLOWED
592 : src_plane == B_SLOWED
594 : src_plane == BOTH_SLOWED
598 dst_plane == NEITHER_SLOWED
600 : dst_plane == A_SLOWED
602 : dst_plane == B_SLOWED
604 : dst_plane == BOTH_SLOWED
610 double& dst = val(dst_plane, row_dst, col_dst);
613 used_rows_[dst_plane].insert(row_dst);
614 used_cols_[dst_plane].insert(col_dst);
626 void prob_matrix::shift_cols_in_row(
unsigned dst,
629 const std::vector<unsigned>& cols,
637 int row_i =
static_cast<int>(row);
638 int max_row =
static_cast<int>(rows_) - 1;
644 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
647 int col_i =
static_cast<int>(cols[col_x]);
648 int drain_amount = col_i * drain_percent / 100 + drain_constant;
649 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
650 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
654 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
655 for(; col_x < cols.size(); ++col_x) {
656 xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
666 void prob_matrix::shift_cols(
667 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
669 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
671 if(drain_constant || drain_percent) {
672 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
677 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
678 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
684 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
685 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
689 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
690 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
699 void prob_matrix::shift_rows_in_col(
unsigned dst,
702 const std::vector<unsigned>& rows,
710 int col_i =
static_cast<int>(col);
711 int max_col =
static_cast<int>(cols_) - 1;
717 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
720 int row_i =
static_cast<int>(rows[row_x]);
721 int drain_amount = row_i * drain_percent / 100 + drain_constant;
722 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
723 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
727 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
728 for(; row_x < rows.size(); ++row_x) {
729 xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
739 void prob_matrix::shift_rows(
740 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
742 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
744 if(drain_constant || drain_percent) {
745 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
750 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
751 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
757 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
758 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
761 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
762 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
770 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
773 for(
const unsigned& row : used_rows_[s_plane]) {
774 xfer(d_plane, s_plane, row, d_col, row, s_col);
781 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
784 for(
const unsigned& col : used_cols_[s_plane]) {
785 xfer(d_plane, s_plane, d_row, col, s_row, col);
793 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
795 auto rows_end = used_rows_[s_plane].end();
796 auto row_it = used_rows_[s_plane].begin();
799 for(++row_it; row_it != rows_end; ++row_it) {
800 xfer(d_plane, s_plane, d_row, col, *row_it, col);
808 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
810 auto rows_end = used_rows_[s_plane].end();
811 auto row_it = used_rows_[s_plane].begin();
814 for(++row_it; row_it != rows_end; ++row_it) {
815 for(
const unsigned& col : used_cols_[s_plane]) {
816 xfer(d_plane, s_plane, d_row, col, *row_it, col);
825 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
827 auto cols_end = used_cols_[s_plane].end();
828 auto col_it = used_cols_[s_plane].begin();
831 for(++col_it; col_it != cols_end; ++col_it) {
832 xfer(d_plane, s_plane, row, d_col, row, *col_it);
840 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
842 auto cols_end = used_cols_[s_plane].end();
844 auto cols_begin = std::next(used_cols_[s_plane].begin());
847 for(
const unsigned row : used_rows_[s_plane]) {
848 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
849 xfer(d_plane, s_plane, row, d_col, row, *col_it);
859 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
864 if(used_rows_[
p].empty()) {
869 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].begin(), used_rows_[
p].end());
870 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
871 for(
unsigned int c = 0u;
c < cols_; ++
c) {
872 plane_[
p][r * cols_ +
c] = 0.0;
876 used_rows_[
p].clear();
877 used_cols_[
p].clear();
883 used_rows_[
p].insert(0u);
884 used_cols_[
p].insert(0u);
891 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
893 assert(a_hp <= rows_);
894 assert(b_hp <= cols_);
895 unsigned int plane = plane_index(a_slowed, b_slowed);
896 ++val(plane, a_hp, b_hp);
897 used_rows_[plane].insert(a_hp);
898 used_cols_[plane].insert(b_hp);
904 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const 908 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
915 for(
const unsigned& row : used_rows_[
p]) {
916 prob += val(p, row, 0);
922 for(
const unsigned& col : used_cols_[
p]) {
923 prob += val(p, 0, col);
936 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const 938 if(!plane_used(plane)) {
943 for(
unsigned col : used_cols_[plane]) {
944 sum += val(plane, row, col);
952 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const 954 if(!plane_used(plane)) {
959 for(
unsigned row : used_rows_[plane]) {
960 sum += val(plane, row, column);
970 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const 972 for(
const unsigned& row : used_rows_[plane]) {
973 for(
const unsigned& col : used_cols_[plane]) {
974 const double& prob = val(plane, row, col);
975 row_sums[row] += prob;
976 col_sums[col] += prob;
981 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG) 982 void prob_matrix::dump()
const 984 unsigned int row, col, m;
985 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
987 for(m = 0; m < NUM_PLANES; ++m) {
993 for(row = 0; row < rows_; ++row) {
995 for(col = 0; col < cols_; ++col) {
996 debug((
"%4.3g ", val(m, row, col) * 100));
1004 void prob_matrix::dump()
const 1014 class combat_matrix :
protected prob_matrix
1017 combat_matrix(
unsigned int a_max_hp,
1018 unsigned int b_max_hp,
1021 const summary_t& a_summary,
1022 const summary_t& b_summary,
1025 unsigned int a_damage,
1026 unsigned int b_damage,
1027 unsigned int a_slow_damage,
1028 unsigned int b_slow_damage,
1029 int a_drain_percent,
1030 int b_drain_percent,
1031 int a_drain_constant,
1032 int b_drain_constant);
1034 virtual ~combat_matrix()
1039 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1040 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1042 void forced_levelup_a();
1043 void conditional_levelup_a();
1045 void forced_levelup_b();
1046 void conditional_levelup_b();
1048 using prob_matrix::row_sum;
1049 using prob_matrix::col_sum;
1052 virtual void extract_results(
1053 summary_t& summary_a, summary_t& summary_b)
1058 prob_matrix::dump();
1065 unsigned a_slow_damage_;
1066 int a_drain_percent_;
1067 int a_drain_constant_;
1072 unsigned b_slow_damage_;
1073 int b_drain_percent_;
1074 int b_drain_constant_;
1091 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1092 unsigned int b_max_hp,
1095 const summary_t& a_summary,
1096 const summary_t& b_summary,
1099 unsigned int a_damage,
1100 unsigned int b_damage,
1101 unsigned int a_slow_damage,
1102 unsigned int b_slow_damage,
1103 int a_drain_percent,
1104 int b_drain_percent,
1105 int a_drain_constant,
1106 int b_drain_constant)
1108 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1109 , a_max_hp_(a_max_hp)
1111 , a_damage_(a_damage)
1112 , a_slow_damage_(a_slow_damage)
1113 , a_drain_percent_(a_drain_percent)
1114 , a_drain_constant_(a_drain_constant)
1115 , b_max_hp_(b_max_hp)
1117 , b_damage_(b_damage)
1118 , b_slow_damage_(b_slow_damage)
1119 , b_drain_percent_(b_drain_percent)
1120 , b_drain_constant_(b_drain_constant)
1125 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1127 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1128 if(!plane_used(
p)) {
1133 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1134 if(b_hp > actual_damage) {
1136 move_column(
p,
p, b_hp - actual_damage, 0);
1141 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1143 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1144 if(!plane_used(
p)) {
1149 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1150 if(a_hp > actual_damage) {
1152 move_row(
p,
p, a_hp - actual_damage, 0);
1157 void combat_matrix::forced_levelup_a()
1161 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1163 merge_cols(
p & -2,
p, a_max_hp_);
1168 void combat_matrix::forced_levelup_b()
1172 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1174 merge_rows(
p & -3,
p, b_max_hp_);
1179 void combat_matrix::conditional_levelup_a()
1183 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1185 merge_col(
p & -2,
p, 0, a_max_hp_);
1190 void combat_matrix::conditional_levelup_b()
1194 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1196 merge_row(
p & -3,
p, 0, b_max_hp_);
1206 class probability_combat_matrix :
public combat_matrix
1209 probability_combat_matrix(
unsigned int a_max_hp,
1210 unsigned int b_max_hp,
1213 const summary_t& a_summary,
1214 const summary_t& b_summary,
1217 unsigned int a_damage,
1218 unsigned int b_damage,
1219 unsigned int a_slow_damage,
1220 unsigned int b_slow_damage,
1221 int a_drain_percent,
1222 int b_drain_percent,
1223 int a_drain_constant,
1224 int b_drain_constant);
1227 void receive_blow_b(
double hit_chance);
1229 void receive_blow_a(
double hit_chance);
1232 double dead_prob()
const 1234 return prob_of_zero(
true,
true);
1238 double dead_prob_a()
const 1240 return prob_of_zero(
true,
false);
1244 double dead_prob_b()
const 1246 return prob_of_zero(
false,
true);
1249 void extract_results(
1250 summary_t& summary_a, summary_t& summary_b)
override;
1266 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1267 unsigned int b_max_hp,
1270 const summary_t& a_summary,
1271 const summary_t& b_summary,
1274 unsigned int a_damage,
1275 unsigned int b_damage,
1276 unsigned int a_slow_damage,
1277 unsigned int b_slow_damage,
1278 int a_drain_percent,
1279 int b_drain_percent,
1280 int a_drain_constant,
1281 int b_drain_constant)
1282 : combat_matrix(a_max_hp,
1303 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1306 unsigned src = NUM_PLANES;
1308 if(!plane_used(src)) {
1313 int dst = a_slows_ ? src | 2 : src;
1316 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1318 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1324 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1327 unsigned src = NUM_PLANES;
1329 if(!plane_used(src)) {
1334 int dst = b_slows_ ? src | 1 : src;
1337 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1339 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1343 void probability_combat_matrix::extract_results(
1344 summary_t& summary_a, summary_t& summary_b)
1347 summary_a[0] = std::vector<double>(num_rows());
1348 summary_b[0] = std::vector<double>(num_cols());
1350 if(plane_used(A_SLOWED)) {
1351 summary_a[1] = std::vector<double>(num_rows());
1354 if(plane_used(B_SLOWED)) {
1355 summary_b[1] = std::vector<double>(num_cols());
1358 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1359 if(!plane_used(
p)) {
1364 unsigned dst_a = (
p & 1) ? 1u : 0u;
1366 unsigned dst_b = (
p & 2) ? 1u : 0u;
1367 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1379 class monte_carlo_combat_matrix :
public combat_matrix
1382 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1383 unsigned int b_max_hp,
1386 const summary_t& a_summary,
1387 const summary_t& b_summary,
1390 unsigned int a_damage,
1391 unsigned int b_damage,
1392 unsigned int a_slow_damage,
1393 unsigned int b_slow_damage,
1394 int a_drain_percent,
1395 int b_drain_percent,
1396 int a_drain_constant,
1397 int b_drain_constant,
1398 unsigned int rounds,
1399 double a_hit_chance,
1400 double b_hit_chance,
1401 const std::vector<combat_slice>& a_split,
1402 const std::vector<combat_slice>& b_split,
1403 double a_initially_slowed_chance,
1404 double b_initially_slowed_chance);
1408 void extract_results(
1409 summary_t& summary_a, summary_t& summary_b)
override;
1411 double get_a_hit_probability()
const;
1412 double get_b_hit_probability()
const;
1415 static const unsigned int NUM_ITERATIONS = 5000u;
1417 std::vector<double> a_initial_;
1418 std::vector<double> b_initial_;
1419 std::vector<double> a_initial_slowed_;
1420 std::vector<double> b_initial_slowed_;
1421 std::vector<combat_slice> a_split_;
1422 std::vector<combat_slice> b_split_;
1423 unsigned int rounds_;
1424 double a_hit_chance_;
1425 double b_hit_chance_;
1426 double a_initially_slowed_chance_;
1427 double b_initially_slowed_chance_;
1428 unsigned int iterations_a_hit_ = 0u;
1429 unsigned int iterations_b_hit_ = 0u;
1431 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1432 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1433 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1434 static void scale_probabilities(
1435 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1438 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1439 unsigned int b_max_hp,
1442 const summary_t& a_summary,
1443 const summary_t& b_summary,
1446 unsigned int a_damage,
1447 unsigned int b_damage,
1448 unsigned int a_slow_damage,
1449 unsigned int b_slow_damage,
1450 int a_drain_percent,
1451 int b_drain_percent,
1452 int a_drain_constant,
1453 int b_drain_constant,
1454 unsigned int rounds,
1455 double a_hit_chance,
1456 double b_hit_chance,
1457 const std::vector<combat_slice>& a_split,
1458 const std::vector<combat_slice>& b_split,
1459 double a_initially_slowed_chance,
1460 double b_initially_slowed_chance)
1461 : combat_matrix(a_max_hp,
1480 , a_hit_chance_(a_hit_chance)
1481 , b_hit_chance_(b_hit_chance)
1482 , a_initially_slowed_chance_(a_initially_slowed_chance)
1483 , b_initially_slowed_chance_(b_initially_slowed_chance)
1485 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1486 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1487 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1488 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1493 void monte_carlo_combat_matrix::simulate()
1497 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1502 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1503 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1506 unsigned int a_strikes = calc_blows_a(a_hp);
1507 unsigned int b_strikes = calc_blows_b(b_hp);
1509 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1510 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1514 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1515 damage = std::min(damage, b_hp);
1517 b_slowed |= a_slows_;
1519 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1520 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1534 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1535 damage = std::min(damage, a_hp);
1537 a_slowed |= b_slows_;
1539 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1540 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1553 iterations_a_hit_ += a_hit ? 1 : 0;
1554 iterations_b_hit_ += b_hit ? 1 : 0;
1556 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1564 void monte_carlo_combat_matrix::extract_results(
1565 summary_t& summary_a, summary_t& summary_b)
1568 summary_a[0] = std::vector<double>(num_rows());
1569 summary_b[0] = std::vector<double>(num_cols());
1571 if(plane_used(A_SLOWED)) {
1572 summary_a[1] = std::vector<double>(num_rows());
1575 if(plane_used(B_SLOWED)) {
1576 summary_b[1] = std::vector<double>(num_cols());
1579 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1580 if(!plane_used(
p)) {
1585 unsigned dst_a = (
p & 1) ? 1u : 0u;
1587 unsigned dst_b = (
p & 2) ? 1u : 0u;
1588 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1591 divide_all_elements(summary_a[0], static_cast<double>(NUM_ITERATIONS));
1592 divide_all_elements(summary_b[0], static_cast<double>(NUM_ITERATIONS));
1594 if(plane_used(A_SLOWED)) {
1595 divide_all_elements(summary_a[1], static_cast<double>(NUM_ITERATIONS));
1598 if(plane_used(B_SLOWED)) {
1599 divide_all_elements(summary_b[1], static_cast<double>(NUM_ITERATIONS));
1603 double monte_carlo_combat_matrix::get_a_hit_probability()
const 1605 return static_cast<double>(iterations_a_hit_) / static_cast<double>(NUM_ITERATIONS);
1608 double monte_carlo_combat_matrix::get_b_hit_probability()
const 1610 return static_cast<double>(iterations_b_hit_) / static_cast<double>(NUM_ITERATIONS);
1613 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const 1615 auto it = a_split_.begin();
1616 while(it != a_split_.end() && it->end_hp <= a_hp) {
1620 if(it == a_split_.end()) {
1627 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const 1629 auto it = b_split_.begin();
1630 while(it != b_split_.end() && it->end_hp <= b_hp) {
1634 if(it == b_split_.end()) {
1641 void monte_carlo_combat_matrix::scale_probabilities(
1642 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1644 if(divisor == 0.0) {
1650 if(source.empty()) {
1651 target.resize(singular_hp + 1u, 0.0);
1652 target[singular_hp] = 1.0;
1655 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) {
return prob / divisor; });
1658 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1661 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1663 for(
double&
e : vec) {
1671 : hp_dist(u.max_hp + 1, 0.0)
1716 void forced_levelup(std::vector<double>&
hp_dist)
1721 auto i = hp_dist.begin();
1723 for(++
i;
i != hp_dist.end(); ++
i) {
1728 hp_dist.back() = 1 - hp_dist.front();
1731 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1736 double scalefactor = 0;
1737 const double chance_to_survive = 1 - hp_dist.front();
1738 if(chance_to_survive > DBL_MIN) {
1739 scalefactor = 1 - kill_prob / chance_to_survive;
1742 auto i = hp_dist.begin();
1744 for(++
i;
i != hp_dist.end(); ++
i) {
1749 hp_dist.back() += kill_prob;
1760 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1762 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1766 prob_touched = std::max(prob_touched, 0.0);
1768 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1772 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1775 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1777 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1781 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1784 const double prob_survive_if_not_hit = 1.0;
1786 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1791 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1794 double prob_debuff = 0.0;
1797 prob_debuff += prob_already_debuffed_not_touched;
1799 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1803 prob_debuff += prob_already_debuffed_touched;
1805 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1812 }
else if(!kill_heals) {
1813 prob_debuff += prob_initially_healthy_touched;
1815 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1822 void round_prob_if_close_to_sure(
double& prob)
1826 }
else if(prob > 1.0 - 1.0
e-9) {
1835 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1837 const unsigned size = hp_dist.size();
1840 for(
unsigned i = 0;
i !=
size; ++
i) {
1841 if(hp_dist[
i] != 0.0) {
1858 unsigned int fight_complexity(
unsigned int num_slices,
1859 unsigned int opp_num_slices,
1863 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1880 unsigned opp_strikes,
1881 std::vector<double>& hp_dist,
1882 std::vector<double>& opp_hp_dist,
1883 double& self_not_hit,
1884 double& opp_not_hit,
1885 bool levelup_considered)
1891 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1892 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1894 if(opp_hp_dist.empty()) {
1896 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1897 opp_hp_dist[opp_stats.
hp] = 1.0;
1899 for(
unsigned int i = 0;
i < strikes; ++
i) {
1900 for(
int j =
i; j >= 0; j--) {
1901 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1902 double move = opp_hp_dist[src_index] * hit_chance;
1903 opp_hp_dist[src_index] -= move;
1904 opp_hp_dist[src_index - stats.
damage] += move;
1907 opp_not_hit *= 1.0 - hit_chance;
1911 for(
unsigned int i = 0;
i < strikes; ++
i) {
1912 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1913 double move = opp_hp_dist[j] * hit_chance;
1914 opp_hp_dist[j] -= move;
1915 opp_hp_dist[j - stats.
damage] += move;
1917 opp_not_hit *= 1.0 - hit_chance;
1923 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1924 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1926 if(hp_dist.empty()) {
1928 hp_dist = std::vector<double>(stats.
max_hp + 1);
1929 hp_dist[stats.
hp] = 1.0;
1930 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1931 for(
int j =
i; j >= 0; j--) {
1932 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1933 double move = hp_dist[src_index] * opp_hit_chance;
1934 hp_dist[src_index] -= move;
1935 hp_dist[src_index - opp_stats.
damage] += move;
1938 self_not_hit *= 1.0 - opp_hit_chance;
1942 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1943 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1944 double move = hp_dist[j] * opp_hit_chance;
1946 hp_dist[j - opp_stats.
damage] += move;
1949 self_not_hit *= 1.0 - opp_hit_chance;
1953 if(!levelup_considered) {
1958 forced_levelup(hp_dist);
1962 forced_levelup(opp_hp_dist);
1970 unsigned opp_strikes,
1971 std::vector<double>& hp_dist,
1972 std::vector<double>& opp_hp_dist,
1973 double& self_not_hit,
1974 double& opp_not_hit,
1975 bool levelup_considered)
1980 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1984 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1986 if(opp_hp_dist.empty()) {
1987 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1988 if(strikes == 1 && opp_stats.
hp > 0) {
1989 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1990 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
1991 opp_not_hit *= 1.0 - hit_chance;
1993 opp_hp_dist[opp_stats.
hp] = 1.0;
1997 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
1998 double move = opp_hp_dist[
i] * hit_chance;
1999 opp_hp_dist[
i] -= move;
2000 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2003 opp_not_hit *= 1.0 - hit_chance;
2008 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2009 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2011 if(hp_dist.empty()) {
2012 hp_dist = std::vector<double>(stats.
max_hp + 1);
2013 if(opp_strikes == 1 && stats.
hp > 0) {
2014 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2015 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2016 self_not_hit *= 1.0 - opp_hit_chance;
2018 hp_dist[stats.
hp] = 1.0;
2021 if(opp_strikes == 1) {
2022 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2023 double move = hp_dist[
i] * opp_hit_chance;
2025 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2028 self_not_hit *= 1.0 - opp_hit_chance;
2032 if(!levelup_considered) {
2037 forced_levelup(hp_dist);
2039 conditional_levelup(hp_dist, opp_hp_dist[0]);
2043 forced_levelup(opp_hp_dist);
2045 conditional_levelup(opp_hp_dist, hp_dist[0]);
2055 unsigned opp_strikes,
2057 summary_t& opp_summary,
2058 double& self_not_hit,
2059 double& opp_not_hit,
2060 bool levelup_considered,
2061 std::vector<combat_slice>
split,
2062 std::vector<combat_slice> opp_split,
2063 double initially_slowed_chance,
2064 double opp_initially_slowed_chance)
2066 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2067 unsigned max_attacks = std::max(strikes, opp_strikes);
2069 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2072 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2078 a_damage = a_slow_damage = opp_stats.
max_hp;
2082 b_damage = b_slow_damage = stats.
max_hp;
2085 const double original_self_not_hit = self_not_hit;
2086 const double original_opp_not_hit = opp_not_hit;
2088 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2089 double self_hit = 0.0;
2090 double opp_hit = 0.0;
2091 double self_hit_unknown = 1.0;
2092 double opp_hit_unknown = 1.0;
2095 std::unique_ptr<combat_matrix> m;
2096 if(mode == attack_prediction_mode::probability_calculation) {
2097 debug((
"Using exact probability calculations.\n"));
2099 probability_combat_matrix* pm =
new probability_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2100 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2101 a_damage, b_damage, a_slow_damage, b_slow_damage,
2106 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2108 debug((
"A strikes\n"));
2109 double b_already_dead = pm->dead_prob_b();
2110 pm->receive_blow_b(hit_chance);
2113 double first_hit = hit_chance * opp_hit_unknown;
2114 opp_hit += first_hit;
2115 opp_hit_unknown -= first_hit;
2116 double both_were_alive = 1.0 - b_already_dead - pm->dead_prob_a();
2117 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2118 self_hit_unknown *= (1.0 - this_hit_killed_b);
2120 if(
i < opp_strikes) {
2121 debug((
"B strikes\n"));
2122 double a_already_dead = pm->dead_prob_a();
2123 pm->receive_blow_a(opp_hit_chance);
2126 double first_hit = opp_hit_chance * self_hit_unknown;
2127 self_hit += first_hit;
2128 self_hit_unknown -= first_hit;
2129 double both_were_alive = 1.0 - a_already_dead - pm->dead_prob_b();
2130 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2131 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2135 debug((
"Combat ends:\n"));
2137 }
while(--rounds && pm->dead_prob() < 0.99);
2139 self_hit = std::min(self_hit, 1.0);
2140 opp_hit = std::min(opp_hit, 1.0);
2142 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2143 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2152 unsigned int plane = plane_index(stats, opp_stats);
2153 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2154 opp_not_hit = original_opp_not_hit * not_hit;
2156 if(opp_stats.
slows) {
2157 unsigned int plane = plane_index(stats, opp_stats);
2158 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2159 self_not_hit = original_self_not_hit * not_hit;
2162 debug((
"Using Monte Carlo simulation.\n"));
2164 monte_carlo_combat_matrix* mcm =
new monte_carlo_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2165 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2166 a_damage, b_damage, a_slow_damage, b_slow_damage,
2168 hit_chance, opp_hit_chance, split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2172 debug((
"Combat ends:\n"));
2175 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2176 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2187 if(levelup_considered) {
2189 m->forced_levelup_a();
2191 m->conditional_levelup_a();
2195 m->forced_levelup_b();
2197 m->conditional_levelup_b();
2202 m->extract_results(summary, opp_summary);
2212 unsigned opp_strikes,
2214 summary_t& opp_summary,
2215 double& self_not_hit,
2216 double& opp_not_hit,
2217 bool levelup_considered)
2223 && opp_summary[1].empty())
2225 if(strikes <= 1 && opp_strikes <= 1) {
2226 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2227 opp_not_hit, levelup_considered);
2228 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2229 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2230 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2231 opp_not_hit, levelup_considered);
2233 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2234 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2235 std::vector<combat_slice>(), 0.0, 0.0);
2238 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2239 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2240 std::vector<combat_slice>(), 0.0, 0.0);
2249 void init_slice_summary(
2250 std::vector<double>& dst,
const std::vector<double>& src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2257 const unsigned size = src.size();
2264 dst.resize(size, 0.0);
2265 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2266 dst[
i] = src[
i] / prob;
2274 void merge_slice_summary(std::vector<double>& dst,
const std::vector<double>& src,
double prob)
2276 const unsigned size = src.size();
2279 if(dst.size() <
size) {
2280 dst.resize(size, 0.0);
2284 for(
unsigned i = 0;
i !=
size; ++
i) {
2285 dst[
i] += src[
i] * prob;
2300 opponent.
fight(*
this, levelup_considered);
2304 #ifdef ATTACK_PREDICTION_DEBUG 2313 complex_fight(opponent, 1);
2314 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2316 opponent.
summary[0] = opp_prev;
2320 double self_not_hit = 1.0;
2321 double opp_not_hit = 1.0;
2324 double self_already_dead =
hp_dist[0];
2325 double opp_already_dead = opponent.
hp_dist[0];
2328 round_prob_if_close_to_sure(
slowed);
2329 round_prob_if_close_to_sure(opponent.
slowed);
2333 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2334 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2336 bool use_monte_carlo_simulation =
2340 if(use_monte_carlo_simulation) {
2343 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2346 }
else if(split.size() == 1 && opp_split.size() == 1) {
2349 opp_not_hit, levelup_considered);
2352 summary_t summary_result, opp_summary_result;
2358 for(
unsigned s = 0;
s != split.size(); ++
s) {
2359 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2360 const double sit_prob = split[
s].prob * opp_split[
t].prob;
2363 summary_t sit_summary, sit_opp_summary;
2364 init_slice_summary(sit_summary[0],
summary[0], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2365 init_slice_summary(sit_summary[1],
summary[1], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2366 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[t].end_hp,
2368 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[t].begin_hp, opp_split[t].end_hp,
2373 double sit_self_not_hit = sit_prob;
2374 double sit_opp_not_hit = sit_prob;
2376 do_fight(
u_, opponent.
u_, split[
s].strikes, opp_split[t].strikes, sit_summary, sit_opp_summary,
2377 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2380 self_not_hit += sit_self_not_hit;
2381 opp_not_hit += sit_opp_not_hit;
2382 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2383 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2384 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2385 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2390 summary[0].swap(summary_result[0]);
2391 summary[1].swap(summary_result[1]);
2392 opponent.
summary[0].swap(opp_summary_result[0]);
2393 opponent.
summary[1].swap(opp_summary_result[1]);
2398 assert(opponent.
summary[0].size() == opp_res.size());
2399 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2400 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2401 PLAIN_LOG <<
"Mismatch for " << i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i];
2405 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2406 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2407 PLAIN_LOG <<
"Mismatch for " << i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i];
2419 for(
unsigned int i = 0;
i <
size; ++
i)
2423 if(opponent.
summary[1].empty()) {
2427 opponent.
hp_dist.resize(size);
2428 for(
unsigned int i = 0;
i <
size; ++
i)
2433 double touched = 1.0 - self_not_hit;
2434 double opp_touched = 1.0 - opp_not_hit;
2446 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2467 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2476 #if defined(BENCHMARK) || defined(CHECK) 2479 static const unsigned int NUM_UNITS = 50;
2481 #ifdef ATTACK_PREDICTION_DEBUG 2484 std::ostringstream ss;
2487 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; " 2503 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2507 ss <<
"firststrike, ";
2510 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2512 std::cout << ss.rdbuf() << std::endl;
2520 #ifdef HUMAN_READABLE 2523 std::ostringstream ss;
2526 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter,
label,
2546 ss <<
"firststrike, ";
2549 std::cout << ss.rdbuf() << std::endl;
2552 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2554 int num_outputs = 0;
2555 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2557 if(num_outputs++ % 6 == 0) {
2563 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2569 #elif defined(CHECK) 2572 std::ostringstream ss;
2594 ss <<
"firststrike, ";
2597 std::cout << ss.rdbuf() << std::endl;
2600 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2602 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2608 #else // ... BENCHMARK 2614 void combatant::reset()
2616 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2623 summary[0] = std::vector<double>();
2624 summary[1] = std::vector<double>();
2627 static void run(
unsigned specific_battle)
2629 using std::chrono::duration_cast;
2630 using std::chrono::microseconds;
2635 unsigned int i, j, k, battle = 0;
2636 std::chrono::high_resolution_clock::time_point
start, end;
2638 for(i = 0; i < NUM_UNITS; ++
i) {
2639 unsigned alt = i + 74;
2642 unsigned max_hp = (i * 2) % 23 + (i * 3) % 14 + 25;
2643 unsigned hp = (alt * 5) % max_hp + 1;
2655 list_combatant(*stats[i], i + 1);
2658 start = std::chrono::high_resolution_clock::now();
2660 for(i = 0; i < NUM_UNITS; ++
i) {
2661 for(j = 0; j < NUM_UNITS; ++j) {
2666 for(k = 0; k < NUM_UNITS; ++k) {
2667 if(i == k || j == k) {
2672 if(specific_battle && battle != specific_battle) {
2680 u[
i]->print(
"Defender", battle, i + 1);
2681 u[j]->print(
"Attacker #1", battle, j + 1);
2682 u[k]->print(
"Attacker #2", battle, k + 1);
2691 end = std::chrono::high_resolution_clock::now();
2693 auto total = end -
start;
2696 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2697 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2698 printf(
"Time per calc = %li us\n", static_cast<long>(duration_cast<microseconds>(total).count())
2699 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2701 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2704 for(i = 0; i < NUM_UNITS; ++
i) {
2715 int add_to_argv = 4;
2716 int damage = atoi((*argv)[1]);
2717 int num_attacks = atoi((*argv)[2]);
2718 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2719 int hit_chance = atoi((*argv)[4]);
2722 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2723 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2727 char* max = strstr((*argv)[5],
"maxhp=");
2729 max_hp = atoi(max + strlen(
"maxhp="));
2730 if(max_hp < hitpoints) {
2731 PLAIN_LOG <<
"maxhp must be at least hitpoints.";
2736 if(strstr((*argv)[5],
"drain")) {
2738 PLAIN_LOG <<
"WARNING: drain specified without maxhp; assuming uninjured.";
2744 if(strstr((*argv)[5],
"slows")) {
2748 if(strstr((*argv)[5],
"slowed")) {
2752 if(strstr((*argv)[5],
"berserk")) {
2756 if(strstr((*argv)[5],
"firststrike")) {
2760 if(strstr((*argv)[5],
"swarm")) {
2762 PLAIN_LOG <<
"WARNING: swarm specified without maxhp; assuming uninjured.";
2770 *argv += add_to_argv;
2774 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2777 int main(
int argc,
char* argv[])
2784 run(argv[1] ? atoi(argv[1]) : 0);
2788 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" " 2789 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] " 2790 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...";
2794 def_stats = parse_unit(&argv);
2796 for(i = 0; argv[1] && i < 19; ++
i) {
2797 att_stats[
i] = parse_unit(&argv);
2803 for(i = 0; att[
i]; ++
i) {
2804 debug((
"Fighting next attacker\n"));
2808 def->print(
"Defender", 0, 0);
2809 for(i = 0; att[
i]; ++
i) {
2810 att[
i]->print(
"Attacker", 0, i + 1);
2813 for(i = 0; att[
i]; ++
i) {
2815 delete att_stats[
i];
2824 #endif // Standalone program
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...
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
std::array< std::vector< double >, 2 > summary
Summary of matrix used to calculate last battle (unslowed & slowed).
int main(int, char **argv)
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Various functions that implement attacks and attack calculations.
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
void clear(const std::string &key)
bool is_slowed
True if the unit is slowed at the beginning of the battle.
bool slows
Attack slows opponent when it hits.
int drain_constant
Base HP drained regardless of damage dealt.
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
bool poisons
Attack poisons opponent when it hits.
int damage
Effective damage of the weapon (all factors accounted for).
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
std::string label
What to show in the filter's drop-down list.
const battle_context_unit_stats & u_
EXIT_STATUS start(const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
unsigned int get_random_element(T first, T last)
This helper method selects a random element from a container of floating-point numbers.
combatant(const battle_context_unit_stats &u, const combatant *prev=nullptr)
Construct a combatant.
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
Structure describing the statistics of a unit involved in the battle.
bool damage_prediction_allow_monte_carlo_simulation()
bool get_random_bool(double probability)
This helper method returns true with the probability supplied as a parameter.
double slowed
Resulting chance we are slowed.
bool swarm
Attack has swarm special.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
static map_location::DIRECTION s
std::vector< std::string > names
static const unsigned int MONTE_CARLO_SIMULATION_THRESHOLD
bool firststrike
Attack has firststrike special.
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
int drain_percent
Percentage of damage recovered as health.
std::vector< std::string > split(const config_attribute_value &val)
Standard logging facilities (interface).
unsigned int num_blows
Effective number of blows, takes swarm into account.
unsigned int max_hp
Maximum hitpoints of the unit.
bool is_attacker
True if the unit is the attacker.
unsigned int max_experience
bool petrifies
Attack petrifies opponent when it hits.
static rng & default_instance()
bool drains
Attack drains opponent when it hits.
double poisoned
Resulting chance we are poisoned.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
this class does not give synced random results derived classes might do.