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 auto a_hp =
static_cast<unsigned int>(rng.
get_random_element(a_initial.begin(), a_initial.end()));
1507 auto b_hp =
static_cast<unsigned int>(rng.
get_random_element(b_initial.begin(), b_initial.end()));
1508 unsigned int a_strikes = calc_blows_a(a_hp);
1509 unsigned int b_strikes = calc_blows_b(b_hp);
1511 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1512 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1516 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1517 damage = std::min(damage, b_hp);
1519 b_slowed |= a_slows_;
1521 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1522 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1536 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1537 damage = std::min(damage, a_hp);
1539 a_slowed |= b_slows_;
1541 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1542 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1555 iterations_a_hit_ += a_hit ? 1 : 0;
1556 iterations_b_hit_ += b_hit ? 1 : 0;
1558 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1566 void monte_carlo_combat_matrix::extract_results(
1567 summary_t& summary_a, summary_t& summary_b)
1570 summary_a[0] = std::vector<double>(num_rows());
1571 summary_b[0] = std::vector<double>(num_cols());
1573 if(plane_used(A_SLOWED)) {
1574 summary_a[1] = std::vector<double>(num_rows());
1577 if(plane_used(B_SLOWED)) {
1578 summary_b[1] = std::vector<double>(num_cols());
1581 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1582 if(!plane_used(
p)) {
1587 unsigned dst_a = (
p & 1) ? 1u : 0u;
1589 unsigned dst_b = (
p & 2) ? 1u : 0u;
1590 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1593 divide_all_elements(summary_a[0],
static_cast<double>(NUM_ITERATIONS));
1594 divide_all_elements(summary_b[0],
static_cast<double>(NUM_ITERATIONS));
1596 if(plane_used(A_SLOWED)) {
1597 divide_all_elements(summary_a[1],
static_cast<double>(NUM_ITERATIONS));
1600 if(plane_used(B_SLOWED)) {
1601 divide_all_elements(summary_b[1],
static_cast<double>(NUM_ITERATIONS));
1605 double monte_carlo_combat_matrix::get_a_hit_probability()
const
1607 return static_cast<double>(iterations_a_hit_) /
static_cast<double>(NUM_ITERATIONS);
1610 double monte_carlo_combat_matrix::get_b_hit_probability()
const
1612 return static_cast<double>(iterations_b_hit_) /
static_cast<double>(NUM_ITERATIONS);
1615 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const
1617 auto it = a_split_.begin();
1618 while(it != a_split_.end() && it->end_hp <= a_hp) {
1622 if(it == a_split_.end()) {
1629 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const
1631 auto it = b_split_.begin();
1632 while(it != b_split_.end() && it->end_hp <= b_hp) {
1636 if(it == b_split_.end()) {
1643 void monte_carlo_combat_matrix::scale_probabilities(
1644 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1646 if(divisor == 0.0) {
1652 if(source.empty()) {
1653 target.resize(singular_hp + 1u, 0.0);
1654 target[singular_hp] = 1.0;
1657 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) { return prob / divisor; });
1660 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1663 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1665 for(
double&
e : vec) {
1673 : hp_dist(u.max_hp + 1, 0.0)
1704 : hp_dist(that.hp_dist)
1705 , untouched(that.untouched)
1716 enum class attack_prediction_mode { probability_calculation, monte_carlo_simulation };
1718 void forced_levelup(std::vector<double>& hp_dist)
1723 auto i = hp_dist.begin();
1725 for(++
i;
i != hp_dist.end(); ++
i) {
1730 hp_dist.back() = 1 - hp_dist.front();
1733 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1738 double scalefactor = 0;
1739 const double chance_to_survive = 1 - hp_dist.front();
1740 if(chance_to_survive > DBL_MIN) {
1741 scalefactor = 1 - kill_prob / chance_to_survive;
1744 auto i = hp_dist.begin();
1746 for(++
i;
i != hp_dist.end(); ++
i) {
1751 hp_dist.back() += kill_prob;
1762 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1764 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1768 prob_touched = std::max(prob_touched, 0.0);
1770 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1774 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1777 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1779 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1783 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1786 const double prob_survive_if_not_hit = 1.0;
1788 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1793 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1796 double prob_debuff = 0.0;
1799 prob_debuff += prob_already_debuffed_not_touched;
1801 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1805 prob_debuff += prob_already_debuffed_touched;
1807 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1814 }
else if(!kill_heals) {
1815 prob_debuff += prob_initially_healthy_touched;
1817 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1824 void round_prob_if_close_to_sure(
double& prob)
1828 }
else if(prob > 1.0 - 1.0e-9) {
1837 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1839 const unsigned size = hp_dist.size();
1842 for(
unsigned i = 0;
i !=
size; ++
i) {
1843 if(hp_dist[
i] != 0.0) {
1860 unsigned int fight_complexity(
unsigned int num_slices,
1861 unsigned int opp_num_slices,
1865 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1866 * ((opp_stats.slows || stats.
is_slowed) ? 2 : 1) * stats.
max_hp * opp_stats.max_hp;
1882 unsigned opp_strikes,
1883 std::vector<double>& hp_dist,
1884 std::vector<double>& opp_hp_dist,
1885 double& self_not_hit,
1886 double& opp_not_hit,
1887 bool levelup_considered)
1893 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1894 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1896 if(opp_hp_dist.empty()) {
1898 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1899 opp_hp_dist[opp_stats.
hp] = 1.0;
1901 for(
unsigned int i = 0;
i < strikes; ++
i) {
1902 for(
int j =
i; j >= 0; j--) {
1903 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1904 double move = opp_hp_dist[src_index] * hit_chance;
1905 opp_hp_dist[src_index] -= move;
1906 opp_hp_dist[src_index - stats.
damage] += move;
1909 opp_not_hit *= 1.0 - hit_chance;
1913 for(
unsigned int i = 0;
i < strikes; ++
i) {
1914 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1915 double move = opp_hp_dist[j] * hit_chance;
1916 opp_hp_dist[j] -= move;
1917 opp_hp_dist[j - stats.
damage] += move;
1919 opp_not_hit *= 1.0 - hit_chance;
1925 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1926 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1928 if(hp_dist.empty()) {
1930 hp_dist = std::vector<double>(stats.
max_hp + 1);
1931 hp_dist[stats.
hp] = 1.0;
1932 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1933 for(
int j =
i; j >= 0; j--) {
1934 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1935 double move = hp_dist[src_index] * opp_hit_chance;
1936 hp_dist[src_index] -= move;
1937 hp_dist[src_index - opp_stats.
damage] += move;
1940 self_not_hit *= 1.0 - opp_hit_chance;
1944 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1945 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1946 double move = hp_dist[j] * opp_hit_chance;
1948 hp_dist[j - opp_stats.
damage] += move;
1951 self_not_hit *= 1.0 - opp_hit_chance;
1955 if(!levelup_considered) {
1960 forced_levelup(hp_dist);
1964 forced_levelup(opp_hp_dist);
1972 unsigned opp_strikes,
1973 std::vector<double>& hp_dist,
1974 std::vector<double>& opp_hp_dist,
1975 double& self_not_hit,
1976 double& opp_not_hit,
1977 bool levelup_considered)
1982 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1986 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1988 if(opp_hp_dist.empty()) {
1989 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1990 if(strikes == 1 && opp_stats.
hp > 0) {
1991 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1992 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
1993 opp_not_hit *= 1.0 - hit_chance;
1995 opp_hp_dist[opp_stats.
hp] = 1.0;
1999 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
2000 double move = opp_hp_dist[
i] * hit_chance;
2001 opp_hp_dist[
i] -= move;
2002 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2005 opp_not_hit *= 1.0 - hit_chance;
2010 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2011 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2013 if(hp_dist.empty()) {
2014 hp_dist = std::vector<double>(stats.
max_hp + 1);
2015 if(opp_strikes == 1 && stats.
hp > 0) {
2016 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2017 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2018 self_not_hit *= 1.0 - opp_hit_chance;
2020 hp_dist[stats.
hp] = 1.0;
2023 if(opp_strikes == 1) {
2024 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2025 double move = hp_dist[
i] * opp_hit_chance;
2027 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2030 self_not_hit *= 1.0 - opp_hit_chance;
2034 if(!levelup_considered) {
2039 forced_levelup(hp_dist);
2041 conditional_levelup(hp_dist, opp_hp_dist[0]);
2045 forced_levelup(opp_hp_dist);
2047 conditional_levelup(opp_hp_dist, hp_dist[0]);
2053 void complex_fight(attack_prediction_mode mode,
2057 unsigned opp_strikes,
2059 summary_t& opp_summary,
2060 double& self_not_hit,
2061 double& opp_not_hit,
2062 bool levelup_considered,
2063 std::vector<combat_slice>
split,
2064 std::vector<combat_slice> opp_split,
2065 double initially_slowed_chance,
2066 double opp_initially_slowed_chance)
2068 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2069 unsigned max_attacks = std::max(strikes, opp_strikes);
2071 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2074 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2080 a_damage = a_slow_damage = opp_stats.
max_hp;
2084 b_damage = b_slow_damage = stats.
max_hp;
2087 const double original_self_not_hit = self_not_hit;
2088 const double original_opp_not_hit = opp_not_hit;
2090 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2091 double self_hit = 0.0;
2092 double opp_hit = 0.0;
2093 double self_hit_unknown = 1.0;
2094 double opp_hit_unknown = 1.0;
2097 std::unique_ptr<combat_matrix> m;
2098 if(mode == attack_prediction_mode::probability_calculation) {
2099 debug((
"Using exact probability calculations.\n"));
2101 probability_combat_matrix* pm =
new probability_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2102 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2103 a_damage, b_damage, a_slow_damage, b_slow_damage,
2108 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2110 debug((
"A strikes\n"));
2111 double b_already_dead = pm->dead_prob_b();
2112 pm->receive_blow_b(hit_chance);
2115 double first_hit = hit_chance * opp_hit_unknown;
2116 opp_hit += first_hit;
2117 opp_hit_unknown -= first_hit;
2118 double both_were_alive = 1.0 - b_already_dead - pm->dead_prob_a();
2119 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2120 self_hit_unknown *= (1.0 - this_hit_killed_b);
2122 if(
i < opp_strikes) {
2123 debug((
"B strikes\n"));
2124 double a_already_dead = pm->dead_prob_a();
2125 pm->receive_blow_a(opp_hit_chance);
2128 double first_hit = opp_hit_chance * self_hit_unknown;
2129 self_hit += first_hit;
2130 self_hit_unknown -= first_hit;
2131 double both_were_alive = 1.0 - a_already_dead - pm->dead_prob_b();
2132 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2133 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2137 debug((
"Combat ends:\n"));
2139 }
while(--rounds && pm->dead_prob() < 0.99);
2141 self_hit = std::min(self_hit, 1.0);
2142 opp_hit = std::min(opp_hit, 1.0);
2144 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2145 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2154 unsigned int plane = plane_index(stats, opp_stats);
2155 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2156 opp_not_hit = original_opp_not_hit * not_hit;
2158 if(opp_stats.
slows) {
2159 unsigned int plane = plane_index(stats, opp_stats);
2160 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2161 self_not_hit = original_self_not_hit * not_hit;
2164 debug((
"Using Monte Carlo simulation.\n"));
2166 monte_carlo_combat_matrix* mcm =
new monte_carlo_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2167 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2168 a_damage, b_damage, a_slow_damage, b_slow_damage,
2170 hit_chance, opp_hit_chance,
split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2174 debug((
"Combat ends:\n"));
2177 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2178 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2189 if(levelup_considered) {
2191 m->forced_levelup_a();
2193 m->conditional_levelup_a();
2197 m->forced_levelup_b();
2199 m->conditional_levelup_b();
2204 m->extract_results(summary, opp_summary);
2214 unsigned opp_strikes,
2216 summary_t& opp_summary,
2217 double& self_not_hit,
2218 double& opp_not_hit,
2219 bool levelup_considered)
2225 && opp_summary[1].empty())
2227 if(strikes <= 1 && opp_strikes <= 1) {
2228 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2229 opp_not_hit, levelup_considered);
2230 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2231 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2232 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2233 opp_not_hit, levelup_considered);
2235 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2236 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2237 std::vector<combat_slice>(), 0.0, 0.0);
2240 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2241 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2242 std::vector<combat_slice>(), 0.0, 0.0);
2251 void init_slice_summary(
2252 std::vector<double>& dst,
const std::vector<double>& src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2259 const unsigned size = src.size();
2266 dst.resize(
size, 0.0);
2267 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2268 dst[
i] = src[
i] / prob;
2276 void merge_slice_summary(std::vector<double>& dst,
const std::vector<double>& src,
double prob)
2278 const unsigned size = src.size();
2281 if(dst.size() <
size) {
2282 dst.resize(
size, 0.0);
2286 for(
unsigned i = 0;
i !=
size; ++
i) {
2287 dst[
i] += src[
i] * prob;
2302 opponent.
fight(*
this, levelup_considered);
2306 #ifdef ATTACK_PREDICTION_DEBUG
2315 complex_fight(opponent, 1);
2316 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2318 opponent.
summary[0] = opp_prev;
2322 double self_not_hit = 1.0;
2323 double opp_not_hit = 1.0;
2326 double self_already_dead =
hp_dist[0];
2327 double opp_already_dead = opponent.
hp_dist[0];
2330 round_prob_if_close_to_sure(
slowed);
2331 round_prob_if_close_to_sure(opponent.
slowed);
2335 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2336 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2338 bool use_monte_carlo_simulation =
2342 if(use_monte_carlo_simulation) {
2345 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2348 }
else if(
split.size() == 1 && opp_split.size() == 1) {
2351 opp_not_hit, levelup_considered);
2354 summary_t summary_result, opp_summary_result;
2360 for(
unsigned s = 0;
s !=
split.size(); ++
s) {
2361 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2362 const double sit_prob =
split[
s].prob * opp_split[
t].prob;
2365 summary_t sit_summary, sit_opp_summary;
2368 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2370 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[
t].begin_hp, opp_split[
t].end_hp,
2375 double sit_self_not_hit = sit_prob;
2376 double sit_opp_not_hit = sit_prob;
2378 do_fight(
u_, opponent.
u_,
split[
s].strikes, opp_split[
t].strikes, sit_summary, sit_opp_summary,
2379 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2382 self_not_hit += sit_self_not_hit;
2383 opp_not_hit += sit_opp_not_hit;
2384 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2385 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2386 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2387 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2392 summary[0].swap(summary_result[0]);
2393 summary[1].swap(summary_result[1]);
2394 opponent.
summary[0].swap(opp_summary_result[0]);
2395 opponent.
summary[1].swap(opp_summary_result[1]);
2400 assert(opponent.
summary[0].size() == opp_res.size());
2401 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2402 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2403 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i];
2407 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2408 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2409 PLAIN_LOG <<
"Mismatch for " <<
i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i];
2421 for(
unsigned int i = 0;
i <
size; ++
i)
2425 if(opponent.
summary[1].empty()) {
2430 for(
unsigned int i = 0;
i <
size; ++
i)
2435 double touched = 1.0 - self_not_hit;
2436 double opp_touched = 1.0 - opp_not_hit;
2448 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2469 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2478 #if defined(BENCHMARK) || defined(CHECK)
2481 static const unsigned int NUM_UNITS = 50;
2483 #ifdef ATTACK_PREDICTION_DEBUG
2486 std::ostringstream ss;
2489 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; "
2505 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2509 ss <<
"firststrike, ";
2512 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2514 std::cout << ss.rdbuf() << std::endl;
2522 #ifdef HUMAN_READABLE
2525 std::ostringstream ss;
2528 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter,
label,
2548 ss <<
"firststrike, ";
2551 std::cout << ss.rdbuf() << std::endl;
2554 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2556 int num_outputs = 0;
2557 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2559 if(num_outputs++ % 6 == 0) {
2565 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2571 #elif defined(CHECK)
2574 std::ostringstream ss;
2596 ss <<
"firststrike, ";
2599 std::cout << ss.rdbuf() << std::endl;
2602 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2604 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2616 void combatant::reset()
2618 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2625 summary[0] = std::vector<double>();
2626 summary[1] = std::vector<double>();
2629 static void run(
unsigned specific_battle)
2631 using std::chrono::duration_cast;
2632 using std::chrono::microseconds;
2637 unsigned int i, j, k, battle = 0;
2638 std::chrono::high_resolution_clock::time_point
start,
end;
2640 for(
i = 0;
i < NUM_UNITS; ++
i) {
2641 unsigned alt =
i + 74;
2644 unsigned max_hp = (
i * 2) % 23 + (
i * 3) % 14 + 25;
2645 unsigned hp = (alt * 5) % max_hp + 1;
2657 list_combatant(*stats[
i],
i + 1);
2660 start = std::chrono::high_resolution_clock::now();
2662 for(
i = 0;
i < NUM_UNITS; ++
i) {
2663 for(j = 0; j < NUM_UNITS; ++j) {
2668 for(k = 0; k < NUM_UNITS; ++k) {
2669 if(
i == k || j == k) {
2674 if(specific_battle && battle != specific_battle) {
2682 u[
i]->print(
"Defender", battle,
i + 1);
2683 u[j]->print(
"Attacker #1", battle, j + 1);
2684 u[k]->print(
"Attacker #2", battle, k + 1);
2693 end = std::chrono::high_resolution_clock::now();
2698 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2699 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2700 printf(
"Time per calc = %li us\n",
static_cast<long>(duration_cast<microseconds>(total).count())
2701 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2703 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2706 for(
i = 0;
i < NUM_UNITS; ++
i) {
2717 int add_to_argv = 4;
2718 int damage = atoi((*argv)[1]);
2719 int num_attacks = atoi((*argv)[2]);
2720 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2721 int hit_chance = atoi((*argv)[4]);
2724 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2725 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2729 char* max = strstr((*argv)[5],
"maxhp=");
2731 max_hp = atoi(max + strlen(
"maxhp="));
2732 if(max_hp < hitpoints) {
2733 PLAIN_LOG <<
"maxhp must be at least hitpoints.";
2738 if(strstr((*argv)[5],
"drain")) {
2740 PLAIN_LOG <<
"WARNING: drain specified without maxhp; assuming uninjured.";
2746 if(strstr((*argv)[5],
"slows")) {
2750 if(strstr((*argv)[5],
"slowed")) {
2754 if(strstr((*argv)[5],
"berserk")) {
2758 if(strstr((*argv)[5],
"firststrike")) {
2762 if(strstr((*argv)[5],
"swarm")) {
2764 PLAIN_LOG <<
"WARNING: swarm specified without maxhp; assuming uninjured.";
2772 *argv += add_to_argv;
2776 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2779 int main(
int argc,
char* argv[])
2786 run(argv[1] ? atoi(argv[1]) : 0);
2790 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" "
2791 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] "
2792 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...";
2796 def_stats = parse_unit(&argv);
2798 for(
i = 0; argv[1] &&
i < 19; ++
i) {
2799 att_stats[
i] = parse_unit(&argv);
2805 for(
i = 0; att[
i]; ++
i) {
2806 debug((
"Fighting next attacker\n"));
2810 def->print(
"Defender", 0, 0);
2811 for(
i = 0; att[
i]; ++
i) {
2812 att[
i]->print(
"Attacker", 0,
i + 1);
2815 for(
i = 0; att[
i]; ++
i) {
2817 delete att_stats[
i];
Various functions that implement attacks and attack calculations.
std::vector< std::string > names
this class does not give synced random results derived classes might do.
static rng & default_instance()
T::difference_type get_random_element(T first, T last)
This helper method selects a random element from a container of floating-point numbers.
bool get_random_bool(double probability)
This helper method returns true with the probability supplied as a parameter.
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
std::string label
What to show in the filter's drop-down list.
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
Standard logging facilities (interface).
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.
void clear(const std::string &key)
bool damage_prediction_allow_monte_carlo_simulation()
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
std::vector< std::string > split(const config_attribute_value &val)
int main(int, char **argv)
Structure describing the statistics of a unit involved in the battle.
bool slows
Attack slows opponent when it hits.
unsigned int num_blows
Effective number of blows, takes swarm into account.
bool petrifies
Attack petrifies opponent when it hits.
int drain_percent
Percentage of damage recovered as health.
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
unsigned int max_experience
bool drains
Attack drains opponent when it hits.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
bool swarm
Attack has swarm special.
bool is_attacker
True if the unit is the attacker.
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
bool is_slowed
True if the unit is slowed at the beginning of the battle.
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp instead of the recorded hp.
unsigned int max_hp
Maximum hitpoints of the unit.
int damage
Effective damage of the weapon (all factors accounted for).
bool poisons
Attack poisons opponent when it hits.
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
int drain_constant
Base HP drained regardless of damage dealt.
bool firststrike
Attack has firststrike special.
double slowed
Resulting chance we are slowed.
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
double poisoned
Resulting chance we are poisoned.
static const unsigned int MONTE_CARLO_SIMULATION_THRESHOLD
const battle_context_unit_stats & u_
std::array< std::vector< double >, 2 > summary
Summary of matrix used to calculate last battle (unslowed & slowed).
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
combatant(const battle_context_unit_stats &u, const combatant *prev=nullptr)
Construct a combatant.
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
static map_location::DIRECTION s