48 #if defined(BENCHMARK) || defined(CHECK) 54 #ifdef ATTACK_PREDICTION_DEBUG 55 #define debug(x) printf x 60 #ifdef ATTACK_PREDICTION_DEBUG 66 std::ostringstream ss;
68 ss <<
"==================================";
72 <<
"\n" <<
"is_slowed: " << stats.
is_slowed 73 <<
"\n" <<
"slows: " << stats.
slows 74 <<
"\n" <<
"drains: " << stats.
drains 75 <<
"\n" <<
"petrifies: " << stats.
petrifies 76 <<
"\n" <<
"poisons: " << stats.
poisons 78 <<
"\n" <<
"swarm: " << stats.
swarm 81 <<
"\n" <<
"rounds: " << stats.
rounds 83 <<
"\n" <<
"hp: " << stats.
hp 84 <<
"\n" <<
"max_hp: " << stats.
max_hp 86 <<
"\n" <<
"damage: " << stats.
damage 90 <<
"\n" <<
"num_blows: " << stats.
num_blows 91 <<
"\n" <<
"swarm_min: " << stats.
swarm_min 92 <<
"\n" <<
"swarm_max: " << stats.
swarm_max 95 std::cout << ss.rdbuf() << std::endl;
102 using summary_t = std::array<std::vector<double>, 2>;
121 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes);
122 combat_slice(
const summary_t& src_summary,
unsigned num_strikes);
128 combat_slice::combat_slice(
129 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes)
133 , strikes(num_strikes)
135 if(src_summary[0].empty()) {
142 if(end > src_summary[0].
size()) {
143 end = src_summary[0].size();
147 for(
unsigned i = begin;
i < end; ++
i) {
148 prob += src_summary[0][
i];
151 if(!src_summary[1].empty()) {
152 for(
unsigned i = begin;
i < end; ++
i) {
153 prob += src_summary[1][
i];
162 combat_slice::combat_slice(
const summary_t& src_summary,
unsigned num_strikes)
164 , end_hp(src_summary[0].
size())
166 , strikes(num_strikes)
178 unsigned old_strikes = stats.
calc_blows(cur_hp);
182 while(++cur_hp <= stats.
max_hp) {
195 std::vector<combat_slice> split_summary(
198 std::vector<combat_slice> result;
202 result.emplace_back(summary, unit_stats.
num_blows);
206 debug((
"Slicing:\n"));
208 unsigned cur_end = 0;
211 const unsigned cur_begin = cur_end;
212 cur_end = hp_for_next_attack(cur_begin, unit_stats);
215 combat_slice slice(summary, cur_begin, cur_end, unit_stats.
calc_blows(cur_begin));
216 if(slice.prob != 0.0) {
217 result.push_back(slice);
218 debug((
"\t%2u-%2u hp; strikes: %u; probability: %6.2f\n", cur_begin, cur_end, slice.strikes,
219 slice.prob * 100.0));
221 }
while(cur_end <= unit_stats.
max_hp);
237 prob_matrix(
unsigned int a_max,
243 const summary_t& a_initial,
244 const summary_t& b_initial);
247 void shift_cols(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
249 void shift_rows(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
252 void move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col);
254 void move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row);
257 void merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row);
258 void merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row);
259 void merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col);
260 void merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col);
266 void record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed);
269 static unsigned int plane_index(
bool a_slowed,
bool b_slowed)
271 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
275 double prob_of_zero(
bool check_a,
bool check_b)
const;
277 double row_sum(
unsigned plane,
unsigned row)
const;
279 double col_sum(
unsigned plane,
unsigned column)
const;
281 void sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const;
284 bool plane_used(
unsigned p)
const 286 return p < NUM_PLANES && plane_[
p] !=
nullptr;
289 unsigned int num_rows()
const 293 unsigned int num_cols()
const 313 std::unique_ptr<double[]> new_plane()
const;
315 void initialize_plane(
unsigned plane,
318 const std::vector<double>& a_initial,
319 const std::vector<double>& b_initial);
321 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial);
323 double& val(
unsigned plane,
unsigned row,
unsigned col);
324 const double& val(
unsigned plane,
unsigned row,
unsigned col)
const;
327 void xfer(
unsigned dst_plane,
335 void xfer(
unsigned dst_plane,
342 void shift_cols_in_row(
unsigned dst,
345 const std::vector<unsigned>& cols,
351 void shift_rows_in_col(
unsigned dst,
354 const std::vector<unsigned>& rows,
362 const unsigned int rows_, cols_;
363 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
367 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
383 prob_matrix::prob_matrix(
unsigned int a_max,
389 const summary_t& a_initial,
390 const summary_t& b_initial)
398 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
399 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
402 for(
unsigned plane = 0; plane != NUM_PLANES; ++plane) {
403 used_rows_[plane].insert(0u);
404 used_cols_[plane].insert(0u);
408 need_a_slowed = need_a_slowed || !a_initial[1].empty();
409 need_b_slowed = need_b_slowed || !b_initial[1].empty();
412 plane_[NEITHER_SLOWED] = new_plane();
413 plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
414 plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
415 plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ?
nullptr : new_plane();
418 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
420 if(!a_initial[1].empty()) {
421 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
424 if(!b_initial[1].empty()) {
425 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
428 if(!a_initial[1].empty() && !b_initial[1].empty()) {
429 initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
433 if(!a_initial[0].empty()) {
434 debug((
"A has fought before (or is slowed).\n"));
438 if(!b_initial[0].empty()) {
439 debug((
"B has fought before (or is slowed).\n"));
445 std::unique_ptr<double[]> prob_matrix::new_plane()
const 447 const unsigned int size = rows_ * cols_;
448 std::unique_ptr<double[]> res(
new double[size]);
449 std::fill_n(res.get(),
size, 0);
462 void prob_matrix::initialize_plane(
unsigned plane,
465 const std::vector<double>& a_initial,
466 const std::vector<double>& b_initial)
468 if(!a_initial.empty()) {
469 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
471 for(
unsigned row = 0; row < row_count; ++row) {
472 if(a_initial[row] != 0.0) {
473 used_rows_[plane].insert(row);
474 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
478 used_rows_[plane].insert(a_cur);
480 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
493 void prob_matrix::initialize_row(
494 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
496 if(!b_initial.empty()) {
497 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
499 for(
unsigned col = 0; col < col_count; ++col) {
500 if(b_initial[col] != 0.0) {
501 used_cols_[plane].insert(col);
502 val(plane, row, col) = row_prob * b_initial[col];
507 used_cols_[plane].insert(b_cur);
508 val(plane, row, b_cur) = row_prob;
512 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
516 return plane_[plane][row * cols_ + col];
519 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const 523 return plane_[plane][row * cols_ + col];
530 void prob_matrix::xfer(
unsigned dst_plane,
538 double& src = val(src_plane, row_src, col_src);
540 double diff = src * prob;
543 double& dst = val(dst_plane, row_dst, col_dst);
546 used_rows_[dst_plane].insert(row_dst);
547 used_cols_[dst_plane].insert(col_dst);
551 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
552 src_plane == NEITHER_SLOWED
554 : src_plane == A_SLOWED
556 : src_plane == B_SLOWED
558 : src_plane == BOTH_SLOWED
563 dst_plane == NEITHER_SLOWED
565 : dst_plane == A_SLOWED
567 : dst_plane == B_SLOWED
569 : dst_plane == BOTH_SLOWED
580 void prob_matrix::xfer(
581 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
583 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
587 double& src = val(src_plane, row_src, col_src);
589 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
590 src_plane == NEITHER_SLOWED
592 : src_plane == A_SLOWED
594 : src_plane == B_SLOWED
596 : src_plane == BOTH_SLOWED
600 dst_plane == NEITHER_SLOWED
602 : dst_plane == A_SLOWED
604 : dst_plane == B_SLOWED
606 : dst_plane == BOTH_SLOWED
612 double& dst = val(dst_plane, row_dst, col_dst);
615 used_rows_[dst_plane].insert(row_dst);
616 used_cols_[dst_plane].insert(col_dst);
628 void prob_matrix::shift_cols_in_row(
unsigned dst,
631 const std::vector<unsigned>& cols,
639 int row_i =
static_cast<int>(row);
640 int max_row =
static_cast<int>(rows_) - 1;
646 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
649 int col_i =
static_cast<int>(cols[col_x]);
650 int drain_amount = col_i * drain_percent / 100 + drain_constant;
651 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
652 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
656 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
657 for(; col_x < cols.size(); ++col_x) {
658 xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
668 void prob_matrix::shift_cols(
669 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
671 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
673 if(drain_constant || drain_percent) {
674 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
679 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
680 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
686 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
687 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
691 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
692 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
701 void prob_matrix::shift_rows_in_col(
unsigned dst,
704 const std::vector<unsigned>& rows,
712 int col_i =
static_cast<int>(col);
713 int max_col =
static_cast<int>(cols_) - 1;
719 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
722 int row_i =
static_cast<int>(rows[row_x]);
723 int drain_amount = row_i * drain_percent / 100 + drain_constant;
724 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
725 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
729 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
730 for(; row_x < rows.size(); ++row_x) {
731 xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
741 void prob_matrix::shift_rows(
742 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
744 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
746 if(drain_constant || drain_percent) {
747 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
752 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
753 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
759 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
760 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
763 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
764 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
772 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
775 for(
const unsigned& row : used_rows_[s_plane]) {
776 xfer(d_plane, s_plane, row, d_col, row, s_col);
783 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
786 for(
const unsigned& col : used_cols_[s_plane]) {
787 xfer(d_plane, s_plane, d_row, col, s_row, col);
795 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
797 auto rows_end = used_rows_[s_plane].end();
798 auto row_it = used_rows_[s_plane].begin();
801 for(++row_it; row_it != rows_end; ++row_it) {
802 xfer(d_plane, s_plane, d_row, col, *row_it, col);
810 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
812 auto rows_end = used_rows_[s_plane].end();
813 auto row_it = used_rows_[s_plane].begin();
816 for(++row_it; row_it != rows_end; ++row_it) {
817 for(
const unsigned& col : used_cols_[s_plane]) {
818 xfer(d_plane, s_plane, d_row, col, *row_it, col);
827 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
829 auto cols_end = used_cols_[s_plane].end();
830 auto col_it = used_cols_[s_plane].begin();
833 for(++col_it; col_it != cols_end; ++col_it) {
834 xfer(d_plane, s_plane, row, d_col, row, *col_it);
842 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
844 auto cols_end = used_cols_[s_plane].end();
846 auto cols_begin = std::next(used_cols_[s_plane].begin());
849 for(
const unsigned row : used_rows_[s_plane]) {
850 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
851 xfer(d_plane, s_plane, row, d_col, row, *col_it);
861 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
866 if(used_rows_[
p].empty()) {
871 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].begin(), used_rows_[
p].end());
872 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
873 for(
unsigned int c = 0u;
c < cols_; ++
c) {
874 plane_[
p][r * cols_ +
c] = 0.0;
878 used_rows_[
p].clear();
879 used_cols_[
p].clear();
885 used_rows_[
p].insert(0u);
886 used_cols_[
p].insert(0u);
893 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
895 assert(a_hp <= rows_);
896 assert(b_hp <= cols_);
897 unsigned int plane = plane_index(a_slowed, b_slowed);
898 ++val(plane, a_hp, b_hp);
899 used_rows_[plane].insert(a_hp);
900 used_cols_[plane].insert(b_hp);
906 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const 910 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
917 for(
const unsigned& row : used_rows_[
p]) {
918 prob += val(p, row, 0);
924 for(
const unsigned& col : used_cols_[
p]) {
925 prob += val(p, 0, col);
938 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const 940 if(!plane_used(plane)) {
945 for(
unsigned col : used_cols_[plane]) {
946 sum += val(plane, row, col);
954 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const 956 if(!plane_used(plane)) {
961 for(
unsigned row : used_rows_[plane]) {
962 sum += val(plane, row, column);
972 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const 974 for(
const unsigned& row : used_rows_[plane]) {
975 for(
const unsigned& col : used_cols_[plane]) {
976 const double& prob = val(plane, row, col);
977 row_sums[row] += prob;
978 col_sums[col] += prob;
983 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG) 984 void prob_matrix::dump()
const 986 unsigned int row, col, m;
987 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
989 for(m = 0; m < NUM_PLANES; ++m) {
995 for(row = 0; row < rows_; ++row) {
997 for(col = 0; col < cols_; ++col) {
998 debug((
"%4.3g ", val(m, row, col) * 100));
1006 void prob_matrix::dump()
const 1016 class combat_matrix :
protected prob_matrix
1019 combat_matrix(
unsigned int a_max_hp,
1020 unsigned int b_max_hp,
1023 const summary_t& a_summary,
1024 const summary_t& b_summary,
1027 unsigned int a_damage,
1028 unsigned int b_damage,
1029 unsigned int a_slow_damage,
1030 unsigned int b_slow_damage,
1031 int a_drain_percent,
1032 int b_drain_percent,
1033 int a_drain_constant,
1034 int b_drain_constant);
1036 virtual ~combat_matrix()
1041 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1042 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1044 void forced_levelup_a();
1045 void conditional_levelup_a();
1047 void forced_levelup_b();
1048 void conditional_levelup_b();
1050 using prob_matrix::row_sum;
1051 using prob_matrix::col_sum;
1054 virtual void extract_results(
1055 summary_t& summary_a, summary_t& summary_b)
1060 prob_matrix::dump();
1067 unsigned a_slow_damage_;
1068 int a_drain_percent_;
1069 int a_drain_constant_;
1074 unsigned b_slow_damage_;
1075 int b_drain_percent_;
1076 int b_drain_constant_;
1093 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1094 unsigned int b_max_hp,
1097 const summary_t& a_summary,
1098 const summary_t& b_summary,
1101 unsigned int a_damage,
1102 unsigned int b_damage,
1103 unsigned int a_slow_damage,
1104 unsigned int b_slow_damage,
1105 int a_drain_percent,
1106 int b_drain_percent,
1107 int a_drain_constant,
1108 int b_drain_constant)
1110 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1111 , a_max_hp_(a_max_hp)
1113 , a_damage_(a_damage)
1114 , a_slow_damage_(a_slow_damage)
1115 , a_drain_percent_(a_drain_percent)
1116 , a_drain_constant_(a_drain_constant)
1117 , b_max_hp_(b_max_hp)
1119 , b_damage_(b_damage)
1120 , b_slow_damage_(b_slow_damage)
1121 , b_drain_percent_(b_drain_percent)
1122 , b_drain_constant_(b_drain_constant)
1127 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1129 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1130 if(!plane_used(
p)) {
1135 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1136 if(b_hp > actual_damage) {
1138 move_column(
p,
p, b_hp - actual_damage, 0);
1143 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1145 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1146 if(!plane_used(
p)) {
1151 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1152 if(a_hp > actual_damage) {
1154 move_row(
p,
p, a_hp - actual_damage, 0);
1159 void combat_matrix::forced_levelup_a()
1163 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1165 merge_cols(
p & -2,
p, a_max_hp_);
1170 void combat_matrix::forced_levelup_b()
1174 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1176 merge_rows(
p & -3,
p, b_max_hp_);
1181 void combat_matrix::conditional_levelup_a()
1185 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1187 merge_col(
p & -2,
p, 0, a_max_hp_);
1192 void combat_matrix::conditional_levelup_b()
1196 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1198 merge_row(
p & -3,
p, 0, b_max_hp_);
1208 class probability_combat_matrix :
public combat_matrix
1211 probability_combat_matrix(
unsigned int a_max_hp,
1212 unsigned int b_max_hp,
1215 const summary_t& a_summary,
1216 const summary_t& b_summary,
1219 unsigned int a_damage,
1220 unsigned int b_damage,
1221 unsigned int a_slow_damage,
1222 unsigned int b_slow_damage,
1223 int a_drain_percent,
1224 int b_drain_percent,
1225 int a_drain_constant,
1226 int b_drain_constant);
1229 void receive_blow_b(
double hit_chance);
1231 void receive_blow_a(
double hit_chance);
1234 double dead_prob()
const 1236 return prob_of_zero(
true,
true);
1240 double dead_prob_a()
const 1242 return prob_of_zero(
true,
false);
1246 double dead_prob_b()
const 1248 return prob_of_zero(
false,
true);
1251 void extract_results(
1252 summary_t& summary_a, summary_t& summary_b)
override;
1268 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1269 unsigned int b_max_hp,
1272 const summary_t& a_summary,
1273 const summary_t& b_summary,
1276 unsigned int a_damage,
1277 unsigned int b_damage,
1278 unsigned int a_slow_damage,
1279 unsigned int b_slow_damage,
1280 int a_drain_percent,
1281 int b_drain_percent,
1282 int a_drain_constant,
1283 int b_drain_constant)
1284 : combat_matrix(a_max_hp,
1305 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1308 unsigned src = NUM_PLANES;
1310 if(!plane_used(src)) {
1315 int dst = a_slows_ ? src | 2 : src;
1318 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1320 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1326 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1329 unsigned src = NUM_PLANES;
1331 if(!plane_used(src)) {
1336 int dst = b_slows_ ? src | 1 : src;
1339 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1341 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1345 void probability_combat_matrix::extract_results(
1346 summary_t& summary_a, summary_t& summary_b)
1349 summary_a[0] = std::vector<double>(num_rows());
1350 summary_b[0] = std::vector<double>(num_cols());
1352 if(plane_used(A_SLOWED)) {
1353 summary_a[1] = std::vector<double>(num_rows());
1356 if(plane_used(B_SLOWED)) {
1357 summary_b[1] = std::vector<double>(num_cols());
1360 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1361 if(!plane_used(
p)) {
1366 unsigned dst_a = (
p & 1) ? 1u : 0u;
1368 unsigned dst_b = (
p & 2) ? 1u : 0u;
1369 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1381 class monte_carlo_combat_matrix :
public combat_matrix
1384 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1385 unsigned int b_max_hp,
1388 const summary_t& a_summary,
1389 const summary_t& b_summary,
1392 unsigned int a_damage,
1393 unsigned int b_damage,
1394 unsigned int a_slow_damage,
1395 unsigned int b_slow_damage,
1396 int a_drain_percent,
1397 int b_drain_percent,
1398 int a_drain_constant,
1399 int b_drain_constant,
1400 unsigned int rounds,
1401 double a_hit_chance,
1402 double b_hit_chance,
1403 const std::vector<combat_slice>& a_split,
1404 const std::vector<combat_slice>& b_split,
1405 double a_initially_slowed_chance,
1406 double b_initially_slowed_chance);
1410 void extract_results(
1411 summary_t& summary_a, summary_t& summary_b)
override;
1413 double get_a_hit_probability()
const;
1414 double get_b_hit_probability()
const;
1417 static const unsigned int NUM_ITERATIONS = 5000u;
1419 std::vector<double> a_initial_;
1420 std::vector<double> b_initial_;
1421 std::vector<double> a_initial_slowed_;
1422 std::vector<double> b_initial_slowed_;
1423 std::vector<combat_slice> a_split_;
1424 std::vector<combat_slice> b_split_;
1425 unsigned int rounds_;
1426 double a_hit_chance_;
1427 double b_hit_chance_;
1428 double a_initially_slowed_chance_;
1429 double b_initially_slowed_chance_;
1430 unsigned int iterations_a_hit_ = 0u;
1431 unsigned int iterations_b_hit_ = 0u;
1433 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1434 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1435 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1436 static void scale_probabilities(
1437 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1440 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1441 unsigned int b_max_hp,
1444 const summary_t& a_summary,
1445 const summary_t& b_summary,
1448 unsigned int a_damage,
1449 unsigned int b_damage,
1450 unsigned int a_slow_damage,
1451 unsigned int b_slow_damage,
1452 int a_drain_percent,
1453 int b_drain_percent,
1454 int a_drain_constant,
1455 int b_drain_constant,
1456 unsigned int rounds,
1457 double a_hit_chance,
1458 double b_hit_chance,
1459 const std::vector<combat_slice>& a_split,
1460 const std::vector<combat_slice>& b_split,
1461 double a_initially_slowed_chance,
1462 double b_initially_slowed_chance)
1463 : combat_matrix(a_max_hp,
1482 , a_hit_chance_(a_hit_chance)
1483 , b_hit_chance_(b_hit_chance)
1484 , a_initially_slowed_chance_(a_initially_slowed_chance)
1485 , b_initially_slowed_chance_(b_initially_slowed_chance)
1487 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1488 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1489 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1490 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1495 void monte_carlo_combat_matrix::simulate()
1499 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1504 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1505 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
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)
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.0
e-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)
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]);
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) * (1.0 - 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) * (1.0 - 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;
2366 init_slice_summary(sit_summary[0],
summary[0], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2367 init_slice_summary(sit_summary[1],
summary[1], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
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 std::cerr <<
"Mismatch for " << i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i] <<
"\n";
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 std::cerr <<
"Mismatch for " << i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i] <<
"\n";
2421 for(
unsigned int i = 0;
i <
size; ++
i)
2425 if(opponent.
summary[1].empty()) {
2429 opponent.
hp_dist.resize(size);
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) {
2610 #else // ... BENCHMARK 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();
2695 auto total = end -
start;
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 std::cerr <<
"maxhp must be at least hitpoints." << std::endl;
2738 if(strstr((*argv)[5],
"drain")) {
2740 std::cerr <<
"WARNING: drain specified without maxhp; assuming uninjured." << std::endl;
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 std::cerr <<
"WARNING: swarm specified without maxhp; assuming uninjured." << std::endl;
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>] ..." 2797 def_stats = parse_unit(&argv);
2799 for(i = 0; argv[1] && i < 19; ++
i) {
2800 att_stats[
i] = parse_unit(&argv);
2806 for(i = 0; att[
i]; ++
i) {
2807 debug((
"Fighting next attacker\n"));
2811 def->print(
"Defender", 0, 0);
2812 for(i = 0; att[
i]; ++
i) {
2813 att[
i]->print(
"Attacker", 0, i + 1);
2816 for(i = 0; att[
i]; ++
i) {
2818 delete att_stats[
i];
2827 #endif // Standalone program
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp instead of the recorded hp...
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
std::array< std::vector< double >, 2 > summary
Summary of matrix used to calculate last battle (unslowed & slowed).
int main(int, char **argv)
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Various functions that implement attacks and attack calculations.
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
void clear(const std::string &key)
bool is_slowed
True if the unit is slowed at the beginning of the battle.
bool slows
Attack slows opponent when it hits.
int drain_constant
Base HP drained regardless of damage dealt.
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
bool poisons
Attack poisons opponent when it hits.
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.
std::string label
What to show in the filter's drop-down list.
const battle_context_unit_stats & u_
EXIT_STATUS start(const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
unsigned int rounds
Berserk special can force us to fight more than one round.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
unsigned int get_random_element(T first, T last)
This helper method selects a random element from a container of floating-point numbers.
combatant(const battle_context_unit_stats &u, const combatant *prev=nullptr)
Construct a combatant.
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
Structure describing the statistics of a unit involved in the battle.
bool damage_prediction_allow_monte_carlo_simulation()
bool get_random_bool(double probability)
This helper method returns true with the probability supplied as a parameter.
double slowed
Resulting chance we are slowed.
bool swarm
Attack has swarm special.
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
static map_location::DIRECTION s
std::vector< std::string > names
static const unsigned int MONTE_CARLO_SIMULATION_THRESHOLD
bool firststrike
Attack has firststrike special.
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
int drain_percent
Percentage of damage recovered as health.
std::vector< std::string > split(const config_attribute_value &val)
Standard logging facilities (interface).
unsigned int num_blows
Effective number of blows, takes swarm into account.
unsigned int max_hp
Maximum hitpoints of the unit.
bool is_attacker
True if the unit is the attacker.
unsigned int max_experience
bool petrifies
Attack petrifies opponent when it hits.
static rng & default_instance()
bool drains
Attack drains opponent when it hits.
double poisoned
Resulting chance we are poisoned.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
this class does not give synced random results derived classes might do.