54 #if defined(BENCHMARK) || defined(CHECK) 60 #ifdef ATTACK_PREDICTION_DEBUG 61 #define debug(x) printf x 66 #ifdef ATTACK_PREDICTION_DEBUG 72 std::ostringstream ss;
74 ss <<
"==================================";
78 <<
"\n" <<
"is_slowed: " << stats.
is_slowed 79 <<
"\n" <<
"slows: " << stats.
slows 80 <<
"\n" <<
"drains: " << stats.
drains 81 <<
"\n" <<
"petrifies: " << stats.
petrifies 82 <<
"\n" <<
"poisons: " << stats.
poisons 84 <<
"\n" <<
"swarm: " << stats.
swarm 87 <<
"\n" <<
"rounds: " << stats.
rounds 89 <<
"\n" <<
"hp: " << stats.
hp 90 <<
"\n" <<
"max_hp: " << stats.
max_hp 92 <<
"\n" <<
"damage: " << stats.
damage 96 <<
"\n" <<
"num_blows: " << stats.
num_blows 97 <<
"\n" <<
"swarm_min: " << stats.
swarm_min 98 <<
"\n" <<
"swarm_max: " << stats.
swarm_max 101 std::cout << ss.rdbuf() << std::endl;
108 using summary_t = std::array<std::vector<double>, 2>;
127 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes);
128 combat_slice(
const summary_t& src_summary,
unsigned num_strikes);
134 combat_slice::combat_slice(
135 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes)
139 , strikes(num_strikes)
141 if(src_summary[0].empty()) {
148 if(end > src_summary[0].
size()) {
149 end = src_summary[0].size();
153 for(
unsigned i = begin;
i < end; ++
i) {
154 prob += src_summary[0][
i];
157 if(!src_summary[1].empty()) {
158 for(
unsigned i = begin;
i < end; ++
i) {
159 prob += src_summary[1][
i];
168 combat_slice::combat_slice(
const summary_t& src_summary,
unsigned num_strikes)
170 , end_hp(src_summary[0].
size())
172 , strikes(num_strikes)
184 unsigned old_strikes = stats.
calc_blows(cur_hp);
188 while(++cur_hp <= stats.
max_hp) {
201 std::vector<combat_slice> split_summary(
204 std::vector<combat_slice> result;
208 result.emplace_back(summary, unit_stats.
num_blows);
212 debug((
"Slicing:\n"));
214 unsigned cur_end = 0;
217 const unsigned cur_begin = cur_end;
218 cur_end = hp_for_next_attack(cur_begin, unit_stats);
221 combat_slice slice(summary, cur_begin, cur_end, unit_stats.
calc_blows(cur_begin));
222 if(slice.prob != 0.0) {
223 result.push_back(slice);
224 debug((
"\t%2u-%2u hp; strikes: %u; probability: %6.2f\n", cur_begin, cur_end, slice.strikes,
225 slice.prob * 100.0));
227 }
while(cur_end <= unit_stats.
max_hp);
243 prob_matrix(
unsigned int a_max,
249 const summary_t& a_initial,
250 const summary_t& b_initial);
253 void shift_cols(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
255 void shift_rows(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
258 void move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col);
260 void move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row);
263 void merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row);
264 void merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row);
265 void merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col);
266 void merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col);
272 void record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed);
275 static unsigned int plane_index(
bool a_slowed,
bool b_slowed)
277 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
281 double prob_of_zero(
bool check_a,
bool check_b)
const;
283 double row_sum(
unsigned plane,
unsigned row)
const;
285 double col_sum(
unsigned plane,
unsigned column)
const;
287 void sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const;
290 bool plane_used(
unsigned p)
const 292 return p < NUM_PLANES && plane_[
p] !=
nullptr;
295 unsigned int num_rows()
const 299 unsigned int num_cols()
const 319 std::unique_ptr<double[]> new_plane()
const;
321 void initialize_plane(
unsigned plane,
324 const std::vector<double>& a_initial,
325 const std::vector<double>& b_initial);
327 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial);
329 double& val(
unsigned plane,
unsigned row,
unsigned col);
330 const double& val(
unsigned plane,
unsigned row,
unsigned col)
const;
333 void xfer(
unsigned dst_plane,
341 void xfer(
unsigned dst_plane,
348 void shift_cols_in_row(
unsigned dst,
351 const std::vector<unsigned>& cols,
357 void shift_rows_in_col(
unsigned dst,
360 const std::vector<unsigned>& rows,
368 const unsigned int rows_, cols_;
369 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
373 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
389 prob_matrix::prob_matrix(
unsigned int a_max,
395 const summary_t& a_initial,
396 const summary_t& b_initial)
404 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
405 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
408 for(
unsigned plane = 0; plane != NUM_PLANES; ++plane) {
409 used_rows_[plane].insert(0u);
410 used_cols_[plane].insert(0u);
414 need_a_slowed = need_a_slowed || !a_initial[1].empty();
415 need_b_slowed = need_b_slowed || !b_initial[1].empty();
418 plane_[NEITHER_SLOWED] = new_plane();
419 plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
420 plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
421 plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ?
nullptr : new_plane();
424 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
426 if(!a_initial[1].empty()) {
427 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
430 if(!b_initial[1].empty()) {
431 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
434 if(!a_initial[1].empty() && !b_initial[1].empty()) {
435 initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
439 if(!a_initial[0].empty()) {
440 debug((
"A has fought before (or is slowed).\n"));
444 if(!b_initial[0].empty()) {
445 debug((
"B has fought before (or is slowed).\n"));
451 std::unique_ptr<double[]> prob_matrix::new_plane()
const 453 const unsigned int size = rows_ * cols_;
454 std::unique_ptr<double[]> res(
new double[size]);
455 std::fill_n(res.get(),
size, 0);
468 void prob_matrix::initialize_plane(
unsigned plane,
471 const std::vector<double>& a_initial,
472 const std::vector<double>& b_initial)
474 if(!a_initial.empty()) {
475 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
477 for(
unsigned row = 0; row < row_count; ++row) {
478 if(a_initial[row] != 0.0) {
479 used_rows_[plane].insert(row);
480 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
484 used_rows_[plane].insert(a_cur);
486 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
499 void prob_matrix::initialize_row(
500 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
502 if(!b_initial.empty()) {
503 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
505 for(
unsigned col = 0; col < col_count; ++col) {
506 if(b_initial[col] != 0.0) {
507 used_cols_[plane].insert(col);
508 val(plane, row, col) = row_prob * b_initial[col];
513 used_cols_[plane].insert(b_cur);
514 val(plane, row, b_cur) = row_prob;
518 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
522 return plane_[plane][row * cols_ + col];
525 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const 529 return plane_[plane][row * cols_ + col];
536 void prob_matrix::xfer(
unsigned dst_plane,
544 double& src = val(src_plane, row_src, col_src);
546 double diff = src * prob;
549 double& dst = val(dst_plane, row_dst, col_dst);
552 used_rows_[dst_plane].insert(row_dst);
553 used_cols_[dst_plane].insert(col_dst);
557 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
558 src_plane == NEITHER_SLOWED
560 : src_plane == A_SLOWED
562 : src_plane == B_SLOWED
564 : src_plane == BOTH_SLOWED
569 dst_plane == NEITHER_SLOWED
571 : dst_plane == A_SLOWED
573 : dst_plane == B_SLOWED
575 : dst_plane == BOTH_SLOWED
586 void prob_matrix::xfer(
587 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
589 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
593 double& src = val(src_plane, row_src, col_src);
595 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
596 src_plane == NEITHER_SLOWED
598 : src_plane == A_SLOWED
600 : src_plane == B_SLOWED
602 : src_plane == BOTH_SLOWED
606 dst_plane == NEITHER_SLOWED
608 : dst_plane == A_SLOWED
610 : dst_plane == B_SLOWED
612 : dst_plane == BOTH_SLOWED
618 double& dst = val(dst_plane, row_dst, col_dst);
621 used_rows_[dst_plane].insert(row_dst);
622 used_cols_[dst_plane].insert(col_dst);
634 void prob_matrix::shift_cols_in_row(
unsigned dst,
637 const std::vector<unsigned>& cols,
645 int row_i =
static_cast<int>(row);
646 int max_row =
static_cast<int>(rows_) - 1;
652 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
655 int col_i =
static_cast<int>(cols[col_x]);
656 int drain_amount = col_i * drain_percent / 100 + drain_constant;
657 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
658 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
662 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
663 for(; col_x < cols.size(); ++col_x) {
664 xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
674 void prob_matrix::shift_cols(
675 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
677 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
679 if(drain_constant || drain_percent) {
680 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
685 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
686 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
692 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
693 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
697 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
698 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
707 void prob_matrix::shift_rows_in_col(
unsigned dst,
710 const std::vector<unsigned>& rows,
718 int col_i =
static_cast<int>(col);
719 int max_col =
static_cast<int>(cols_) - 1;
725 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
728 int row_i =
static_cast<int>(rows[row_x]);
729 int drain_amount = row_i * drain_percent / 100 + drain_constant;
730 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
731 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
735 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
736 for(; row_x < rows.size(); ++row_x) {
737 xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
747 void prob_matrix::shift_rows(
748 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
750 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
752 if(drain_constant || drain_percent) {
753 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
758 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
759 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
765 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
766 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
769 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
770 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
778 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
781 for(
const unsigned& row : used_rows_[s_plane]) {
782 xfer(d_plane, s_plane, row, d_col, row, s_col);
789 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
792 for(
const unsigned& col : used_cols_[s_plane]) {
793 xfer(d_plane, s_plane, d_row, col, s_row, col);
801 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
803 auto rows_end = used_rows_[s_plane].end();
804 auto row_it = used_rows_[s_plane].begin();
807 for(++row_it; row_it != rows_end; ++row_it) {
808 xfer(d_plane, s_plane, d_row, col, *row_it, col);
816 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
818 auto rows_end = used_rows_[s_plane].end();
819 auto row_it = used_rows_[s_plane].begin();
822 for(++row_it; row_it != rows_end; ++row_it) {
823 for(
const unsigned& col : used_cols_[s_plane]) {
824 xfer(d_plane, s_plane, d_row, col, *row_it, col);
833 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
835 auto cols_end = used_cols_[s_plane].end();
836 auto col_it = used_cols_[s_plane].begin();
839 for(++col_it; col_it != cols_end; ++col_it) {
840 xfer(d_plane, s_plane, row, d_col, row, *col_it);
848 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
850 auto cols_end = used_cols_[s_plane].end();
852 auto cols_begin =
std::next(used_cols_[s_plane].begin());
855 for(
const unsigned row : used_rows_[s_plane]) {
856 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
857 xfer(d_plane, s_plane, row, d_col, row, *col_it);
867 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
872 if(used_rows_[
p].empty()) {
877 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].begin(), used_rows_[
p].end());
878 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
879 for(
unsigned int c = 0u;
c < cols_; ++
c) {
880 plane_[
p][r * cols_ +
c] = 0.0;
884 used_rows_[
p].clear();
885 used_cols_[
p].clear();
891 used_rows_[
p].insert(0u);
892 used_cols_[
p].insert(0u);
899 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
901 assert(a_hp <= rows_);
902 assert(b_hp <= cols_);
903 unsigned int plane = plane_index(a_slowed, b_slowed);
904 ++val(plane, a_hp, b_hp);
905 used_rows_[plane].insert(a_hp);
906 used_cols_[plane].insert(b_hp);
912 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const 916 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
923 for(
const unsigned& row : used_rows_[
p]) {
924 prob += val(p, row, 0);
930 for(
const unsigned& col : used_cols_[
p]) {
931 prob += val(p, 0, col);
944 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const 946 if(!plane_used(plane)) {
951 for(
unsigned col : used_cols_[plane]) {
952 sum += val(plane, row, col);
960 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const 962 if(!plane_used(plane)) {
967 for(
unsigned row : used_rows_[plane]) {
968 sum += val(plane, row, column);
978 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const 980 for(
const unsigned& row : used_rows_[plane]) {
981 for(
const unsigned& col : used_cols_[plane]) {
982 const double& prob = val(plane, row, col);
983 row_sums[row] += prob;
984 col_sums[col] += prob;
989 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG) 990 void prob_matrix::dump()
const 992 unsigned int row, col, m;
993 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
995 for(m = 0; m < NUM_PLANES; ++m) {
1001 for(row = 0; row < rows_; ++row) {
1003 for(col = 0; col < cols_; ++col) {
1004 debug((
"%4.3g ", val(m, row, col) * 100));
1012 void prob_matrix::dump()
const 1022 class combat_matrix :
protected prob_matrix
1025 combat_matrix(
unsigned int a_max_hp,
1026 unsigned int b_max_hp,
1029 const summary_t& a_summary,
1030 const summary_t& b_summary,
1033 unsigned int a_damage,
1034 unsigned int b_damage,
1035 unsigned int a_slow_damage,
1036 unsigned int b_slow_damage,
1037 int a_drain_percent,
1038 int b_drain_percent,
1039 int a_drain_constant,
1040 int b_drain_constant);
1042 virtual ~combat_matrix()
1047 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1048 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1050 void forced_levelup_a();
1051 void conditional_levelup_a();
1053 void forced_levelup_b();
1054 void conditional_levelup_b();
1056 using prob_matrix::row_sum;
1057 using prob_matrix::col_sum;
1060 virtual void extract_results(
1061 summary_t& summary_a, summary_t& summary_b)
1066 prob_matrix::dump();
1073 unsigned a_slow_damage_;
1074 int a_drain_percent_;
1075 int a_drain_constant_;
1080 unsigned b_slow_damage_;
1081 int b_drain_percent_;
1082 int b_drain_constant_;
1099 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1100 unsigned int b_max_hp,
1103 const summary_t& a_summary,
1104 const summary_t& b_summary,
1107 unsigned int a_damage,
1108 unsigned int b_damage,
1109 unsigned int a_slow_damage,
1110 unsigned int b_slow_damage,
1111 int a_drain_percent,
1112 int b_drain_percent,
1113 int a_drain_constant,
1114 int b_drain_constant)
1116 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1117 , a_max_hp_(a_max_hp)
1119 , a_damage_(a_damage)
1120 , a_slow_damage_(a_slow_damage)
1121 , a_drain_percent_(a_drain_percent)
1122 , a_drain_constant_(a_drain_constant)
1123 , b_max_hp_(b_max_hp)
1125 , b_damage_(b_damage)
1126 , b_slow_damage_(b_slow_damage)
1127 , b_drain_percent_(b_drain_percent)
1128 , b_drain_constant_(b_drain_constant)
1133 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1135 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1136 if(!plane_used(
p)) {
1141 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1142 if(b_hp > actual_damage) {
1144 move_column(
p,
p, b_hp - actual_damage, 0);
1149 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1151 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1152 if(!plane_used(
p)) {
1157 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1158 if(a_hp > actual_damage) {
1160 move_row(
p,
p, a_hp - actual_damage, 0);
1165 void combat_matrix::forced_levelup_a()
1169 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1171 merge_cols(
p & -2,
p, a_max_hp_);
1176 void combat_matrix::forced_levelup_b()
1180 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1182 merge_rows(
p & -3,
p, b_max_hp_);
1187 void combat_matrix::conditional_levelup_a()
1191 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1193 merge_col(
p & -2,
p, 0, a_max_hp_);
1198 void combat_matrix::conditional_levelup_b()
1202 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1204 merge_row(
p & -3,
p, 0, b_max_hp_);
1214 class probability_combat_matrix :
public combat_matrix
1217 probability_combat_matrix(
unsigned int a_max_hp,
1218 unsigned int b_max_hp,
1221 const summary_t& a_summary,
1222 const summary_t& b_summary,
1225 unsigned int a_damage,
1226 unsigned int b_damage,
1227 unsigned int a_slow_damage,
1228 unsigned int b_slow_damage,
1229 int a_drain_percent,
1230 int b_drain_percent,
1231 int a_drain_constant,
1232 int b_drain_constant);
1235 void receive_blow_b(
double hit_chance);
1237 void receive_blow_a(
double hit_chance);
1240 double dead_prob()
const 1242 return prob_of_zero(
true,
true);
1246 double dead_prob_a()
const 1248 return prob_of_zero(
true,
false);
1252 double dead_prob_b()
const 1254 return prob_of_zero(
false,
true);
1257 void extract_results(
1258 summary_t& summary_a, summary_t& summary_b)
override;
1274 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1275 unsigned int b_max_hp,
1278 const summary_t& a_summary,
1279 const summary_t& b_summary,
1282 unsigned int a_damage,
1283 unsigned int b_damage,
1284 unsigned int a_slow_damage,
1285 unsigned int b_slow_damage,
1286 int a_drain_percent,
1287 int b_drain_percent,
1288 int a_drain_constant,
1289 int b_drain_constant)
1290 : combat_matrix(a_max_hp,
1311 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1314 unsigned src = NUM_PLANES;
1316 if(!plane_used(src)) {
1321 int dst = a_slows_ ? src | 2 : src;
1324 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1326 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1332 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1335 unsigned src = NUM_PLANES;
1337 if(!plane_used(src)) {
1342 int dst = b_slows_ ? src | 1 : src;
1345 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1347 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1351 void probability_combat_matrix::extract_results(
1352 summary_t& summary_a, summary_t& summary_b)
1355 summary_a[0] = std::vector<double>(num_rows());
1356 summary_b[0] = std::vector<double>(num_cols());
1358 if(plane_used(A_SLOWED)) {
1359 summary_a[1] = std::vector<double>(num_rows());
1362 if(plane_used(B_SLOWED)) {
1363 summary_b[1] = std::vector<double>(num_cols());
1366 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1367 if(!plane_used(
p)) {
1372 unsigned dst_a = (
p & 1) ? 1u : 0u;
1374 unsigned dst_b = (
p & 2) ? 1u : 0u;
1375 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1387 class monte_carlo_combat_matrix :
public combat_matrix
1390 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1391 unsigned int b_max_hp,
1394 const summary_t& a_summary,
1395 const summary_t& b_summary,
1398 unsigned int a_damage,
1399 unsigned int b_damage,
1400 unsigned int a_slow_damage,
1401 unsigned int b_slow_damage,
1402 int a_drain_percent,
1403 int b_drain_percent,
1404 int a_drain_constant,
1405 int b_drain_constant,
1406 unsigned int rounds,
1407 double a_hit_chance,
1408 double b_hit_chance,
1409 const std::vector<combat_slice>& a_split,
1410 const std::vector<combat_slice>& b_split,
1411 double a_initially_slowed_chance,
1412 double b_initially_slowed_chance);
1416 void extract_results(
1417 summary_t& summary_a, summary_t& summary_b)
override;
1419 double get_a_hit_probability()
const;
1420 double get_b_hit_probability()
const;
1423 static const unsigned int NUM_ITERATIONS = 5000u;
1425 std::vector<double> a_initial_;
1426 std::vector<double> b_initial_;
1427 std::vector<double> a_initial_slowed_;
1428 std::vector<double> b_initial_slowed_;
1429 std::vector<combat_slice> a_split_;
1430 std::vector<combat_slice> b_split_;
1431 unsigned int rounds_;
1432 double a_hit_chance_;
1433 double b_hit_chance_;
1434 double a_initially_slowed_chance_;
1435 double b_initially_slowed_chance_;
1436 unsigned int iterations_a_hit_ = 0u;
1437 unsigned int iterations_b_hit_ = 0u;
1439 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1440 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1441 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1442 static void scale_probabilities(
1443 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1446 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1447 unsigned int b_max_hp,
1450 const summary_t& a_summary,
1451 const summary_t& b_summary,
1454 unsigned int a_damage,
1455 unsigned int b_damage,
1456 unsigned int a_slow_damage,
1457 unsigned int b_slow_damage,
1458 int a_drain_percent,
1459 int b_drain_percent,
1460 int a_drain_constant,
1461 int b_drain_constant,
1462 unsigned int rounds,
1463 double a_hit_chance,
1464 double b_hit_chance,
1465 const std::vector<combat_slice>& a_split,
1466 const std::vector<combat_slice>& b_split,
1467 double a_initially_slowed_chance,
1468 double b_initially_slowed_chance)
1469 : combat_matrix(a_max_hp,
1488 , a_hit_chance_(a_hit_chance)
1489 , b_hit_chance_(b_hit_chance)
1490 , a_initially_slowed_chance_(a_initially_slowed_chance)
1491 , b_initially_slowed_chance_(b_initially_slowed_chance)
1493 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1494 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1495 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1496 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1501 void monte_carlo_combat_matrix::simulate()
1505 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1510 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1511 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1514 unsigned int a_strikes = calc_blows_a(a_hp);
1515 unsigned int b_strikes = calc_blows_b(b_hp);
1517 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1518 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1522 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1523 damage = std::min(damage, b_hp);
1525 b_slowed |= a_slows_;
1527 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1528 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1542 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1543 damage = std::min(damage, a_hp);
1545 a_slowed |= b_slows_;
1547 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1548 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1561 iterations_a_hit_ += a_hit ? 1 : 0;
1562 iterations_b_hit_ += b_hit ? 1 : 0;
1564 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1572 void monte_carlo_combat_matrix::extract_results(
1573 summary_t& summary_a, summary_t& summary_b)
1576 summary_a[0] = std::vector<double>(num_rows());
1577 summary_b[0] = std::vector<double>(num_cols());
1579 if(plane_used(A_SLOWED)) {
1580 summary_a[1] = std::vector<double>(num_rows());
1583 if(plane_used(B_SLOWED)) {
1584 summary_b[1] = std::vector<double>(num_cols());
1587 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1588 if(!plane_used(
p)) {
1593 unsigned dst_a = (
p & 1) ? 1u : 0u;
1595 unsigned dst_b = (
p & 2) ? 1u : 0u;
1596 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1599 divide_all_elements(summary_a[0], static_cast<double>(NUM_ITERATIONS));
1600 divide_all_elements(summary_b[0], static_cast<double>(NUM_ITERATIONS));
1602 if(plane_used(A_SLOWED)) {
1603 divide_all_elements(summary_a[1], static_cast<double>(NUM_ITERATIONS));
1606 if(plane_used(B_SLOWED)) {
1607 divide_all_elements(summary_b[1], static_cast<double>(NUM_ITERATIONS));
1611 double monte_carlo_combat_matrix::get_a_hit_probability()
const 1613 return static_cast<double>(iterations_a_hit_) / static_cast<double>(NUM_ITERATIONS);
1616 double monte_carlo_combat_matrix::get_b_hit_probability()
const 1618 return static_cast<double>(iterations_b_hit_) / static_cast<double>(NUM_ITERATIONS);
1621 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const 1623 auto it = a_split_.begin();
1624 while(it != a_split_.end() && it->end_hp <= a_hp) {
1628 if(it == a_split_.end()) {
1635 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const 1637 auto it = b_split_.begin();
1638 while(it != b_split_.end() && it->end_hp <= b_hp) {
1642 if(it == b_split_.end()) {
1649 void monte_carlo_combat_matrix::scale_probabilities(
1650 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1652 if(divisor == 0.0) {
1658 if(source.empty()) {
1659 target.resize(singular_hp + 1u, 0.0);
1660 target[singular_hp] = 1.0;
1663 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) {
return prob / divisor; });
1666 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1669 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1671 for(
double&
e : vec) {
1679 : hp_dist(u.max_hp + 1, 0.0)
1724 void forced_levelup(std::vector<double>&
hp_dist)
1729 auto i = hp_dist.begin();
1731 for(++
i;
i != hp_dist.end(); ++
i) {
1736 hp_dist.back() = 1 - hp_dist.front();
1739 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1744 double scalefactor = 0;
1745 const double chance_to_survive = 1 - hp_dist.front();
1746 if(chance_to_survive > DBL_MIN) {
1747 scalefactor = 1 - kill_prob / chance_to_survive;
1750 auto i = hp_dist.begin();
1752 for(++
i;
i != hp_dist.end(); ++
i) {
1757 hp_dist.back() += kill_prob;
1768 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1770 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1774 prob_touched = std::max(prob_touched, 0.0);
1776 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1780 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1783 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1785 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1789 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1792 const double prob_survive_if_not_hit = 1.0;
1794 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1799 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1802 double prob_debuff = 0.0;
1805 prob_debuff += prob_already_debuffed_not_touched;
1807 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1811 prob_debuff += prob_already_debuffed_touched;
1813 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1820 }
else if(!kill_heals) {
1821 prob_debuff += prob_initially_healthy_touched;
1823 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1830 void round_prob_if_close_to_sure(
double& prob)
1834 }
else if(prob > 1.0 - 1.0
e-9) {
1843 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1845 const unsigned size = hp_dist.size();
1848 for(
unsigned i = 0;
i !=
size; ++
i) {
1849 if(hp_dist[
i] != 0.0) {
1866 unsigned int fight_complexity(
unsigned int num_slices,
1867 unsigned int opp_num_slices,
1871 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1888 unsigned opp_strikes,
1889 std::vector<double>& hp_dist,
1890 std::vector<double>& opp_hp_dist,
1891 double& self_not_hit,
1892 double& opp_not_hit,
1893 bool levelup_considered)
1899 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1900 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1902 if(opp_hp_dist.empty()) {
1904 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1905 opp_hp_dist[opp_stats.
hp] = 1.0;
1907 for(
unsigned int i = 0;
i < strikes; ++
i) {
1908 for(
int j =
i; j >= 0; j--) {
1909 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1910 double move = opp_hp_dist[src_index] * hit_chance;
1911 opp_hp_dist[src_index] -= move;
1912 opp_hp_dist[src_index - stats.
damage] += move;
1915 opp_not_hit *= 1.0 - hit_chance;
1919 for(
unsigned int i = 0;
i < strikes; ++
i) {
1920 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1921 double move = opp_hp_dist[j] * hit_chance;
1922 opp_hp_dist[j] -= move;
1923 opp_hp_dist[j - stats.
damage] += move;
1925 opp_not_hit *= 1.0 - hit_chance;
1931 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1932 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1934 if(hp_dist.empty()) {
1936 hp_dist = std::vector<double>(stats.
max_hp + 1);
1937 hp_dist[stats.
hp] = 1.0;
1938 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1939 for(
int j =
i; j >= 0; j--) {
1940 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1941 double move = hp_dist[src_index] * opp_hit_chance;
1942 hp_dist[src_index] -= move;
1943 hp_dist[src_index - opp_stats.
damage] += move;
1946 self_not_hit *= 1.0 - opp_hit_chance;
1950 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1951 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1952 double move = hp_dist[j] * opp_hit_chance;
1954 hp_dist[j - opp_stats.
damage] += move;
1957 self_not_hit *= 1.0 - opp_hit_chance;
1961 if(!levelup_considered) {
1966 forced_levelup(hp_dist);
1970 forced_levelup(opp_hp_dist);
1978 unsigned opp_strikes,
1979 std::vector<double>& hp_dist,
1980 std::vector<double>& opp_hp_dist,
1981 double& self_not_hit,
1982 double& opp_not_hit,
1983 bool levelup_considered)
1988 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1992 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1994 if(opp_hp_dist.empty()) {
1995 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1996 if(strikes == 1 && opp_stats.
hp > 0) {
1997 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1998 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
1999 opp_not_hit *= 1.0 - hit_chance;
2001 opp_hp_dist[opp_stats.
hp] = 1.0;
2005 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
2006 double move = opp_hp_dist[
i] * hit_chance;
2007 opp_hp_dist[
i] -= move;
2008 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2011 opp_not_hit *= 1.0 - hit_chance;
2016 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2017 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2019 if(hp_dist.empty()) {
2020 hp_dist = std::vector<double>(stats.
max_hp + 1);
2021 if(opp_strikes == 1 && stats.
hp > 0) {
2022 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2023 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2024 self_not_hit *= 1.0 - opp_hit_chance;
2026 hp_dist[stats.
hp] = 1.0;
2029 if(opp_strikes == 1) {
2030 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2031 double move = hp_dist[
i] * opp_hit_chance;
2033 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2036 self_not_hit *= 1.0 - opp_hit_chance;
2040 if(!levelup_considered) {
2045 forced_levelup(hp_dist);
2047 conditional_levelup(hp_dist, opp_hp_dist[0]);
2051 forced_levelup(opp_hp_dist);
2053 conditional_levelup(opp_hp_dist, hp_dist[0]);
2063 unsigned opp_strikes,
2065 summary_t& opp_summary,
2066 double& self_not_hit,
2067 double& opp_not_hit,
2068 bool levelup_considered,
2069 std::vector<combat_slice>
split,
2070 std::vector<combat_slice> opp_split,
2071 double initially_slowed_chance,
2072 double opp_initially_slowed_chance)
2074 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2075 unsigned max_attacks = std::max(strikes, opp_strikes);
2077 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2080 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2086 a_damage = a_slow_damage = opp_stats.
max_hp;
2090 b_damage = b_slow_damage = stats.
max_hp;
2093 const double original_self_not_hit = self_not_hit;
2094 const double original_opp_not_hit = opp_not_hit;
2096 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2097 double self_hit = 0.0;
2098 double opp_hit = 0.0;
2099 double self_hit_unknown = 1.0;
2100 double opp_hit_unknown = 1.0;
2103 std::unique_ptr<combat_matrix> m;
2104 if(mode == attack_prediction_mode::probability_calculation) {
2105 debug((
"Using exact probability calculations.\n"));
2107 probability_combat_matrix* pm =
new probability_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2108 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2109 a_damage, b_damage, a_slow_damage, b_slow_damage,
2114 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2116 debug((
"A strikes\n"));
2117 double b_already_dead = pm->dead_prob_b();
2118 pm->receive_blow_b(hit_chance);
2121 double first_hit = hit_chance * opp_hit_unknown;
2122 opp_hit += first_hit;
2123 opp_hit_unknown -= first_hit;
2124 double both_were_alive = (1.0 - b_already_dead) * (1.0 - pm->dead_prob_a());
2125 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2126 self_hit_unknown *= (1.0 - this_hit_killed_b);
2128 if(
i < opp_strikes) {
2129 debug((
"B strikes\n"));
2130 double a_already_dead = pm->dead_prob_a();
2131 pm->receive_blow_a(opp_hit_chance);
2134 double first_hit = opp_hit_chance * self_hit_unknown;
2135 self_hit += first_hit;
2136 self_hit_unknown -= first_hit;
2137 double both_were_alive = (1.0 - a_already_dead) * (1.0 - pm->dead_prob_b());
2138 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2139 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2143 debug((
"Combat ends:\n"));
2145 }
while(--rounds && pm->dead_prob() < 0.99);
2147 self_hit = std::min(self_hit, 1.0);
2148 opp_hit = std::min(opp_hit, 1.0);
2150 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2151 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2160 unsigned int plane = plane_index(stats, opp_stats);
2161 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2162 opp_not_hit = original_opp_not_hit * not_hit;
2164 if(opp_stats.
slows) {
2165 unsigned int plane = plane_index(stats, opp_stats);
2166 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2167 self_not_hit = original_self_not_hit * not_hit;
2170 debug((
"Using Monte Carlo simulation.\n"));
2172 monte_carlo_combat_matrix* mcm =
new monte_carlo_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2173 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2174 a_damage, b_damage, a_slow_damage, b_slow_damage,
2176 hit_chance, opp_hit_chance, split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2180 debug((
"Combat ends:\n"));
2183 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2184 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2195 if(levelup_considered) {
2197 m->forced_levelup_a();
2199 m->conditional_levelup_a();
2203 m->forced_levelup_b();
2205 m->conditional_levelup_b();
2210 m->extract_results(summary, opp_summary);
2220 unsigned opp_strikes,
2222 summary_t& opp_summary,
2223 double& self_not_hit,
2224 double& opp_not_hit,
2225 bool levelup_considered)
2231 && opp_summary[1].empty())
2233 if(strikes <= 1 && opp_strikes <= 1) {
2234 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2235 opp_not_hit, levelup_considered);
2236 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2237 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2238 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2239 opp_not_hit, levelup_considered);
2241 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2242 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2243 std::vector<combat_slice>(), 0.0, 0.0);
2246 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2247 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2248 std::vector<combat_slice>(), 0.0, 0.0);
2257 void init_slice_summary(
2258 std::vector<double>& dst,
const std::vector<double>& src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2265 const unsigned size = src.size();
2272 dst.resize(size, 0.0);
2273 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2274 dst[
i] = src[
i] / prob;
2282 void merge_slice_summary(std::vector<double>& dst,
const std::vector<double>& src,
double prob)
2284 const unsigned size = src.size();
2287 if(dst.size() <
size) {
2288 dst.resize(size, 0.0);
2292 for(
unsigned i = 0;
i !=
size; ++
i) {
2293 dst[
i] += src[
i] * prob;
2308 opponent.
fight(*
this, levelup_considered);
2312 #ifdef ATTACK_PREDICTION_DEBUG 2321 complex_fight(opponent, 1);
2322 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2324 opponent.
summary[0] = opp_prev;
2328 double self_not_hit = 1.0;
2329 double opp_not_hit = 1.0;
2332 double self_already_dead =
hp_dist[0];
2333 double opp_already_dead = opponent.
hp_dist[0];
2336 round_prob_if_close_to_sure(
slowed);
2337 round_prob_if_close_to_sure(opponent.
slowed);
2341 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2342 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2344 bool use_monte_carlo_simulation =
2348 if(use_monte_carlo_simulation) {
2351 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2354 }
else if(split.size() == 1 && opp_split.size() == 1) {
2357 opp_not_hit, levelup_considered);
2360 summary_t summary_result, opp_summary_result;
2366 for(
unsigned s = 0;
s != split.size(); ++
s) {
2367 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2368 const double sit_prob = split[
s].prob * opp_split[
t].prob;
2371 summary_t sit_summary, sit_opp_summary;
2372 init_slice_summary(sit_summary[0],
summary[0], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2373 init_slice_summary(sit_summary[1],
summary[1], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2374 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[t].end_hp,
2376 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[t].begin_hp, opp_split[t].end_hp,
2381 double sit_self_not_hit = sit_prob;
2382 double sit_opp_not_hit = sit_prob;
2384 do_fight(
u_, opponent.
u_, split[
s].strikes, opp_split[t].strikes, sit_summary, sit_opp_summary,
2385 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2388 self_not_hit += sit_self_not_hit;
2389 opp_not_hit += sit_opp_not_hit;
2390 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2391 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2392 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2393 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2398 summary[0].swap(summary_result[0]);
2399 summary[1].swap(summary_result[1]);
2400 opponent.
summary[0].swap(opp_summary_result[0]);
2401 opponent.
summary[1].swap(opp_summary_result[1]);
2406 assert(opponent.
summary[0].size() == opp_res.size());
2407 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2408 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2409 std::cerr <<
"Mismatch for " << i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i] <<
"\n";
2413 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2414 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2415 std::cerr <<
"Mismatch for " << i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i] <<
"\n";
2427 for(
unsigned int i = 0;
i <
size; ++
i)
2431 if(opponent.
summary[1].empty()) {
2435 opponent.
hp_dist.resize(size);
2436 for(
unsigned int i = 0;
i <
size; ++
i)
2441 double touched = 1.0 - self_not_hit;
2442 double opp_touched = 1.0 - opp_not_hit;
2454 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2475 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2484 #if defined(BENCHMARK) || defined(CHECK) 2487 static const unsigned int NUM_UNITS = 50;
2489 #ifdef ATTACK_PREDICTION_DEBUG 2492 std::ostringstream ss;
2495 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; " 2511 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2515 ss <<
"firststrike, ";
2518 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2520 std::cout << ss.rdbuf() << std::endl;
2528 #ifdef HUMAN_READABLE 2529 void combatant::print(
const char label[],
unsigned int battle,
unsigned int fighter)
const 2531 std::ostringstream ss;
2534 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter, label,
2554 ss <<
"firststrike, ";
2557 std::cout << ss.rdbuf() << std::endl;
2560 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2562 int num_outputs = 0;
2563 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2565 if(num_outputs++ % 6 == 0) {
2571 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2577 #elif defined(CHECK) 2578 void combatant::print(
const char label[],
unsigned int battle,
unsigned int )
const 2580 std::ostringstream ss;
2602 ss <<
"firststrike, ";
2605 std::cout << ss.rdbuf() << std::endl;
2608 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2610 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2616 #else // ... BENCHMARK 2622 void combatant::reset()
2624 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2631 summary[0] = std::vector<double>();
2632 summary[1] = std::vector<double>();
2635 static void run(
unsigned specific_battle)
2637 using std::chrono::duration_cast;
2638 using std::chrono::microseconds;
2643 unsigned int i, j, k, battle = 0;
2644 std::chrono::high_resolution_clock::time_point
start, end;
2646 for(i = 0; i < NUM_UNITS; ++
i) {
2647 unsigned alt = i + 74;
2650 unsigned max_hp = (i * 2) % 23 + (i * 3) % 14 + 25;
2651 unsigned hp = (alt * 5) % max_hp + 1;
2663 list_combatant(*stats[i], i + 1);
2666 start = std::chrono::high_resolution_clock::now();
2668 for(i = 0; i < NUM_UNITS; ++
i) {
2669 for(j = 0; j < NUM_UNITS; ++j) {
2674 for(k = 0; k < NUM_UNITS; ++k) {
2675 if(i == k || j == k) {
2680 if(specific_battle && battle != specific_battle) {
2688 u[
i]->print(
"Defender", battle, i + 1);
2689 u[j]->print(
"Attacker #1", battle, j + 1);
2690 u[k]->print(
"Attacker #2", battle, k + 1);
2699 end = std::chrono::high_resolution_clock::now();
2701 auto total = end -
start;
2704 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2705 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2706 printf(
"Time per calc = %li us\n", static_cast<long>(duration_cast<microseconds>(total).count())
2707 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2709 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2712 for(i = 0; i < NUM_UNITS; ++
i) {
2723 int add_to_argv = 4;
2724 int damage = atoi((*argv)[1]);
2725 int num_attacks = atoi((*argv)[2]);
2726 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2727 int hit_chance = atoi((*argv)[4]);
2730 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2731 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2735 char* max = strstr((*argv)[5],
"maxhp=");
2737 max_hp = atoi(max + strlen(
"maxhp="));
2738 if(max_hp < hitpoints) {
2739 std::cerr <<
"maxhp must be at least hitpoints." << std::endl;
2744 if(strstr((*argv)[5],
"drain")) {
2746 std::cerr <<
"WARNING: drain specified without maxhp; assuming uninjured." << std::endl;
2752 if(strstr((*argv)[5],
"slows")) {
2756 if(strstr((*argv)[5],
"slowed")) {
2760 if(strstr((*argv)[5],
"berserk")) {
2764 if(strstr((*argv)[5],
"firststrike")) {
2768 if(strstr((*argv)[5],
"swarm")) {
2770 std::cerr <<
"WARNING: swarm specified without maxhp; assuming uninjured." << std::endl;
2778 *argv += add_to_argv;
2782 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2785 int main(
int argc,
char* argv[])
2792 run(argv[1] ? atoi(argv[1]) : 0);
2796 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" " 2797 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] " 2798 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ..." 2803 def_stats = parse_unit(&argv);
2805 for(i = 0; argv[1] && i < 19; ++
i) {
2806 att_stats[
i] = parse_unit(&argv);
2812 for(i = 0; att[
i]; ++
i) {
2813 debug((
"Fighting next attacker\n"));
2817 def->print(
"Defender", 0, 0);
2818 for(i = 0; att[
i]; ++
i) {
2819 att[
i]->print(
"Attacker", 0, i + 1);
2822 for(i = 0; att[
i]; ++
i) {
2824 delete att_stats[
i];
2833 #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).
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).
int main(int argc, char **argv)
bool poisons
Attack poisons opponent when it hits.
bool backstab_pos
True if the attacker is in position to backstab the defender (this is used to determine whether to ap...
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.
const battle_context_unit_stats & u_
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).
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 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.