55 #if defined(BENCHMARK) || defined(CHECK) 61 #ifdef ATTACK_PREDICTION_DEBUG 62 #define debug(x) printf x 67 #ifdef ATTACK_PREDICTION_DEBUG 73 std::ostringstream ss;
75 ss <<
"==================================";
79 <<
"\n" <<
"is_slowed: " << stats.
is_slowed 80 <<
"\n" <<
"slows: " << stats.
slows 81 <<
"\n" <<
"drains: " << stats.
drains 82 <<
"\n" <<
"petrifies: " << stats.
petrifies 83 <<
"\n" <<
"poisons: " << stats.
poisons 85 <<
"\n" <<
"swarm: " << stats.
swarm 88 <<
"\n" <<
"rounds: " << stats.
rounds 90 <<
"\n" <<
"hp: " << stats.
hp 91 <<
"\n" <<
"max_hp: " << stats.
max_hp 93 <<
"\n" <<
"damage: " << stats.
damage 97 <<
"\n" <<
"num_blows: " << stats.
num_blows 98 <<
"\n" <<
"swarm_min: " << stats.
swarm_min 99 <<
"\n" <<
"swarm_max: " << stats.
swarm_max 102 std::cout << ss.rdbuf() << std::endl;
109 using summary_t = std::array<std::vector<double>, 2>;
128 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes);
129 combat_slice(
const summary_t& src_summary,
unsigned num_strikes);
135 combat_slice::combat_slice(
136 const summary_t& src_summary,
unsigned begin,
unsigned end,
unsigned num_strikes)
140 , strikes(num_strikes)
142 if(src_summary[0].empty()) {
149 if(end > src_summary[0].
size()) {
150 end = src_summary[0].size();
154 for(
unsigned i = begin;
i < end; ++
i) {
155 prob += src_summary[0][
i];
158 if(!src_summary[1].empty()) {
159 for(
unsigned i = begin;
i < end; ++
i) {
160 prob += src_summary[1][
i];
169 combat_slice::combat_slice(
const summary_t& src_summary,
unsigned num_strikes)
171 , end_hp(src_summary[0].
size())
173 , strikes(num_strikes)
185 unsigned old_strikes = stats.
calc_blows(cur_hp);
189 while(++cur_hp <= stats.
max_hp) {
202 std::vector<combat_slice> split_summary(
205 std::vector<combat_slice> result;
209 result.emplace_back(summary, unit_stats.
num_blows);
213 debug((
"Slicing:\n"));
215 unsigned cur_end = 0;
218 const unsigned cur_begin = cur_end;
219 cur_end = hp_for_next_attack(cur_begin, unit_stats);
222 combat_slice slice(summary, cur_begin, cur_end, unit_stats.
calc_blows(cur_begin));
223 if(slice.prob != 0.0) {
224 result.push_back(slice);
225 debug((
"\t%2u-%2u hp; strikes: %u; probability: %6.2f\n", cur_begin, cur_end, slice.strikes,
226 slice.prob * 100.0));
228 }
while(cur_end <= unit_stats.
max_hp);
244 prob_matrix(
unsigned int a_max,
250 const summary_t& a_initial,
251 const summary_t& b_initial);
254 void shift_cols(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
256 void shift_rows(
unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent);
259 void move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col);
261 void move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row);
264 void merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row);
265 void merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row);
266 void merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col);
267 void merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col);
273 void record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed);
276 static unsigned int plane_index(
bool a_slowed,
bool b_slowed)
278 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
282 double prob_of_zero(
bool check_a,
bool check_b)
const;
284 double row_sum(
unsigned plane,
unsigned row)
const;
286 double col_sum(
unsigned plane,
unsigned column)
const;
288 void sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const;
291 bool plane_used(
unsigned p)
const 293 return p < NUM_PLANES && plane_[
p] !=
nullptr;
296 unsigned int num_rows()
const 300 unsigned int num_cols()
const 320 std::unique_ptr<double[]> new_plane()
const;
322 void initialize_plane(
unsigned plane,
325 const std::vector<double>& a_initial,
326 const std::vector<double>& b_initial);
328 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial);
330 double& val(
unsigned plane,
unsigned row,
unsigned col);
331 const double& val(
unsigned plane,
unsigned row,
unsigned col)
const;
334 void xfer(
unsigned dst_plane,
342 void xfer(
unsigned dst_plane,
349 void shift_cols_in_row(
unsigned dst,
352 const std::vector<unsigned>& cols,
358 void shift_rows_in_col(
unsigned dst,
361 const std::vector<unsigned>& rows,
369 const unsigned int rows_, cols_;
370 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
374 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
390 prob_matrix::prob_matrix(
unsigned int a_max,
396 const summary_t& a_initial,
397 const summary_t& b_initial)
405 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
406 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
409 for(
unsigned plane = 0; plane != NUM_PLANES; ++plane) {
410 used_rows_[plane].insert(0u);
411 used_cols_[plane].insert(0u);
415 need_a_slowed = need_a_slowed || !a_initial[1].empty();
416 need_b_slowed = need_b_slowed || !b_initial[1].empty();
419 plane_[NEITHER_SLOWED] = new_plane();
420 plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
421 plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
422 plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ?
nullptr : new_plane();
425 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
427 if(!a_initial[1].empty()) {
428 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
431 if(!b_initial[1].empty()) {
432 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
435 if(!a_initial[1].empty() && !b_initial[1].empty()) {
436 initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
440 if(!a_initial[0].empty()) {
441 debug((
"A has fought before (or is slowed).\n"));
445 if(!b_initial[0].empty()) {
446 debug((
"B has fought before (or is slowed).\n"));
452 std::unique_ptr<double[]> prob_matrix::new_plane()
const 454 const unsigned int size = rows_ * cols_;
455 std::unique_ptr<double[]> res(
new double[size]);
456 std::fill_n(res.get(),
size, 0);
469 void prob_matrix::initialize_plane(
unsigned plane,
472 const std::vector<double>& a_initial,
473 const std::vector<double>& b_initial)
475 if(!a_initial.empty()) {
476 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
478 for(
unsigned row = 0; row < row_count; ++row) {
479 if(a_initial[row] != 0.0) {
480 used_rows_[plane].insert(row);
481 initialize_row(plane, row, a_initial[row], b_cur, b_initial);
485 used_rows_[plane].insert(a_cur);
487 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
500 void prob_matrix::initialize_row(
501 unsigned plane,
unsigned row,
double row_prob,
unsigned b_cur,
const std::vector<double>& b_initial)
503 if(!b_initial.empty()) {
504 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
506 for(
unsigned col = 0; col < col_count; ++col) {
507 if(b_initial[col] != 0.0) {
508 used_cols_[plane].insert(col);
509 val(plane, row, col) = row_prob * b_initial[col];
514 used_cols_[plane].insert(b_cur);
515 val(plane, row, b_cur) = row_prob;
519 double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
523 return plane_[plane][row * cols_ + col];
526 const double& prob_matrix::val(
unsigned plane,
unsigned row,
unsigned col)
const 530 return plane_[plane][row * cols_ + col];
537 void prob_matrix::xfer(
unsigned dst_plane,
545 double& src = val(src_plane, row_src, col_src);
547 double diff = src * prob;
550 double& dst = val(dst_plane, row_dst, col_dst);
553 used_rows_[dst_plane].insert(row_dst);
554 used_cols_[dst_plane].insert(col_dst);
558 debug((
"Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
559 src_plane == NEITHER_SLOWED
561 : src_plane == A_SLOWED
563 : src_plane == B_SLOWED
565 : src_plane == BOTH_SLOWED
570 dst_plane == NEITHER_SLOWED
572 : dst_plane == A_SLOWED
574 : dst_plane == B_SLOWED
576 : dst_plane == BOTH_SLOWED
587 void prob_matrix::xfer(
588 unsigned dst_plane,
unsigned src_plane,
unsigned row_dst,
unsigned col_dst,
unsigned row_src,
unsigned col_src)
590 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
594 double& src = val(src_plane, row_src, col_src);
596 debug((
"Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
597 src_plane == NEITHER_SLOWED
599 : src_plane == A_SLOWED
601 : src_plane == B_SLOWED
603 : src_plane == BOTH_SLOWED
607 dst_plane == NEITHER_SLOWED
609 : dst_plane == A_SLOWED
611 : dst_plane == B_SLOWED
613 : dst_plane == BOTH_SLOWED
619 double& dst = val(dst_plane, row_dst, col_dst);
622 used_rows_[dst_plane].insert(row_dst);
623 used_cols_[dst_plane].insert(col_dst);
635 void prob_matrix::shift_cols_in_row(
unsigned dst,
638 const std::vector<unsigned>& cols,
646 int row_i =
static_cast<int>(row);
647 int max_row =
static_cast<int>(rows_) - 1;
653 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
656 int col_i =
static_cast<int>(cols[col_x]);
657 int drain_amount = col_i * drain_percent / 100 + drain_constant;
658 unsigned newrow = std::clamp(row_i + drain_amount, 1, max_row);
659 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
663 unsigned newrow = std::clamp(row_i + drainmax, 1, max_row);
664 for(; col_x < cols.size(); ++col_x) {
665 xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
675 void prob_matrix::shift_cols(
676 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
678 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
680 if(drain_constant || drain_percent) {
681 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
686 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
687 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
693 for(
unsigned row_x = rows.size() - 1; row_x != 0; --row_x) {
694 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
698 for(
unsigned row_x = 1; row_x != rows.size(); ++row_x) {
699 shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax, drain_constant, drain_percent);
708 void prob_matrix::shift_rows_in_col(
unsigned dst,
711 const std::vector<unsigned>& rows,
719 int col_i =
static_cast<int>(col);
720 int max_col =
static_cast<int>(cols_) - 1;
726 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
729 int row_i =
static_cast<int>(rows[row_x]);
730 int drain_amount = row_i * drain_percent / 100 + drain_constant;
731 unsigned newcol = std::clamp(col_i + drain_amount, 1, max_col);
732 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
736 unsigned newcol = std::clamp(col_i + drainmax, 1, max_col);
737 for(; row_x < rows.size(); ++row_x) {
738 xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
748 void prob_matrix::shift_rows(
749 unsigned dst,
unsigned src,
unsigned damage,
double prob,
int drain_constant,
int drain_percent)
751 int drainmax = (drain_percent * (
static_cast<signed>(damage)) / 100 + drain_constant);
753 if(drain_constant || drain_percent) {
754 debug((
"Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
759 const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
760 const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
766 for(
unsigned col_x = cols.size() - 1; col_x != 0; --col_x)
767 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
770 for(
unsigned col_x = 1; col_x != cols.size(); ++col_x) {
771 shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax, drain_constant, drain_percent);
779 void prob_matrix::move_column(
unsigned d_plane,
unsigned s_plane,
unsigned d_col,
unsigned s_col)
782 for(
const unsigned& row : used_rows_[s_plane]) {
783 xfer(d_plane, s_plane, row, d_col, row, s_col);
790 void prob_matrix::move_row(
unsigned d_plane,
unsigned s_plane,
unsigned d_row,
unsigned s_row)
793 for(
const unsigned& col : used_cols_[s_plane]) {
794 xfer(d_plane, s_plane, d_row, col, s_row, col);
802 void prob_matrix::merge_col(
unsigned d_plane,
unsigned s_plane,
unsigned col,
unsigned d_row)
804 auto rows_end = used_rows_[s_plane].end();
805 auto row_it = used_rows_[s_plane].begin();
808 for(++row_it; row_it != rows_end; ++row_it) {
809 xfer(d_plane, s_plane, d_row, col, *row_it, col);
817 void prob_matrix::merge_cols(
unsigned d_plane,
unsigned s_plane,
unsigned d_row)
819 auto rows_end = used_rows_[s_plane].end();
820 auto row_it = used_rows_[s_plane].begin();
823 for(++row_it; row_it != rows_end; ++row_it) {
824 for(
const unsigned& col : used_cols_[s_plane]) {
825 xfer(d_plane, s_plane, d_row, col, *row_it, col);
834 void prob_matrix::merge_row(
unsigned d_plane,
unsigned s_plane,
unsigned row,
unsigned d_col)
836 auto cols_end = used_cols_[s_plane].end();
837 auto col_it = used_cols_[s_plane].begin();
840 for(++col_it; col_it != cols_end; ++col_it) {
841 xfer(d_plane, s_plane, row, d_col, row, *col_it);
849 void prob_matrix::merge_rows(
unsigned d_plane,
unsigned s_plane,
unsigned d_col)
851 auto cols_end = used_cols_[s_plane].end();
853 auto cols_begin =
std::next(used_cols_[s_plane].begin());
856 for(
const unsigned row : used_rows_[s_plane]) {
857 for(
auto col_it = cols_begin; col_it != cols_end; ++col_it) {
858 xfer(d_plane, s_plane, row, d_col, row, *col_it);
868 for(
unsigned int p = 0u;
p < NUM_PLANES; ++
p) {
873 if(used_rows_[
p].empty()) {
878 auto [first_row, last_row] = std::minmax_element(used_rows_[
p].begin(), used_rows_[
p].end());
879 for(
unsigned int r = *first_row; r <= *last_row; ++r) {
880 for(
unsigned int c = 0u;
c < cols_; ++
c) {
881 plane_[
p][r * cols_ +
c] = 0.0;
885 used_rows_[
p].clear();
886 used_cols_[
p].clear();
892 used_rows_[
p].insert(0u);
893 used_cols_[
p].insert(0u);
900 void prob_matrix::record_monte_carlo_result(
unsigned int a_hp,
unsigned int b_hp,
bool a_slowed,
bool b_slowed)
902 assert(a_hp <= rows_);
903 assert(b_hp <= cols_);
904 unsigned int plane = plane_index(a_slowed, b_slowed);
905 ++val(plane, a_hp, b_hp);
906 used_rows_[plane].insert(a_hp);
907 used_cols_[plane].insert(b_hp);
913 double prob_matrix::prob_of_zero(
bool check_a,
bool check_b)
const 917 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
924 for(
const unsigned& row : used_rows_[
p]) {
925 prob += val(p, row, 0);
931 for(
const unsigned& col : used_cols_[
p]) {
932 prob += val(p, 0, col);
945 double prob_matrix::row_sum(
unsigned plane,
unsigned row)
const 947 if(!plane_used(plane)) {
952 for(
unsigned col : used_cols_[plane]) {
953 sum += val(plane, row, col);
961 double prob_matrix::col_sum(
unsigned plane,
unsigned column)
const 963 if(!plane_used(plane)) {
968 for(
unsigned row : used_rows_[plane]) {
969 sum += val(plane, row, column);
979 void prob_matrix::sum(
unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums)
const 981 for(
const unsigned& row : used_rows_[plane]) {
982 for(
const unsigned& col : used_cols_[plane]) {
983 const double& prob = val(plane, row, col);
984 row_sums[row] += prob;
985 col_sums[col] += prob;
990 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG) 991 void prob_matrix::dump()
const 993 unsigned int row, col, m;
994 const char*
names[] {
"NEITHER_SLOWED",
"A_SLOWED",
"B_SLOWED",
"BOTH_SLOWED"};
996 for(m = 0; m < NUM_PLANES; ++m) {
1002 for(row = 0; row < rows_; ++row) {
1004 for(col = 0; col < cols_; ++col) {
1005 debug((
"%4.3g ", val(m, row, col) * 100));
1013 void prob_matrix::dump()
const 1023 class combat_matrix :
protected prob_matrix
1026 combat_matrix(
unsigned int a_max_hp,
1027 unsigned int b_max_hp,
1030 const summary_t& a_summary,
1031 const summary_t& b_summary,
1034 unsigned int a_damage,
1035 unsigned int b_damage,
1036 unsigned int a_slow_damage,
1037 unsigned int b_slow_damage,
1038 int a_drain_percent,
1039 int b_drain_percent,
1040 int a_drain_constant,
1041 int b_drain_constant);
1043 virtual ~combat_matrix()
1048 void remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp);
1049 void remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp);
1051 void forced_levelup_a();
1052 void conditional_levelup_a();
1054 void forced_levelup_b();
1055 void conditional_levelup_b();
1057 using prob_matrix::row_sum;
1058 using prob_matrix::col_sum;
1061 virtual void extract_results(
1062 summary_t& summary_a, summary_t& summary_b)
1067 prob_matrix::dump();
1074 unsigned a_slow_damage_;
1075 int a_drain_percent_;
1076 int a_drain_constant_;
1081 unsigned b_slow_damage_;
1082 int b_drain_percent_;
1083 int b_drain_constant_;
1100 combat_matrix::combat_matrix(
unsigned int a_max_hp,
1101 unsigned int b_max_hp,
1104 const summary_t& a_summary,
1105 const summary_t& b_summary,
1108 unsigned int a_damage,
1109 unsigned int b_damage,
1110 unsigned int a_slow_damage,
1111 unsigned int b_slow_damage,
1112 int a_drain_percent,
1113 int b_drain_percent,
1114 int a_drain_constant,
1115 int b_drain_constant)
1117 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1118 , a_max_hp_(a_max_hp)
1120 , a_damage_(a_damage)
1121 , a_slow_damage_(a_slow_damage)
1122 , a_drain_percent_(a_drain_percent)
1123 , a_drain_constant_(a_drain_constant)
1124 , b_max_hp_(b_max_hp)
1126 , b_damage_(b_damage)
1127 , b_slow_damage_(b_slow_damage)
1128 , b_drain_percent_(b_drain_percent)
1129 , b_drain_constant_(b_drain_constant)
1134 void combat_matrix::remove_petrify_distortion_a(
unsigned damage,
unsigned slow_damage,
unsigned b_hp)
1136 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1137 if(!plane_used(
p)) {
1142 unsigned actual_damage = (
p & 1) ? slow_damage : damage;
1143 if(b_hp > actual_damage) {
1145 move_column(
p,
p, b_hp - actual_damage, 0);
1150 void combat_matrix::remove_petrify_distortion_b(
unsigned damage,
unsigned slow_damage,
unsigned a_hp)
1152 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1153 if(!plane_used(
p)) {
1158 unsigned actual_damage = (
p & 2) ? slow_damage : damage;
1159 if(a_hp > actual_damage) {
1161 move_row(
p,
p, a_hp - actual_damage, 0);
1166 void combat_matrix::forced_levelup_a()
1170 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1172 merge_cols(
p & -2,
p, a_max_hp_);
1177 void combat_matrix::forced_levelup_b()
1181 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1183 merge_rows(
p & -3,
p, b_max_hp_);
1188 void combat_matrix::conditional_levelup_a()
1192 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1194 merge_col(
p & -2,
p, 0, a_max_hp_);
1199 void combat_matrix::conditional_levelup_b()
1203 for(
int p = 0;
p < NUM_PLANES; ++
p) {
1205 merge_row(
p & -3,
p, 0, b_max_hp_);
1215 class probability_combat_matrix :
public combat_matrix
1218 probability_combat_matrix(
unsigned int a_max_hp,
1219 unsigned int b_max_hp,
1222 const summary_t& a_summary,
1223 const summary_t& b_summary,
1226 unsigned int a_damage,
1227 unsigned int b_damage,
1228 unsigned int a_slow_damage,
1229 unsigned int b_slow_damage,
1230 int a_drain_percent,
1231 int b_drain_percent,
1232 int a_drain_constant,
1233 int b_drain_constant);
1236 void receive_blow_b(
double hit_chance);
1238 void receive_blow_a(
double hit_chance);
1241 double dead_prob()
const 1243 return prob_of_zero(
true,
true);
1247 double dead_prob_a()
const 1249 return prob_of_zero(
true,
false);
1253 double dead_prob_b()
const 1255 return prob_of_zero(
false,
true);
1258 void extract_results(
1259 summary_t& summary_a, summary_t& summary_b)
override;
1275 probability_combat_matrix::probability_combat_matrix(
unsigned int a_max_hp,
1276 unsigned int b_max_hp,
1279 const summary_t& a_summary,
1280 const summary_t& b_summary,
1283 unsigned int a_damage,
1284 unsigned int b_damage,
1285 unsigned int a_slow_damage,
1286 unsigned int b_slow_damage,
1287 int a_drain_percent,
1288 int b_drain_percent,
1289 int a_drain_constant,
1290 int b_drain_constant)
1291 : combat_matrix(a_max_hp,
1312 void probability_combat_matrix::receive_blow_b(
double hit_chance)
1315 unsigned src = NUM_PLANES;
1317 if(!plane_used(src)) {
1322 int dst = a_slows_ ? src | 2 : src;
1325 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1327 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1333 void probability_combat_matrix::receive_blow_a(
double hit_chance)
1336 unsigned src = NUM_PLANES;
1338 if(!plane_used(src)) {
1343 int dst = b_slows_ ? src | 1 : src;
1346 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1348 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1352 void probability_combat_matrix::extract_results(
1353 summary_t& summary_a, summary_t& summary_b)
1356 summary_a[0] = std::vector<double>(num_rows());
1357 summary_b[0] = std::vector<double>(num_cols());
1359 if(plane_used(A_SLOWED)) {
1360 summary_a[1] = std::vector<double>(num_rows());
1363 if(plane_used(B_SLOWED)) {
1364 summary_b[1] = std::vector<double>(num_cols());
1367 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1368 if(!plane_used(
p)) {
1373 unsigned dst_a = (
p & 1) ? 1u : 0u;
1375 unsigned dst_b = (
p & 2) ? 1u : 0u;
1376 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1388 class monte_carlo_combat_matrix :
public combat_matrix
1391 monte_carlo_combat_matrix(
unsigned int a_max_hp,
1392 unsigned int b_max_hp,
1395 const summary_t& a_summary,
1396 const summary_t& b_summary,
1399 unsigned int a_damage,
1400 unsigned int b_damage,
1401 unsigned int a_slow_damage,
1402 unsigned int b_slow_damage,
1403 int a_drain_percent,
1404 int b_drain_percent,
1405 int a_drain_constant,
1406 int b_drain_constant,
1407 unsigned int rounds,
1408 double a_hit_chance,
1409 double b_hit_chance,
1410 const std::vector<combat_slice>& a_split,
1411 const std::vector<combat_slice>& b_split,
1412 double a_initially_slowed_chance,
1413 double b_initially_slowed_chance);
1417 void extract_results(
1418 summary_t& summary_a, summary_t& summary_b)
override;
1420 double get_a_hit_probability()
const;
1421 double get_b_hit_probability()
const;
1424 static const unsigned int NUM_ITERATIONS = 5000u;
1426 std::vector<double> a_initial_;
1427 std::vector<double> b_initial_;
1428 std::vector<double> a_initial_slowed_;
1429 std::vector<double> b_initial_slowed_;
1430 std::vector<combat_slice> a_split_;
1431 std::vector<combat_slice> b_split_;
1432 unsigned int rounds_;
1433 double a_hit_chance_;
1434 double b_hit_chance_;
1435 double a_initially_slowed_chance_;
1436 double b_initially_slowed_chance_;
1437 unsigned int iterations_a_hit_ = 0u;
1438 unsigned int iterations_b_hit_ = 0u;
1440 unsigned int calc_blows_a(
unsigned int a_hp)
const;
1441 unsigned int calc_blows_b(
unsigned int b_hp)
const;
1442 static void divide_all_elements(std::vector<double>& vec,
double divisor);
1443 static void scale_probabilities(
1444 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp);
1447 monte_carlo_combat_matrix::monte_carlo_combat_matrix(
unsigned int a_max_hp,
1448 unsigned int b_max_hp,
1451 const summary_t& a_summary,
1452 const summary_t& b_summary,
1455 unsigned int a_damage,
1456 unsigned int b_damage,
1457 unsigned int a_slow_damage,
1458 unsigned int b_slow_damage,
1459 int a_drain_percent,
1460 int b_drain_percent,
1461 int a_drain_constant,
1462 int b_drain_constant,
1463 unsigned int rounds,
1464 double a_hit_chance,
1465 double b_hit_chance,
1466 const std::vector<combat_slice>& a_split,
1467 const std::vector<combat_slice>& b_split,
1468 double a_initially_slowed_chance,
1469 double b_initially_slowed_chance)
1470 : combat_matrix(a_max_hp,
1489 , a_hit_chance_(a_hit_chance)
1490 , b_hit_chance_(b_hit_chance)
1491 , a_initially_slowed_chance_(a_initially_slowed_chance)
1492 , b_initially_slowed_chance_(b_initially_slowed_chance)
1494 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1495 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1496 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1497 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1502 void monte_carlo_combat_matrix::simulate()
1506 for(
unsigned int i = 0u;
i < NUM_ITERATIONS; ++
i) {
1511 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1512 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1515 unsigned int a_strikes = calc_blows_a(a_hp);
1516 unsigned int b_strikes = calc_blows_b(b_hp);
1518 for(
unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1519 for(
unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1523 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1524 damage = std::min(damage, b_hp);
1526 b_slowed |= a_slows_;
1528 int drain_amount = (a_drain_percent_ *
static_cast<signed>(damage) / 100 + a_drain_constant_);
1529 a_hp = std::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1543 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1544 damage = std::min(damage, a_hp);
1546 a_slowed |= b_slows_;
1548 int drain_amount = (b_drain_percent_ *
static_cast<signed>(damage) / 100 + b_drain_constant_);
1549 b_hp = std::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1562 iterations_a_hit_ += a_hit ? 1 : 0;
1563 iterations_b_hit_ += b_hit ? 1 : 0;
1565 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1573 void monte_carlo_combat_matrix::extract_results(
1574 summary_t& summary_a, summary_t& summary_b)
1577 summary_a[0] = std::vector<double>(num_rows());
1578 summary_b[0] = std::vector<double>(num_cols());
1580 if(plane_used(A_SLOWED)) {
1581 summary_a[1] = std::vector<double>(num_rows());
1584 if(plane_used(B_SLOWED)) {
1585 summary_b[1] = std::vector<double>(num_cols());
1588 for(
unsigned p = 0;
p < NUM_PLANES; ++
p) {
1589 if(!plane_used(
p)) {
1594 unsigned dst_a = (
p & 1) ? 1u : 0u;
1596 unsigned dst_b = (
p & 2) ? 1u : 0u;
1597 sum(
p, summary_a[dst_a], summary_b[dst_b]);
1600 divide_all_elements(summary_a[0], static_cast<double>(NUM_ITERATIONS));
1601 divide_all_elements(summary_b[0], static_cast<double>(NUM_ITERATIONS));
1603 if(plane_used(A_SLOWED)) {
1604 divide_all_elements(summary_a[1], static_cast<double>(NUM_ITERATIONS));
1607 if(plane_used(B_SLOWED)) {
1608 divide_all_elements(summary_b[1], static_cast<double>(NUM_ITERATIONS));
1612 double monte_carlo_combat_matrix::get_a_hit_probability()
const 1614 return static_cast<double>(iterations_a_hit_) / static_cast<double>(NUM_ITERATIONS);
1617 double monte_carlo_combat_matrix::get_b_hit_probability()
const 1619 return static_cast<double>(iterations_b_hit_) / static_cast<double>(NUM_ITERATIONS);
1622 unsigned int monte_carlo_combat_matrix::calc_blows_a(
unsigned int a_hp)
const 1624 auto it = a_split_.begin();
1625 while(it != a_split_.end() && it->end_hp <= a_hp) {
1629 if(it == a_split_.end()) {
1636 unsigned int monte_carlo_combat_matrix::calc_blows_b(
unsigned int b_hp)
const 1638 auto it = b_split_.begin();
1639 while(it != b_split_.end() && it->end_hp <= b_hp) {
1643 if(it == b_split_.end()) {
1650 void monte_carlo_combat_matrix::scale_probabilities(
1651 const std::vector<double>& source, std::vector<double>& target,
double divisor,
unsigned int singular_hp)
1653 if(divisor == 0.0) {
1659 if(source.empty()) {
1660 target.resize(singular_hp + 1u, 0.0);
1661 target[singular_hp] = 1.0;
1664 source.begin(), source.end(), std::back_inserter(target), [=](
double prob) {
return prob / divisor; });
1667 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1670 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec,
double divisor)
1672 for(
double&
e : vec) {
1680 : hp_dist(u.max_hp + 1, 0.0)
1725 void forced_levelup(std::vector<double>&
hp_dist)
1730 auto i = hp_dist.begin();
1732 for(++
i;
i != hp_dist.end(); ++
i) {
1737 hp_dist.back() = 1 - hp_dist.front();
1740 void conditional_levelup(std::vector<double>& hp_dist,
double kill_prob)
1745 double scalefactor = 0;
1746 const double chance_to_survive = 1 - hp_dist.front();
1747 if(chance_to_survive > DBL_MIN) {
1748 scalefactor = 1 - kill_prob / chance_to_survive;
1751 auto i = hp_dist.begin();
1753 for(++
i;
i != hp_dist.end(); ++
i) {
1758 hp_dist.back() += kill_prob;
1769 double calculate_probability_of_debuff(
double initial_prob,
bool enemy_gives,
double prob_touched,
double prob_stay_alive,
bool kill_heals,
double prob_kill)
1771 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1775 prob_touched = std::max(prob_touched, 0.0);
1777 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1781 prob_kill = std::clamp(prob_kill, 0.0, 1.0);
1784 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1786 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1790 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1793 const double prob_survive_if_not_hit = 1.0;
1795 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1800 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1803 double prob_debuff = 0.0;
1806 prob_debuff += prob_already_debuffed_not_touched;
1808 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1812 prob_debuff += prob_already_debuffed_touched;
1814 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1821 }
else if(!kill_heals) {
1822 prob_debuff += prob_initially_healthy_touched;
1824 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1831 void round_prob_if_close_to_sure(
double& prob)
1835 }
else if(prob > 1.0 - 1.0
e-9) {
1844 unsigned min_hp(
const std::vector<double>& hp_dist,
unsigned def)
1846 const unsigned size = hp_dist.size();
1849 for(
unsigned i = 0;
i !=
size; ++
i) {
1850 if(hp_dist[
i] != 0.0) {
1867 unsigned int fight_complexity(
unsigned int num_slices,
1868 unsigned int opp_num_slices,
1872 return num_slices * opp_num_slices * ((stats.
slows || opp_stats.
is_slowed) ? 2 : 1)
1889 unsigned opp_strikes,
1890 std::vector<double>& hp_dist,
1891 std::vector<double>& opp_hp_dist,
1892 double& self_not_hit,
1893 double& opp_not_hit,
1894 bool levelup_considered)
1900 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1901 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1903 if(opp_hp_dist.empty()) {
1905 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1906 opp_hp_dist[opp_stats.
hp] = 1.0;
1908 for(
unsigned int i = 0;
i < strikes; ++
i) {
1909 for(
int j =
i; j >= 0; j--) {
1910 unsigned src_index = opp_stats.
hp - j * stats.
damage;
1911 double move = opp_hp_dist[src_index] * hit_chance;
1912 opp_hp_dist[src_index] -= move;
1913 opp_hp_dist[src_index - stats.
damage] += move;
1916 opp_not_hit *= 1.0 - hit_chance;
1920 for(
unsigned int i = 0;
i < strikes; ++
i) {
1921 for(
unsigned int j = stats.
damage; j < opp_hp_dist.size(); ++j) {
1922 double move = opp_hp_dist[j] * hit_chance;
1923 opp_hp_dist[j] -= move;
1924 opp_hp_dist[j - stats.
damage] += move;
1926 opp_not_hit *= 1.0 - hit_chance;
1932 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1933 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_alive_prob;
1935 if(hp_dist.empty()) {
1937 hp_dist = std::vector<double>(stats.
max_hp + 1);
1938 hp_dist[stats.
hp] = 1.0;
1939 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1940 for(
int j =
i; j >= 0; j--) {
1941 unsigned src_index = stats.
hp - j * opp_stats.
damage;
1942 double move = hp_dist[src_index] * opp_hit_chance;
1943 hp_dist[src_index] -= move;
1944 hp_dist[src_index - opp_stats.
damage] += move;
1947 self_not_hit *= 1.0 - opp_hit_chance;
1951 for(
unsigned int i = 0;
i < opp_strikes; ++
i) {
1952 for(
unsigned int j = opp_stats.
damage; j < hp_dist.size(); ++j) {
1953 double move = hp_dist[j] * opp_hit_chance;
1955 hp_dist[j - opp_stats.
damage] += move;
1958 self_not_hit *= 1.0 - opp_hit_chance;
1962 if(!levelup_considered) {
1967 forced_levelup(hp_dist);
1971 forced_levelup(opp_hp_dist);
1979 unsigned opp_strikes,
1980 std::vector<double>& hp_dist,
1981 std::vector<double>& opp_hp_dist,
1982 double& self_not_hit,
1983 double& opp_not_hit,
1984 bool levelup_considered)
1989 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1993 const double hit_chance = (stats.
chance_to_hit / 100.0) * alive_prob;
1995 if(opp_hp_dist.empty()) {
1996 opp_hp_dist = std::vector<double>(opp_stats.
max_hp + 1);
1997 if(strikes == 1 && opp_stats.
hp > 0) {
1998 opp_hp_dist[opp_stats.
hp] = 1.0 - hit_chance;
1999 opp_hp_dist[std::max<int>(opp_stats.
hp - stats.
damage, 0)] = hit_chance;
2000 opp_not_hit *= 1.0 - hit_chance;
2002 opp_hp_dist[opp_stats.
hp] = 1.0;
2006 for(
unsigned int i = 1;
i < opp_hp_dist.size(); ++
i) {
2007 double move = opp_hp_dist[
i] * hit_chance;
2008 opp_hp_dist[
i] -= move;
2009 opp_hp_dist[std::max<int>(
i - stats.
damage, 0)] += move;
2012 opp_not_hit *= 1.0 - hit_chance;
2017 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2018 const double opp_hit_chance = (opp_stats.
chance_to_hit / 100.0) * opp_attack_prob;
2020 if(hp_dist.empty()) {
2021 hp_dist = std::vector<double>(stats.
max_hp + 1);
2022 if(opp_strikes == 1 && stats.
hp > 0) {
2023 hp_dist[stats.
hp] = 1.0 - opp_hit_chance;
2024 hp_dist[std::max<int>(stats.
hp - opp_stats.
damage, 0)] = opp_hit_chance;
2025 self_not_hit *= 1.0 - opp_hit_chance;
2027 hp_dist[stats.
hp] = 1.0;
2030 if(opp_strikes == 1) {
2031 for(
unsigned int i = 1;
i < hp_dist.size(); ++
i) {
2032 double move = hp_dist[
i] * opp_hit_chance;
2034 hp_dist[std::max<int>(
i - opp_stats.
damage, 0)] += move;
2037 self_not_hit *= 1.0 - opp_hit_chance;
2041 if(!levelup_considered) {
2046 forced_levelup(hp_dist);
2048 conditional_levelup(hp_dist, opp_hp_dist[0]);
2052 forced_levelup(opp_hp_dist);
2054 conditional_levelup(opp_hp_dist, hp_dist[0]);
2064 unsigned opp_strikes,
2066 summary_t& opp_summary,
2067 double& self_not_hit,
2068 double& opp_not_hit,
2069 bool levelup_considered,
2070 std::vector<combat_slice>
split,
2071 std::vector<combat_slice> opp_split,
2072 double initially_slowed_chance,
2073 double opp_initially_slowed_chance)
2075 unsigned int rounds = std::max<unsigned int>(stats.
rounds, opp_stats.
rounds);
2076 unsigned max_attacks = std::max(strikes, opp_strikes);
2078 debug((
"A gets %u attacks, B %u.\n", strikes, opp_strikes));
2081 unsigned int b_damage = opp_stats.
damage, b_slow_damage = opp_stats.
slow_damage;
2087 a_damage = a_slow_damage = opp_stats.
max_hp;
2091 b_damage = b_slow_damage = stats.
max_hp;
2094 const double original_self_not_hit = self_not_hit;
2095 const double original_opp_not_hit = opp_not_hit;
2097 const double opp_hit_chance = opp_stats.
chance_to_hit / 100.0;
2098 double self_hit = 0.0;
2099 double opp_hit = 0.0;
2100 double self_hit_unknown = 1.0;
2101 double opp_hit_unknown = 1.0;
2104 std::unique_ptr<combat_matrix> m;
2105 if(mode == attack_prediction_mode::probability_calculation) {
2106 debug((
"Using exact probability calculations.\n"));
2108 probability_combat_matrix* pm =
new probability_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2109 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2110 a_damage, b_damage, a_slow_damage, b_slow_damage,
2115 for(
unsigned int i = 0;
i < max_attacks; ++
i) {
2117 debug((
"A strikes\n"));
2118 double b_already_dead = pm->dead_prob_b();
2119 pm->receive_blow_b(hit_chance);
2122 double first_hit = hit_chance * opp_hit_unknown;
2123 opp_hit += first_hit;
2124 opp_hit_unknown -= first_hit;
2125 double both_were_alive = (1.0 - b_already_dead) * (1.0 - pm->dead_prob_a());
2126 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2127 self_hit_unknown *= (1.0 - this_hit_killed_b);
2129 if(
i < opp_strikes) {
2130 debug((
"B strikes\n"));
2131 double a_already_dead = pm->dead_prob_a();
2132 pm->receive_blow_a(opp_hit_chance);
2135 double first_hit = opp_hit_chance * self_hit_unknown;
2136 self_hit += first_hit;
2137 self_hit_unknown -= first_hit;
2138 double both_were_alive = (1.0 - a_already_dead) * (1.0 - pm->dead_prob_b());
2139 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2140 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2144 debug((
"Combat ends:\n"));
2146 }
while(--rounds && pm->dead_prob() < 0.99);
2148 self_hit = std::min(self_hit, 1.0);
2149 opp_hit = std::min(opp_hit, 1.0);
2151 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2152 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2161 unsigned int plane = plane_index(stats, opp_stats);
2162 double not_hit = pm->col_sum(plane, opp_stats.
hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.
hp));
2163 opp_not_hit = original_opp_not_hit * not_hit;
2165 if(opp_stats.
slows) {
2166 unsigned int plane = plane_index(stats, opp_stats);
2167 double not_hit = pm->row_sum(plane, stats.
hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.
hp));
2168 self_not_hit = original_self_not_hit * not_hit;
2171 debug((
"Using Monte Carlo simulation.\n"));
2173 monte_carlo_combat_matrix* mcm =
new monte_carlo_combat_matrix(stats.
max_hp, opp_stats.
max_hp, stats.
hp,
2174 opp_stats.
hp, summary, opp_summary, stats.
slows, opp_stats.
slows,
2175 a_damage, b_damage, a_slow_damage, b_slow_damage,
2177 hit_chance, opp_hit_chance, split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2181 debug((
"Combat ends:\n"));
2184 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2185 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2196 if(levelup_considered) {
2198 m->forced_levelup_a();
2200 m->conditional_levelup_a();
2204 m->forced_levelup_b();
2206 m->conditional_levelup_b();
2211 m->extract_results(summary, opp_summary);
2221 unsigned opp_strikes,
2223 summary_t& opp_summary,
2224 double& self_not_hit,
2225 double& opp_not_hit,
2226 bool levelup_considered)
2232 && opp_summary[1].empty())
2234 if(strikes <= 1 && opp_strikes <= 1) {
2235 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2236 opp_not_hit, levelup_considered);
2237 }
else if(strikes * stats.
damage < min_hp(opp_summary[0], opp_stats.
hp)
2238 && opp_strikes * opp_stats.
damage < min_hp(summary[0], stats.
hp)) {
2239 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2240 opp_not_hit, levelup_considered);
2242 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2243 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2244 std::vector<combat_slice>(), 0.0, 0.0);
2247 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2248 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2249 std::vector<combat_slice>(), 0.0, 0.0);
2258 void init_slice_summary(
2259 std::vector<double>& dst,
const std::vector<double>& src,
unsigned begin_hp,
unsigned end_hp,
double prob)
2266 const unsigned size = src.size();
2273 dst.resize(size, 0.0);
2274 for(
unsigned i = begin_hp;
i < end_hp; ++
i) {
2275 dst[
i] = src[
i] / prob;
2283 void merge_slice_summary(std::vector<double>& dst,
const std::vector<double>& src,
double prob)
2285 const unsigned size = src.size();
2288 if(dst.size() <
size) {
2289 dst.resize(size, 0.0);
2293 for(
unsigned i = 0;
i !=
size; ++
i) {
2294 dst[
i] += src[
i] * prob;
2309 opponent.
fight(*
this, levelup_considered);
2313 #ifdef ATTACK_PREDICTION_DEBUG 2322 complex_fight(opponent, 1);
2323 std::vector<double> res =
summary[0], opp_res = opponent.
summary[0];
2325 opponent.
summary[0] = opp_prev;
2329 double self_not_hit = 1.0;
2330 double opp_not_hit = 1.0;
2333 double self_already_dead =
hp_dist[0];
2334 double opp_already_dead = opponent.
hp_dist[0];
2337 round_prob_if_close_to_sure(
slowed);
2338 round_prob_if_close_to_sure(opponent.
slowed);
2342 const std::vector<combat_slice>
split = split_summary(
u_,
summary);
2343 const std::vector<combat_slice> opp_split = split_summary(opponent.
u_, opponent.
summary);
2345 bool use_monte_carlo_simulation =
2349 if(use_monte_carlo_simulation) {
2352 complex_fight(attack_prediction_mode::monte_carlo_simulation,
u_, opponent.
u_,
u_.
num_blows,
2355 }
else if(split.size() == 1 && opp_split.size() == 1) {
2358 opp_not_hit, levelup_considered);
2361 summary_t summary_result, opp_summary_result;
2367 for(
unsigned s = 0;
s != split.size(); ++
s) {
2368 for(
unsigned t = 0;
t != opp_split.size(); ++
t) {
2369 const double sit_prob = split[
s].prob * opp_split[
t].prob;
2372 summary_t sit_summary, sit_opp_summary;
2373 init_slice_summary(sit_summary[0],
summary[0], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2374 init_slice_summary(sit_summary[1],
summary[1], split[
s].begin_hp, split[
s].end_hp, split[
s].prob);
2375 init_slice_summary(sit_opp_summary[0], opponent.
summary[0], opp_split[
t].begin_hp, opp_split[t].end_hp,
2377 init_slice_summary(sit_opp_summary[1], opponent.
summary[1], opp_split[t].begin_hp, opp_split[t].end_hp,
2382 double sit_self_not_hit = sit_prob;
2383 double sit_opp_not_hit = sit_prob;
2385 do_fight(
u_, opponent.
u_, split[
s].strikes, opp_split[t].strikes, sit_summary, sit_opp_summary,
2386 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2389 self_not_hit += sit_self_not_hit;
2390 opp_not_hit += sit_opp_not_hit;
2391 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2392 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2393 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2394 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2399 summary[0].swap(summary_result[0]);
2400 summary[1].swap(summary_result[1]);
2401 opponent.
summary[0].swap(opp_summary_result[0]);
2402 opponent.
summary[1].swap(opp_summary_result[1]);
2407 assert(opponent.
summary[0].size() == opp_res.size());
2408 for(
unsigned int i = 0;
i <
summary[0].size(); ++
i) {
2409 if(std::fabs(
summary[0][
i] - res[
i]) > 0.000001) {
2410 std::cerr <<
"Mismatch for " << i <<
" hp: " <<
summary[0][
i] <<
" should have been " << res[
i] <<
"\n";
2414 for(
unsigned int i = 0;
i < opponent.
summary[0].size(); ++
i) {
2415 if(std::fabs(opponent.
summary[0][
i] - opp_res[
i]) > 0.000001) {
2416 std::cerr <<
"Mismatch for " << i <<
" hp: " << opponent.
summary[0][
i] <<
" should have been " << opp_res[
i] <<
"\n";
2428 for(
unsigned int i = 0;
i <
size; ++
i)
2432 if(opponent.
summary[1].empty()) {
2436 opponent.
hp_dist.resize(size);
2437 for(
unsigned int i = 0;
i <
size; ++
i)
2442 double touched = 1.0 - self_not_hit;
2443 double opp_touched = 1.0 - opp_not_hit;
2450 if(!use_monte_carlo_simulation) {
2461 opponent.
slowed = std::min(std::accumulate(opponent.
summary[1].begin(), opponent.
summary[1].end(), 0.0), 1.0);
2483 for(
unsigned int i = 1;
i <
hp_dist.size(); ++
i) {
2492 #if defined(BENCHMARK) || defined(CHECK) 2495 static const unsigned int NUM_UNITS = 50;
2497 #ifdef ATTACK_PREDICTION_DEBUG 2500 std::ostringstream ss;
2503 ss <<
"#" << fighter <<
": " << stats.
swarm_max <<
"-" << stats.
damage <<
"; " 2519 ss <<
"swarm(" << stats.
num_blows <<
"), ";
2523 ss <<
"firststrike, ";
2526 ss <<
"max hp = " << stats.
max_hp <<
"\n";
2528 std::cout << ss.rdbuf() << std::endl;
2536 #ifdef HUMAN_READABLE 2537 void combatant::print(
const char label[],
unsigned int battle,
unsigned int fighter)
const 2539 std::ostringstream ss;
2542 printf(
"#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter, label,
2562 ss <<
"firststrike, ";
2565 std::cout << ss.rdbuf() << std::endl;
2568 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2570 int num_outputs = 0;
2571 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2573 if(num_outputs++ % 6 == 0) {
2579 printf(
"%2u: %5.2f",
i,
hp_dist[
i] * 100);
2585 #elif defined(CHECK) 2586 void combatant::print(
const char label[],
unsigned int battle,
unsigned int )
const 2588 std::ostringstream ss;
2610 ss <<
"firststrike, ";
2613 std::cout << ss.rdbuf() << std::endl;
2616 printf(
"maxhp=%zu ",
hp_dist.size() - 1);
2618 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2624 #else // ... BENCHMARK 2630 void combatant::reset()
2632 for(
unsigned int i = 0;
i <
hp_dist.size(); ++
i) {
2639 summary[0] = std::vector<double>();
2640 summary[1] = std::vector<double>();
2643 static void run(
unsigned specific_battle)
2645 using std::chrono::duration_cast;
2646 using std::chrono::microseconds;
2651 unsigned int i, j, k, battle = 0;
2652 std::chrono::high_resolution_clock::time_point
start, end;
2654 for(i = 0; i < NUM_UNITS; ++
i) {
2655 unsigned alt = i + 74;
2658 unsigned max_hp = (i * 2) % 23 + (i * 3) % 14 + 25;
2659 unsigned hp = (alt * 5) % max_hp + 1;
2671 list_combatant(*stats[i], i + 1);
2674 start = std::chrono::high_resolution_clock::now();
2676 for(i = 0; i < NUM_UNITS; ++
i) {
2677 for(j = 0; j < NUM_UNITS; ++j) {
2682 for(k = 0; k < NUM_UNITS; ++k) {
2683 if(i == k || j == k) {
2688 if(specific_battle && battle != specific_battle) {
2696 u[
i]->print(
"Defender", battle, i + 1);
2697 u[j]->print(
"Attacker #1", battle, j + 1);
2698 u[k]->print(
"Attacker #2", battle, k + 1);
2707 end = std::chrono::high_resolution_clock::now();
2709 auto total = end -
start;
2712 printf(
"Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2713 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2714 printf(
"Time per calc = %li us\n", static_cast<long>(duration_cast<microseconds>(total).count())
2715 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2717 printf(
"Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2720 for(i = 0; i < NUM_UNITS; ++
i) {
2731 int add_to_argv = 4;
2732 int damage = atoi((*argv)[1]);
2733 int num_attacks = atoi((*argv)[2]);
2734 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2735 int hit_chance = atoi((*argv)[4]);
2738 bool drains =
false, slows =
false,
slowed =
false, berserk =
false, firststrike =
false, swarm =
false;
2739 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2743 char* max = strstr((*argv)[5],
"maxhp=");
2745 max_hp = atoi(max + strlen(
"maxhp="));
2746 if(max_hp < hitpoints) {
2747 std::cerr <<
"maxhp must be at least hitpoints." << std::endl;
2752 if(strstr((*argv)[5],
"drain")) {
2754 std::cerr <<
"WARNING: drain specified without maxhp; assuming uninjured." << std::endl;
2760 if(strstr((*argv)[5],
"slows")) {
2764 if(strstr((*argv)[5],
"slowed")) {
2768 if(strstr((*argv)[5],
"berserk")) {
2772 if(strstr((*argv)[5],
"firststrike")) {
2776 if(strstr((*argv)[5],
"swarm")) {
2778 std::cerr <<
"WARNING: swarm specified without maxhp; assuming uninjured." << std::endl;
2786 *argv += add_to_argv;
2790 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows,
slowed, berserk, firststrike, swarm);
2793 int main(
int argc,
char* argv[])
2800 run(argv[1] ? atoi(argv[1]) : 0);
2804 <<
"Usage: " << argv[0] <<
" [<battle>]\n\t" << argv[0] <<
" " 2805 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] " 2806 <<
"<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ..." 2811 def_stats = parse_unit(&argv);
2813 for(i = 0; argv[1] && i < 19; ++
i) {
2814 att_stats[
i] = parse_unit(&argv);
2820 for(i = 0; att[
i]; ++
i) {
2821 debug((
"Fighting next attacker\n"));
2825 def->print(
"Defender", 0, 0);
2826 for(i = 0; att[
i]; ++
i) {
2827 att[
i]->print(
"Attacker", 0, i + 1);
2830 for(i = 0; att[
i]; ++
i) {
2832 delete att_stats[
i];
2841 #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.