1 /*
2 Copyright (C) 2006 - 2018 by Rusty Russell <rusty@rustcorp.com.au>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13
14 Full algorithm by Yogin. Original typing and optimization by Rusty.
15
16 Monte Carlo simulation mode implemented by Jyrki Vesterinen.
17
18 This code has lots of debugging. It is there for a reason:
19 this code is kinda tricky. Do not remove it.
20 */
21
22 /**
23 * @file
24 * Simulate combat to calculate attacks.
25 * This can be compiled as a stand-alone program to either verify
26 * correctness or to benchmark performance.
27 *
28 * Compile with -O3 -DBENCHMARK for speed testing, and with -DCHECK for
29 * testing correctness (redirect the output to a file, then compile
30 * utils/wesnoth-attack-sim.c and run that with the arguments
31 * --check \<file name\>).
32 * For either option, use -DHUMAN_READABLE if you want to see the results
33 * from each combat displayed in a prettier format (but no longer suitable
34 * for wesnoth-attack-sim.c).
35 */
36
37 #include "attack_prediction.hpp"
38
39 #include "actions/attack.hpp"
40 #include "game_config.hpp"
41 #include "log.hpp"
42 #include "preferences/general.hpp"
43 #include "random.hpp"
44 #include "serialization/string_utils.hpp"
45 #include "utils/general.hpp"
46
47 #include <array>
48 #include <cfloat>
49 #include <cmath>
50 #include <iostream>
51 #include <memory>
52 #include <numeric>
53 #include <sstream>
54
55 #if defined(BENCHMARK) || defined(CHECK)
56 #include <chrono>
57 #include <cstdio>
58 #include <cstdlib>
59 #endif
60
61 #ifdef ATTACK_PREDICTION_DEBUG
62 #define debug(x) printf x
63 #else
64 #define debug(x)
65 #endif
66
67 #ifdef ATTACK_PREDICTION_DEBUG
68 namespace
69 {
70 /** Prints the attack statistics of a unit to cout. */
dump(const battle_context_unit_stats & stats)71 void dump(const battle_context_unit_stats& stats)
72 {
73 std::ostringstream ss;
74
75 ss << "==================================";
76 << std::boolalpha
77 << "\n" << "is_attacker: " << stats.is_attacker
78 << "\n" << "is_poisoned: " << stats.is_poisoned
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
84 << "\n" << "backstab_pos: " << stats.backstab_pos
85 << "\n" << "swarm: " << stats.swarm
86 << "\n" << "firststrike: " << stats.firststrike
87 << std::noboolalpha
88 << "\n" << "rounds: " << stats.rounds
89 << "\n\n"
90 << "\n" << "hp: " << stats.hp
91 << "\n" << "max_hp: " << stats.max_hp
92 << "\n" << "chance_to_hit: " << stats.chance_to_hit
93 << "\n" << "damage: " << stats.damage
94 << "\n" << "slow_damage: " << stats.slow_damage
95 << "\n" << "drain_percent: " << stats.drain_percent
96 << "\n" << "drain_constant: " << stats.drain_constant
97 << "\n" << "num_blows: " << stats.num_blows
98 << "\n" << "swarm_min: " << stats.swarm_min
99 << "\n" << "swarm_max: " << stats.swarm_max
100 << "\n\n";
101
102 std::cout << ss.rdbuf() << std::endl;
103 }
104 }
105 #endif
106
107 namespace
108 {
109 using summary_t = std::array<std::vector<double>, 2>;
110
111 /**
112 * A struct to describe one possible combat scenario.
113 * (Needed when the number of attacks can vary due to swarm.)
114 */
115 struct combat_slice
116 {
117 // The hit point range this slice covers.
118 unsigned begin_hp; // included in the range.
119 unsigned end_hp; // excluded from the range.
120
121 // The probability of this slice.
122 double prob;
123
124 // The number of strikes applicable with this slice.
125 unsigned strikes;
126
127 combat_slice(
128 const summary_t& src_summary, unsigned begin, unsigned end, unsigned num_strikes);
129 combat_slice(const summary_t& src_summary, unsigned num_strikes);
130 };
131
132 /**
133 * Creates a slice from a summary, and associates a number of strikes.
134 */
combat_slice(const summary_t & src_summary,unsigned begin,unsigned end,unsigned num_strikes)135 combat_slice::combat_slice(
136 const summary_t& src_summary, unsigned begin, unsigned end, unsigned num_strikes)
137 : begin_hp(begin)
138 , end_hp(end)
139 , prob(0.0)
140 , strikes(num_strikes)
141 {
142 if(src_summary[0].empty()) {
143 // No summary; this should be the only slice.
144 prob = 1.0;
145 return;
146 }
147
148 // Avoid accessing beyond the end of the vectors.
149 if(end > src_summary[0].size()) {
150 end = src_summary[0].size();
151 }
152
153 // Sum the probabilities in the slice.
154 for(unsigned i = begin; i < end; ++i) {
155 prob += src_summary[0][i];
156 }
157
158 if(!src_summary[1].empty()) {
159 for(unsigned i = begin; i < end; ++i) {
160 prob += src_summary[1][i];
161 }
162 }
163 }
164
165 /**
166 * Creates a slice from the summaries, and associates a number of strikes.
167 * This version of the constructor creates a slice consisting of everything.
168 */
combat_slice(const summary_t & src_summary,unsigned num_strikes)169 combat_slice::combat_slice(const summary_t& src_summary, unsigned num_strikes)
170 : begin_hp(0)
171 , end_hp(src_summary[0].size())
172 , prob(1.0)
173 , strikes(num_strikes)
174 {
175 }
176
177 /**
178 * Returns the number of hit points greater than cur_hp, and at most
179 * stats.max_hp+1, at which the unit would get another attack because
180 * of swarm.
181 * Helper function for split_summary().
182 */
hp_for_next_attack(unsigned cur_hp,const battle_context_unit_stats & stats)183 unsigned hp_for_next_attack(unsigned cur_hp, const battle_context_unit_stats& stats)
184 {
185 unsigned old_strikes = stats.calc_blows(cur_hp);
186
187 // A formula would have to deal with rounding issues; instead
188 // loop until we find more strikes.
189 while(++cur_hp <= stats.max_hp) {
190 if(stats.calc_blows(cur_hp) != old_strikes) {
191 break;
192 }
193 }
194
195 return cur_hp;
196 }
197
198 /**
199 * Split the combat by number of attacks per combatant (for swarm).
200 * This also clears the current summaries.
201 */
split_summary(const battle_context_unit_stats & unit_stats,summary_t & summary)202 std::vector<combat_slice> split_summary(
203 const battle_context_unit_stats& unit_stats, summary_t& summary)
204 {
205 std::vector<combat_slice> result;
206
207 if(unit_stats.swarm_min == unit_stats.swarm_max || summary[0].empty()) {
208 // We use the same number of blows for all possibilities.
209 result.emplace_back(summary, unit_stats.num_blows);
210 return result;
211 }
212
213 debug(("Slicing:\n"));
214 // Loop through our slices.
215 unsigned cur_end = 0;
216 do {
217 // Advance to the next slice.
218 const unsigned cur_begin = cur_end;
219 cur_end = hp_for_next_attack(cur_begin, unit_stats);
220
221 // Add this slice.
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));
227 }
228 } while(cur_end <= unit_stats.max_hp);
229
230 return result;
231 }
232
233 /**
234 * A matrix of A's hitpoints vs B's hitpoints vs. their slowed states.
235 * This class is concerned only with the matrix implementation and
236 * implements functionality for shifting and retrieving probabilities
237 * (i.e. low-level stuff).
238 */
239 class prob_matrix
240 {
241 // Since this gets used very often (especially by the AI), it has
242 // been optimized for speed as a sparse matrix.
243 public:
244 prob_matrix(unsigned int a_max,
245 unsigned int b_max,
246 bool need_a_slowed,
247 bool need_b_slowed,
248 unsigned int a_cur,
249 unsigned int b_cur,
250 const summary_t& a_initial,
251 const summary_t& b_initial);
252
253 // Shift columns on this plane (b taking damage).
254 void shift_cols(unsigned dst, unsigned src, unsigned damage, double prob, int drain_constant, int drain_percent);
255 // Shift rows on this plane (a taking damage).
256 void shift_rows(unsigned dst, unsigned src, unsigned damage, double prob, int drain_constant, int drain_percent);
257
258 /// Move a column (adding it to the destination).
259 void move_column(unsigned d_plane, unsigned s_plane, unsigned d_col, unsigned s_col);
260 /// Move a row (adding it to the destination).
261 void move_row(unsigned d_plane, unsigned s_plane, unsigned d_row, unsigned s_row);
262
263 // Move values within a row (or column) to a specified column (or 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);
268
269 // Set all values to zero and clear the lists of used columns/rows.
270 void clear();
271
272 // Record the result of a single Monte Carlo simulation iteration.
273 void record_monte_carlo_result(unsigned int a_hp, unsigned int b_hp, bool a_slowed, bool b_slowed);
274
275 // Returns the index of the plane with the given slow statuses.
plane_index(bool a_slowed,bool b_slowed)276 static unsigned int plane_index(bool a_slowed, bool b_slowed)
277 {
278 return (a_slowed ? 1 : 0) + (b_slowed ? 2 : 0);
279 }
280
281 /// What is the chance that an indicated combatant (one of them) is at zero?
282 double prob_of_zero(bool check_a, bool check_b) const;
283 /// Sums the values in the specified row.
284 double row_sum(unsigned plane, unsigned row) const;
285 /// Sums the values in the specified column.
286 double col_sum(unsigned plane, unsigned column) const;
287 /// Sums the values in the specified plane.
288 void sum(unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums) const;
289
290 /// Returns true if the specified plane might have data in it.
plane_used(unsigned p) const291 bool plane_used(unsigned p) const
292 {
293 return p < NUM_PLANES && plane_[p] != nullptr;
294 }
295
num_rows() const296 unsigned int num_rows() const
297 {
298 return rows_;
299 }
num_cols() const300 unsigned int num_cols() const
301 {
302 return cols_;
303 }
304
305 // Debugging tool.
306 void dump() const;
307
308 // We need four matrices, or "planes", reflecting the possible
309 // "slowed" states (neither slowed, A slowed, B slowed, both slowed).
310 enum {
311 NEITHER_SLOWED,
312 A_SLOWED,
313 B_SLOWED,
314 BOTH_SLOWED,
315 NUM_PLANES // Symbolic constant for the number of planes.
316 };
317
318 private:
319 // This gives me 10% speed improvement over std::vector<> (g++4.0.3 x86)
320 std::unique_ptr<double[]> new_plane() const;
321
322 void initialize_plane(unsigned plane,
323 unsigned a_cur,
324 unsigned b_cur,
325 const std::vector<double>& a_initial,
326 const std::vector<double>& b_initial);
327 void initialize_row(
328 unsigned plane, unsigned row, double row_prob, unsigned b_cur, const std::vector<double>& b_initial);
329
330 double& val(unsigned plane, unsigned row, unsigned col);
331 const double& val(unsigned plane, unsigned row, unsigned col) const;
332
333 /// Transfers a portion (value * prob) of one value in the matrix to another.
334 void xfer(unsigned dst_plane,
335 unsigned src_plane,
336 unsigned row_dst,
337 unsigned col_dst,
338 unsigned row_src,
339 unsigned col_src,
340 double prob);
341 /// Transfers one value in the matrix to another.
342 void xfer(unsigned dst_plane,
343 unsigned src_plane,
344 unsigned row_dst,
345 unsigned col_dst,
346 unsigned row_src,
347 unsigned col_src);
348
349 void shift_cols_in_row(unsigned dst,
350 unsigned src,
351 unsigned row,
352 const std::vector<unsigned>& cols,
353 unsigned damage,
354 double prob,
355 int drainmax,
356 int drain_constant,
357 int drain_percent);
358 void shift_rows_in_col(unsigned dst,
359 unsigned src,
360 unsigned col,
361 const std::vector<unsigned>& rows,
362 unsigned damage,
363 double prob,
364 int drainmax,
365 int drain_constant,
366 int drain_percent);
367
368 private: // data
369 const unsigned int rows_, cols_;
370 std::array<std::unique_ptr<double[]>, NUM_PLANES> plane_;
371
372 // For optimization, we keep track of the rows and columns with data.
373 // (The matrices are likely going to be rather sparse, with data on a grid.)
374 std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
375 };
376
377 /**
378 * Constructor.
379 * @param a_max The maximum value we will track for A.
380 * @param b_max The maximum value we will track for B.
381 * @param need_a_slowed Set to true if there might be transfers to a "slow" plane for A.
382 * @param need_b_slowed Set to true if there might be transfers to a "slow" plane for B.
383 * @param a_cur The current value for A. (Ignored if a_initial[0] is not empty.)
384 * @param b_cur The current value for B. (Ignored if b_initial[0] is not empty.)
385 * @param a_initial The initial distribution of values for A. Element [0] is for normal A. while [1] is for slowed
386 * A.
387 * @param b_initial The initial distribution of values for B. Element [0] is for normal B. while [1] is for slowed
388 * B.
389 */
prob_matrix(unsigned int a_max,unsigned int b_max,bool need_a_slowed,bool need_b_slowed,unsigned int a_cur,unsigned int b_cur,const summary_t & a_initial,const summary_t & b_initial)390 prob_matrix::prob_matrix(unsigned int a_max,
391 unsigned int b_max,
392 bool need_a_slowed,
393 bool need_b_slowed,
394 unsigned int a_cur,
395 unsigned int b_cur,
396 const summary_t& a_initial,
397 const summary_t& b_initial)
398 : rows_(a_max + 1)
399 , cols_(b_max + 1)
400 , plane_()
401 , used_rows_()
402 , used_cols_()
403 {
404 // Make sure we do not access the matrix in invalid positions.
405 a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
406 b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
407
408 // It will be convenient to always consider row/col 0 to be used.
409 for(unsigned plane = 0; plane != NUM_PLANES; ++plane) {
410 used_rows_[plane].insert(0u);
411 used_cols_[plane].insert(0u);
412 }
413
414 // We will need slowed planes if the initial vectors have them.
415 need_a_slowed = need_a_slowed || !a_initial[1].empty();
416 need_b_slowed = need_b_slowed || !b_initial[1].empty();
417
418 // Allocate the needed planes.
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();
423
424 // Initialize the probability distribution.
425 initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
426
427 if(!a_initial[1].empty()) {
428 initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
429 }
430
431 if(!b_initial[1].empty()) {
432 initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
433 }
434
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]);
437 }
438
439 // Some debugging messages.
440 if(!a_initial[0].empty()) {
441 debug(("A has fought before (or is slowed).\n"));
442 dump();
443 }
444
445 if(!b_initial[0].empty()) {
446 debug(("B has fought before (or is slowed).\n"));
447 dump();
448 }
449 }
450
451 /** Allocate a new probability array, initialized to 0. */
new_plane() const452 std::unique_ptr<double[]> prob_matrix::new_plane() const
453 {
454 const unsigned int size = rows_ * cols_;
455 std::unique_ptr<double[]> res(new double[size]);
456 std::fill_n(res.get(), size, 0);
457 return res;
458 }
459
460 /**
461 * Fills the indicated plane with its initial (non-zero) values.
462 * (Part of construction)
463 * @param plane The plane to initialize.
464 * @param a_cur The current value for A. (Ignored if a_initial is not empty.)
465 * @param b_cur The current value for B. (Ignored if b_initial is not empty.)
466 * @param a_initial The initial distribution of values for A for this plane.
467 * @param b_initial The initial distribution of values for B for this plane.
468 */
initialize_plane(unsigned plane,unsigned a_cur,unsigned b_cur,const std::vector<double> & a_initial,const std::vector<double> & b_initial)469 void prob_matrix::initialize_plane(unsigned plane,
470 unsigned a_cur,
471 unsigned b_cur,
472 const std::vector<double>& a_initial,
473 const std::vector<double>& b_initial)
474 {
475 if(!a_initial.empty()) {
476 unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
477 // The probabilities for each row are contained in a_initial.
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);
482 }
483 }
484 } else {
485 used_rows_[plane].insert(a_cur);
486 // Only the row indicated by a_cur is a possibility.
487 initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
488 }
489 }
490
491 /**
492 * Fills the indicated row with its initial (non-zero) values.
493 * (Part of construction)
494 * @param plane The plane containing the row to initialize.
495 * @param row The row to initialize.
496 * @param row_prob The probability of A having the value for this row.
497 * @param b_cur The current value for B. (Ignored if b_initial is not empty.)
498 * @param b_initial The initial distribution of values for B for this plane.
499 */
initialize_row(unsigned plane,unsigned row,double row_prob,unsigned b_cur,const std::vector<double> & 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)
502 {
503 if(!b_initial.empty()) {
504 unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
505 // The probabilities for each column are contained in b_initial.
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];
510 }
511 }
512 } else {
513 // Only the column indicated by b_cur is a possibility.
514 used_cols_[plane].insert(b_cur);
515 val(plane, row, b_cur) = row_prob;
516 }
517 }
518
val(unsigned plane,unsigned row,unsigned col)519 double& prob_matrix::val(unsigned plane, unsigned row, unsigned col)
520 {
521 assert(row < rows_);
522 assert(col < cols_);
523 return plane_[plane][row * cols_ + col];
524 }
525
val(unsigned plane,unsigned row,unsigned col) const526 const double& prob_matrix::val(unsigned plane, unsigned row, unsigned col) const
527 {
528 assert(row < rows_);
529 assert(col < cols_);
530 return plane_[plane][row * cols_ + col];
531 }
532
533 // xfer, shift_cols and shift_rows use up most of our time. Careful!
534 /**
535 * Transfers a portion (value * prob) of one value in the matrix to another.
536 */
xfer(unsigned dst_plane,unsigned src_plane,unsigned row_dst,unsigned col_dst,unsigned row_src,unsigned col_src,double prob)537 void prob_matrix::xfer(unsigned dst_plane,
538 unsigned src_plane,
539 unsigned row_dst,
540 unsigned col_dst,
541 unsigned row_src,
542 unsigned col_src,
543 double prob)
544 {
545 double& src = val(src_plane, row_src, col_src);
546 if(src != 0.0) {
547 double diff = src * prob;
548 src -= diff;
549
550 double& dst = val(dst_plane, row_dst, col_dst);
551 if(dst == 0.0) {
552 // Track that this entry is now used.
553 used_rows_[dst_plane].insert(row_dst);
554 used_cols_[dst_plane].insert(col_dst);
555 }
556 dst += diff;
557
558 debug(("Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n", diff,
559 src_plane == NEITHER_SLOWED
560 ? ""
561 : src_plane == A_SLOWED
562 ? "[A_SLOWED]"
563 : src_plane == B_SLOWED
564 ? "[B_SLOWED]"
565 : src_plane == BOTH_SLOWED
566 ? "[BOTH_SLOWED]"
567 : "INVALID",
568
569 row_src, col_src,
570 dst_plane == NEITHER_SLOWED
571 ? ""
572 : dst_plane == A_SLOWED
573 ? "[A_SLOWED]"
574 : dst_plane == B_SLOWED
575 ? "[B_SLOWED]"
576 : dst_plane == BOTH_SLOWED
577 ? "[BOTH_SLOWED]"
578 : "INVALID",
579 row_dst, col_dst)
580 );
581 }
582 }
583
584 /**
585 * Transfers one value in the matrix to another.
586 */
xfer(unsigned dst_plane,unsigned src_plane,unsigned row_dst,unsigned col_dst,unsigned row_src,unsigned col_src)587 void prob_matrix::xfer(
588 unsigned dst_plane, unsigned src_plane, unsigned row_dst, unsigned col_dst, unsigned row_src, unsigned col_src)
589 {
590 if(dst_plane == src_plane && row_dst == row_src && col_dst == col_src)
591 // Transferring to itself. Nothing to do.
592 return;
593
594 double& src = val(src_plane, row_src, col_src);
595 if(src != 0.0) {
596 debug(("Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n", src,
597 src_plane == NEITHER_SLOWED
598 ? ""
599 : src_plane == A_SLOWED
600 ? "[A_SLOWED]"
601 : src_plane == B_SLOWED
602 ? "[B_SLOWED]"
603 : src_plane == BOTH_SLOWED
604 ? "[BOTH_SLOWED]"
605 : "INVALID",
606 row_src, col_src,
607 dst_plane == NEITHER_SLOWED
608 ? ""
609 : dst_plane == A_SLOWED
610 ? "[A_SLOWED]"
611 : dst_plane == B_SLOWED
612 ? "[B_SLOWED]"
613 : dst_plane == BOTH_SLOWED
614 ? "[BOTH_SLOWED]"
615 : "INVALID",
616 row_dst, col_dst)
617 );
618
619 double& dst = val(dst_plane, row_dst, col_dst);
620 if(dst == 0.0) {
621 // Track that this entry is now used.
622 used_rows_[dst_plane].insert(row_dst);
623 used_cols_[dst_plane].insert(col_dst);
624 }
625
626 dst += src;
627 src = 0.0;
628 }
629 }
630
631 /**
632 * Transfers a portion (value * prob) of the values in a row to another.
633 * Part of shift_cols().
634 */
shift_cols_in_row(unsigned dst,unsigned src,unsigned row,const std::vector<unsigned> & cols,unsigned damage,double prob,int drainmax,int drain_constant,int drain_percent)635 void prob_matrix::shift_cols_in_row(unsigned dst,
636 unsigned src,
637 unsigned row,
638 const std::vector<unsigned>& cols,
639 unsigned damage,
640 double prob,
641 int drainmax,
642 int drain_constant,
643 int drain_percent)
644 {
645 // Some conversions to (signed) int.
646 int row_i = static_cast<int>(row);
647 int max_row = static_cast<int>(rows_) - 1;
648
649 // cols[0] is excluded since that should be 0, representing already dead.
650 unsigned col_x = 1;
651
652 // Killing blows can have different drain amounts, so handle them first
653 for(; col_x < cols.size() && cols[col_x] < damage; ++col_x) {
654 // These variables are not strictly necessary, but they make the
655 // calculation easier to parse.
656 int col_i = static_cast<int>(cols[col_x]);
657 int drain_amount = col_i * drain_percent / 100 + drain_constant;
658 unsigned newrow = utils::clamp(row_i + drain_amount, 1, max_row);
659 xfer(dst, src, newrow, 0, row, cols[col_x], prob);
660 }
661
662 // The remaining columns use the specified drainmax.
663 unsigned newrow = utils::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);
666 }
667 }
668
669 /**
670 * Transfers a portion (value * prob) of each column in a plane to another.
671 * Each column in the @a src plane gets shifted @a damage columns towards 0, and
672 * also shifted into the @a dst plane. In addition, the rows can shift if
673 * @a drain constant or @a drain_percent is nonzero.
674 */
shift_cols(unsigned dst,unsigned src,unsigned damage,double prob,int drain_constant,int drain_percent)675 void prob_matrix::shift_cols(
676 unsigned dst, unsigned src, unsigned damage, double prob, int drain_constant, int drain_percent)
677 {
678 int drainmax = (drain_percent * (static_cast<signed>(damage)) / 100 + drain_constant);
679
680 if(drain_constant || drain_percent) {
681 debug(("Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
682 }
683
684 // Get lists of indices currently used in the source plane.
685 // (This needs to be cached since we might add indices while shifting.)
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());
688
689 // Loop downwards if we drain positive, but upwards if we drain negative,
690 // so we write behind us (for when src == dst).
691 if(drainmax > 0) {
692 // rows[0] is excluded since that should be 0, representing already dead.
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);
695 }
696 } else {
697 // rows[0] is excluded since that should be 0, representing already dead.
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);
700 }
701 }
702 }
703
704 /**
705 * Transfers a portion (value * prob) of the values in a column to another.
706 * Part of shift_rows().
707 */
shift_rows_in_col(unsigned dst,unsigned src,unsigned col,const std::vector<unsigned> & rows,unsigned damage,double prob,int drainmax,int drain_constant,int drain_percent)708 void prob_matrix::shift_rows_in_col(unsigned dst,
709 unsigned src,
710 unsigned col,
711 const std::vector<unsigned>& rows,
712 unsigned damage,
713 double prob,
714 int drainmax,
715 int drain_constant,
716 int drain_percent)
717 {
718 // Some conversions to (signed) int.
719 int col_i = static_cast<int>(col);
720 int max_col = static_cast<int>(cols_) - 1;
721
722 // rows[0] is excluded since that should be 0, representing already dead.
723 unsigned row_x = 1;
724
725 // Killing blows can have different drain amounts, so handle them first
726 for(; row_x < rows.size() && rows[row_x] < damage; ++row_x) {
727 // These variables are not strictly necessary, but they make the
728 // calculation easier to parse.
729 int row_i = static_cast<int>(rows[row_x]);
730 int drain_amount = row_i * drain_percent / 100 + drain_constant;
731 unsigned newcol = utils::clamp(col_i + drain_amount, 1, max_col);
732 xfer(dst, src, 0, newcol, rows[row_x], col, prob);
733 }
734
735 // The remaining rows use the specified drainmax.
736 unsigned newcol = utils::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);
739 }
740 }
741
742 /**
743 * Transfers a portion (value * prob) of each row in a plane to another.
744 * Each row in the @a src plane gets shifted @a damage columns towards 0, and
745 * also shifted into the @a dst plane. In addition, the columns can shift if
746 * @a drain constant or @a drain_percent is nonzero.
747 */
shift_rows(unsigned dst,unsigned src,unsigned damage,double prob,int drain_constant,int drain_percent)748 void prob_matrix::shift_rows(
749 unsigned dst, unsigned src, unsigned damage, double prob, int drain_constant, int drain_percent)
750 {
751 int drainmax = (drain_percent * (static_cast<signed>(damage)) / 100 + drain_constant);
752
753 if(drain_constant || drain_percent) {
754 debug(("Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
755 }
756
757 // Get lists of indices currently used in the source plane.
758 // (This needs to be cached since we might add indices while shifting.)
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());
761
762 // Loop downwards if we drain positive, but upwards if we drain negative,
763 // so we write behind us (for when src == dst).
764 if(drainmax > 0) {
765 // cols[0] is excluded since that should be 0, representing already dead.
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);
768 } else {
769 // cols[0] is excluded since that should be 0, representing already dead.
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);
772 }
773 }
774 }
775
776 /**
777 * Move a column (adding it to the destination).
778 */
move_column(unsigned d_plane,unsigned s_plane,unsigned d_col,unsigned s_col)779 void prob_matrix::move_column(unsigned d_plane, unsigned s_plane, unsigned d_col, unsigned s_col)
780 {
781 // Transfer the data.
782 for(const unsigned& row : used_rows_[s_plane]) {
783 xfer(d_plane, s_plane, row, d_col, row, s_col);
784 }
785 }
786
787 /**
788 * Move a row (adding it to the destination).
789 */
move_row(unsigned d_plane,unsigned s_plane,unsigned d_row,unsigned s_row)790 void prob_matrix::move_row(unsigned d_plane, unsigned s_plane, unsigned d_row, unsigned s_row)
791 {
792 // Transfer the data.
793 for(const unsigned& col : used_cols_[s_plane]) {
794 xfer(d_plane, s_plane, d_row, col, s_row, col);
795 }
796 }
797
798 /**
799 * Move values in the specified column -- excluding row zero -- to the
800 * specified row in that column (possibly shifting planes in the process).
801 */
merge_col(unsigned d_plane,unsigned s_plane,unsigned col,unsigned d_row)802 void prob_matrix::merge_col(unsigned d_plane, unsigned s_plane, unsigned col, unsigned d_row)
803 {
804 auto rows_end = used_rows_[s_plane].end();
805 auto row_it = used_rows_[s_plane].begin();
806
807 // Transfer the data, excluding row zero.
808 for(++row_it; row_it != rows_end; ++row_it) {
809 xfer(d_plane, s_plane, d_row, col, *row_it, col);
810 }
811 }
812
813 /**
814 * Move values within columns in the specified plane -- excluding row zero --
815 * to the specified row (possibly shifting planes in the process).
816 */
merge_cols(unsigned d_plane,unsigned s_plane,unsigned d_row)817 void prob_matrix::merge_cols(unsigned d_plane, unsigned s_plane, unsigned d_row)
818 {
819 auto rows_end = used_rows_[s_plane].end();
820 auto row_it = used_rows_[s_plane].begin();
821
822 // Transfer the data, excluding row zero.
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);
826 }
827 }
828 }
829
830 /**
831 * Move values in the specified row -- excluding column zero -- to the
832 * specified column in that row (possibly shifting planes in the process).
833 */
merge_row(unsigned d_plane,unsigned s_plane,unsigned row,unsigned d_col)834 void prob_matrix::merge_row(unsigned d_plane, unsigned s_plane, unsigned row, unsigned d_col)
835 {
836 auto cols_end = used_cols_[s_plane].end();
837 auto col_it = used_cols_[s_plane].begin();
838
839 // Transfer the data, excluding column zero.
840 for(++col_it; col_it != cols_end; ++col_it) {
841 xfer(d_plane, s_plane, row, d_col, row, *col_it);
842 }
843 }
844
845 /**
846 * Move values within rows in the specified plane -- excluding column zero --
847 * to the specified column (possibly shifting planes in the process).
848 */
merge_rows(unsigned d_plane,unsigned s_plane,unsigned d_col)849 void prob_matrix::merge_rows(unsigned d_plane, unsigned s_plane, unsigned d_col)
850 {
851 auto cols_end = used_cols_[s_plane].end();
852 // Exclude column zero.
853 auto cols_begin = std::next(used_cols_[s_plane].begin());
854
855 // Transfer the data, excluding column zero.
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);
859 }
860 }
861 }
862
863 /**
864 * Set all values to zero and clear the lists of used columns/rows.
865 */
clear()866 void prob_matrix::clear()
867 {
868 for(unsigned int p = 0u; p < NUM_PLANES; ++p) {
869 if(!plane_used(p)) {
870 continue;
871 }
872
873 if(used_rows_[p].empty()) {
874 // Nothing to do
875 continue;
876 }
877
878 decltype(used_rows_[p].begin()) first_row, last_row;
879 std::tie(first_row, last_row) = std::minmax_element(used_rows_[p].begin(), used_rows_[p].end());
880 for(unsigned int r = *first_row; r <= *last_row; ++r) {
881 for(unsigned int c = 0u; c < cols_; ++c) {
882 plane_[p][r * cols_ + c] = 0.0;
883 }
884 }
885
886 used_rows_[p].clear();
887 used_cols_[p].clear();
888
889 /* Row and column 0 are always considered to be used.
890 Functions like merge_col() access *used_rows_[plane].begin() without checking if there are
891 any used rows: thus, ensuring that there are used rows and columns is necessary to avoid
892 memory corruption. */
893 used_rows_[p].insert(0u);
894 used_cols_[p].insert(0u);
895 }
896 }
897
898 /**
899 * Record the result of a single Monte Carlo simulation iteration.
900 */
record_monte_carlo_result(unsigned int a_hp,unsigned int b_hp,bool a_slowed,bool b_slowed)901 void prob_matrix::record_monte_carlo_result(unsigned int a_hp, unsigned int b_hp, bool a_slowed, bool b_slowed)
902 {
903 assert(a_hp <= rows_);
904 assert(b_hp <= cols_);
905 unsigned int plane = plane_index(a_slowed, b_slowed);
906 ++val(plane, a_hp, b_hp);
907 used_rows_[plane].insert(a_hp);
908 used_cols_[plane].insert(b_hp);
909 }
910
911 /**
912 * What is the chance that an indicated combatant (one of them) is at zero?
913 */
prob_of_zero(bool check_a,bool check_b) const914 double prob_matrix::prob_of_zero(bool check_a, bool check_b) const
915 {
916 double prob = 0.0;
917
918 for(unsigned p = 0; p < NUM_PLANES; ++p) {
919 if(!plane_used(p)) {
920 continue;
921 }
922
923 // Column 0 is where b is at zero.
924 if(check_b) {
925 for(const unsigned& row : used_rows_[p]) {
926 prob += val(p, row, 0);
927 }
928 }
929
930 // Row 0 is where a is at zero.
931 if(check_a) {
932 for(const unsigned& col : used_cols_[p]) {
933 prob += val(p, 0, col);
934 }
935 }
936 // Theoretically, if checking both, we should subtract the chance that
937 // both are dead, but that chance is zero, so don't worry about it.
938 }
939
940 return prob;
941 }
942
943 /**
944 * Sums the values in the specified row.
945 */
row_sum(unsigned plane,unsigned row) const946 double prob_matrix::row_sum(unsigned plane, unsigned row) const
947 {
948 if(!plane_used(plane)) {
949 return 0.0;
950 }
951
952 double sum = 0.0;
953 for(unsigned col : used_cols_[plane]) {
954 sum += val(plane, row, col);
955 }
956 return sum;
957 }
958
959 /**
960 * Sums the values in the specified column.
961 */
col_sum(unsigned plane,unsigned column) const962 double prob_matrix::col_sum(unsigned plane, unsigned column) const
963 {
964 if(!plane_used(plane)) {
965 return 0.0;
966 }
967
968 double sum = 0.0;
969 for(unsigned row : used_rows_[plane]) {
970 sum += val(plane, row, column);
971 }
972 return sum;
973 }
974
975 /**
976 * Sums the values in the specified plane.
977 * The sum of each row is added to the corresponding entry in row_sums.
978 * The sum of each column is added to the corresponding entry in col_sums.
979 */
sum(unsigned plane,std::vector<double> & row_sums,std::vector<double> & col_sums) const980 void prob_matrix::sum(unsigned plane, std::vector<double>& row_sums, std::vector<double>& col_sums) const
981 {
982 for(const unsigned& row : used_rows_[plane]) {
983 for(const unsigned& col : used_cols_[plane]) {
984 const double& prob = val(plane, row, col);
985 row_sums[row] += prob;
986 col_sums[col] += prob;
987 }
988 }
989 }
990
991 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG)
dump() const992 void prob_matrix::dump() const
993 {
994 unsigned int row, col, m;
995 const char* names[] {"NEITHER_SLOWED", "A_SLOWED", "B_SLOWED", "BOTH_SLOWED"};
996
997 for(m = 0; m < NUM_PLANES; ++m) {
998 if(!plane_used(m)) {
999 continue;
1000 }
1001
1002 debug(("%s:\n", names[m]));
1003 for(row = 0; row < rows_; ++row) {
1004 debug((" "));
1005 for(col = 0; col < cols_; ++col) {
1006 debug(("%4.3g ", val(m, row, col) * 100));
1007 }
1008
1009 debug(("\n"));
1010 }
1011 }
1012 }
1013 #else
dump() const1014 void prob_matrix::dump() const
1015 {
1016 }
1017 #endif
1018
1019 /**
1020 * A matrix for calculating the outcome of combat.
1021 * This class specifies the interface and functionality shared between
1022 * probability_combat_matrix and monte_carlo_combat_matrix.
1023 */
1024 class combat_matrix : protected prob_matrix
1025 {
1026 public:
1027 combat_matrix(unsigned int a_max_hp,
1028 unsigned int b_max_hp,
1029 unsigned int a_hp,
1030 unsigned int b_hp,
1031 const summary_t& a_summary,
1032 const summary_t& b_summary,
1033 bool a_slows,
1034 bool b_slows,
1035 unsigned int a_damage,
1036 unsigned int b_damage,
1037 unsigned int a_slow_damage,
1038 unsigned int b_slow_damage,
1039 int a_drain_percent,
1040 int b_drain_percent,
1041 int a_drain_constant,
1042 int b_drain_constant);
1043
~combat_matrix()1044 virtual ~combat_matrix()
1045 {
1046 }
1047
1048 // We lied: actually did less damage, adjust matrix.
1049 void remove_petrify_distortion_a(unsigned damage, unsigned slow_damage, unsigned b_hp);
1050 void remove_petrify_distortion_b(unsigned damage, unsigned slow_damage, unsigned a_hp);
1051
1052 void forced_levelup_a();
1053 void conditional_levelup_a();
1054
1055 void forced_levelup_b();
1056 void conditional_levelup_b();
1057
1058 using prob_matrix::row_sum;
1059 using prob_matrix::col_sum;
1060
1061 // Its over, and here's the bill.
1062 virtual void extract_results(
1063 summary_t& summary_a, summary_t& summary_b)
1064 = 0;
1065
dump() const1066 void dump() const
1067 {
1068 prob_matrix::dump();
1069 }
1070
1071 protected:
1072 unsigned a_max_hp_;
1073 bool a_slows_;
1074 unsigned a_damage_;
1075 unsigned a_slow_damage_;
1076 int a_drain_percent_;
1077 int a_drain_constant_;
1078
1079 unsigned b_max_hp_;
1080 bool b_slows_;
1081 unsigned b_damage_;
1082 unsigned b_slow_damage_;
1083 int b_drain_percent_;
1084 int b_drain_constant_;
1085 };
1086
1087 /**
1088 * Constructor.
1089 * @param a_max_hp The maximum hit points for A.
1090 * @param b_max_hp The maximum hit points for B.
1091 * @param a_slows Set to true if A slows B when A hits B.
1092 * @param b_slows Set to true if B slows A when B hits A.
1093 * @param a_hp The current hit points for A. (Ignored if a_summary[0] is not empty.)
1094 * @param b_hp The current hit points for B. (Ignored if b_summary[0] is not empty.)
1095 * @param a_summary The hit point distribution for A (from previous combats). Element [0] is for normal A. while
1096 * [1] is for slowed A.
1097 * @param b_summary The hit point distribution for B (from previous combats). Element [0] is for normal B. while
1098 * [1] is for slowed B.
1099 */
1100
combat_matrix(unsigned int a_max_hp,unsigned int b_max_hp,unsigned int a_hp,unsigned int b_hp,const summary_t & a_summary,const summary_t & b_summary,bool a_slows,bool b_slows,unsigned int a_damage,unsigned int b_damage,unsigned int a_slow_damage,unsigned int b_slow_damage,int a_drain_percent,int b_drain_percent,int a_drain_constant,int b_drain_constant)1101 combat_matrix::combat_matrix(unsigned int a_max_hp,
1102 unsigned int b_max_hp,
1103 unsigned int a_hp,
1104 unsigned int b_hp,
1105 const summary_t& a_summary,
1106 const summary_t& b_summary,
1107 bool a_slows,
1108 bool b_slows,
1109 unsigned int a_damage,
1110 unsigned int b_damage,
1111 unsigned int a_slow_damage,
1112 unsigned int b_slow_damage,
1113 int a_drain_percent,
1114 int b_drain_percent,
1115 int a_drain_constant,
1116 int b_drain_constant)
1117 // The inversion of the order of the *_slows parameters here is intentional.
1118 : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary)
1119 , a_max_hp_(a_max_hp)
1120 , a_slows_(a_slows)
1121 , a_damage_(a_damage)
1122 , a_slow_damage_(a_slow_damage)
1123 , a_drain_percent_(a_drain_percent)
1124 , a_drain_constant_(a_drain_constant)
1125 , b_max_hp_(b_max_hp)
1126 , b_slows_(b_slows)
1127 , b_damage_(b_damage)
1128 , b_slow_damage_(b_slow_damage)
1129 , b_drain_percent_(b_drain_percent)
1130 , b_drain_constant_(b_drain_constant)
1131 {
1132 }
1133
1134 // We lied: actually did less damage, adjust matrix.
remove_petrify_distortion_a(unsigned damage,unsigned slow_damage,unsigned b_hp)1135 void combat_matrix::remove_petrify_distortion_a(unsigned damage, unsigned slow_damage, unsigned b_hp)
1136 {
1137 for(int p = 0; p < NUM_PLANES; ++p) {
1138 if(!plane_used(p)) {
1139 continue;
1140 }
1141
1142 // A is slow in planes 1 and 3.
1143 unsigned actual_damage = (p & 1) ? slow_damage : damage;
1144 if(b_hp > actual_damage) {
1145 // B was actually petrified, not killed.
1146 move_column(p, p, b_hp - actual_damage, 0);
1147 }
1148 }
1149 }
1150
remove_petrify_distortion_b(unsigned damage,unsigned slow_damage,unsigned a_hp)1151 void combat_matrix::remove_petrify_distortion_b(unsigned damage, unsigned slow_damage, unsigned a_hp)
1152 {
1153 for(int p = 0; p < NUM_PLANES; ++p) {
1154 if(!plane_used(p)) {
1155 continue;
1156 }
1157
1158 // B is slow in planes 2 and 3.
1159 unsigned actual_damage = (p & 2) ? slow_damage : damage;
1160 if(a_hp > actual_damage) {
1161 // A was actually petrified, not killed.
1162 move_row(p, p, a_hp - actual_damage, 0);
1163 }
1164 }
1165 }
1166
forced_levelup_a()1167 void combat_matrix::forced_levelup_a()
1168 {
1169 /* Move all the values (except 0hp) of all the planes to the "fully healed"
1170 row of the planes unslowed for A. */
1171 for(int p = 0; p < NUM_PLANES; ++p) {
1172 if(plane_used(p)) {
1173 merge_cols(p & -2, p, a_max_hp_);
1174 }
1175 }
1176 }
1177
forced_levelup_b()1178 void combat_matrix::forced_levelup_b()
1179 {
1180 /* Move all the values (except 0hp) of all the planes to the "fully healed"
1181 column of planes unslowed for B. */
1182 for(int p = 0; p < NUM_PLANES; ++p) {
1183 if(plane_used(p)) {
1184 merge_rows(p & -3, p, b_max_hp_);
1185 }
1186 }
1187 }
1188
conditional_levelup_a()1189 void combat_matrix::conditional_levelup_a()
1190 {
1191 /* Move the values of the first column (except 0hp) of all the
1192 planes to the "fully healed" row of the planes unslowed for A. */
1193 for(int p = 0; p < NUM_PLANES; ++p) {
1194 if(plane_used(p)) {
1195 merge_col(p & -2, p, 0, a_max_hp_);
1196 }
1197 }
1198 }
1199
conditional_levelup_b()1200 void combat_matrix::conditional_levelup_b()
1201 {
1202 /* Move the values of the first row (except 0hp) of all the
1203 planes to the last column of the planes unslowed for B. */
1204 for(int p = 0; p < NUM_PLANES; ++p) {
1205 if(plane_used(p)) {
1206 merge_row(p & -3, p, 0, b_max_hp_);
1207 }
1208 }
1209 }
1210
1211 /**
1212 * Implementation of combat_matrix that calculates exact probabilities of events.
1213 * Fast in "simple" fights (low number of strikes, low HP, and preferably no slow
1214 * or swarm effect), but can be unusably expensive in extremely complex situations.
1215 */
1216 class probability_combat_matrix : public combat_matrix
1217 {
1218 public:
1219 probability_combat_matrix(unsigned int a_max_hp,
1220 unsigned int b_max_hp,
1221 unsigned int a_hp,
1222 unsigned int b_hp,
1223 const summary_t& a_summary,
1224 const summary_t& b_summary,
1225 bool a_slows,
1226 bool b_slows,
1227 unsigned int a_damage,
1228 unsigned int b_damage,
1229 unsigned int a_slow_damage,
1230 unsigned int b_slow_damage,
1231 int a_drain_percent,
1232 int b_drain_percent,
1233 int a_drain_constant,
1234 int b_drain_constant);
1235
1236 // A hits B.
1237 void receive_blow_b(double hit_chance);
1238 // B hits A. Why can't they just get along?
1239 void receive_blow_a(double hit_chance);
1240
1241 /// What is the chance that one of the combatants is dead?
dead_prob() const1242 double dead_prob() const
1243 {
1244 return prob_of_zero(true, true);
1245 }
1246
1247 /// What is the chance that combatant 'a' is dead?
dead_prob_a() const1248 double dead_prob_a() const
1249 {
1250 return prob_of_zero(true, false);
1251 }
1252
1253 /// What is the chance that combatant 'b' is dead?
dead_prob_b() const1254 double dead_prob_b() const
1255 {
1256 return prob_of_zero(false, true);
1257 }
1258
1259 void extract_results(
1260 summary_t& summary_a, summary_t& summary_b) override;
1261 };
1262
1263 /**
1264 * Constructor.
1265 * @param a_max_hp The maximum hit points for A.
1266 * @param b_max_hp The maximum hit points for B.
1267 * @param a_slows Set to true if A slows B when A hits B.
1268 * @param b_slows Set to true if B slows A when B hits A.
1269 * @param a_hp The current hit points for A. (Ignored if a_summary[0] is not empty.)
1270 * @param b_hp The current hit points for B. (Ignored if b_summary[0] is not empty.)
1271 * @param a_summary The hit point distribution for A (from previous combats). Element [0] is for normal A. while
1272 * [1] is for slowed A.
1273 * @param b_summary The hit point distribution for B (from previous combats). Element [0] is for normal B. while
1274 * [1] is for slowed B.
1275 */
probability_combat_matrix(unsigned int a_max_hp,unsigned int b_max_hp,unsigned int a_hp,unsigned int b_hp,const summary_t & a_summary,const summary_t & b_summary,bool a_slows,bool b_slows,unsigned int a_damage,unsigned int b_damage,unsigned int a_slow_damage,unsigned int b_slow_damage,int a_drain_percent,int b_drain_percent,int a_drain_constant,int b_drain_constant)1276 probability_combat_matrix::probability_combat_matrix(unsigned int a_max_hp,
1277 unsigned int b_max_hp,
1278 unsigned int a_hp,
1279 unsigned int b_hp,
1280 const summary_t& a_summary,
1281 const summary_t& b_summary,
1282 bool a_slows,
1283 bool b_slows,
1284 unsigned int a_damage,
1285 unsigned int b_damage,
1286 unsigned int a_slow_damage,
1287 unsigned int b_slow_damage,
1288 int a_drain_percent,
1289 int b_drain_percent,
1290 int a_drain_constant,
1291 int b_drain_constant)
1292 : combat_matrix(a_max_hp,
1293 b_max_hp,
1294 a_hp,
1295 b_hp,
1296 a_summary,
1297 b_summary,
1298 a_slows,
1299 b_slows,
1300 a_damage,
1301 b_damage,
1302 a_slow_damage,
1303 b_slow_damage,
1304 a_drain_percent,
1305 b_drain_percent,
1306 a_drain_constant,
1307 b_drain_constant)
1308 {
1309 }
1310
1311 // Shift combat_matrix to reflect the probability 'hit_chance' that damage
1312 // is done to 'b'.
receive_blow_b(double hit_chance)1313 void probability_combat_matrix::receive_blow_b(double hit_chance)
1314 {
1315 // Walk backwards so we don't copy already-copied matrix planes.
1316 unsigned src = NUM_PLANES;
1317 while(src-- != 0) {
1318 if(!plane_used(src)) {
1319 continue;
1320 }
1321
1322 // If A slows us, we go from 0=>2, 1=>3, 2=>2 3=>3.
1323 int dst = a_slows_ ? src | 2 : src;
1324
1325 // A is slow in planes 1 and 3.
1326 unsigned damage = (src & 1) ? a_slow_damage_ : a_damage_;
1327
1328 shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
1329 }
1330 }
1331
1332 // Shift matrix to reflect probability 'hit_chance'
1333 // that damage (up to) 'damage' is done to 'a'.
receive_blow_a(double hit_chance)1334 void probability_combat_matrix::receive_blow_a(double hit_chance)
1335 {
1336 // Walk backwards so we don't copy already-copied matrix planes.
1337 unsigned src = NUM_PLANES;
1338 while(src-- != 0) {
1339 if(!plane_used(src)) {
1340 continue;
1341 }
1342
1343 // If B slows us, we go from 0=>1, 1=>1, 2=>3 3=>3.
1344 int dst = b_slows_ ? src | 1 : src;
1345
1346 // B is slow in planes 2 and 3.
1347 unsigned damage = (src & 2) ? b_slow_damage_ : b_damage_;
1348
1349 shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
1350 }
1351 }
1352
extract_results(summary_t & summary_a,summary_t & summary_b)1353 void probability_combat_matrix::extract_results(
1354 summary_t& summary_a, summary_t& summary_b)
1355 {
1356 // Reset the summaries.
1357 summary_a[0] = std::vector<double>(num_rows());
1358 summary_b[0] = std::vector<double>(num_cols());
1359
1360 if(plane_used(A_SLOWED)) {
1361 summary_a[1] = std::vector<double>(num_rows());
1362 }
1363
1364 if(plane_used(B_SLOWED)) {
1365 summary_b[1] = std::vector<double>(num_cols());
1366 }
1367
1368 for(unsigned p = 0; p < NUM_PLANES; ++p) {
1369 if(!plane_used(p)) {
1370 continue;
1371 }
1372
1373 // A is slow in planes 1 and 3.
1374 unsigned dst_a = (p & 1) ? 1u : 0u;
1375 // B is slow in planes 2 and 3.
1376 unsigned dst_b = (p & 2) ? 1u : 0u;
1377 sum(p, summary_a[dst_a], summary_b[dst_b]);
1378 }
1379 }
1380
1381 /**
1382 * Implementation of combat_matrix based on Monte Carlo simulation.
1383 * This does not give exact results, but the error should be small
1384 * thanks to the law of large numbers. Probably more important is that
1385 * the simulation time doesn't depend on anything other than the number
1386 * of strikes, which makes this method much faster if the combatants
1387 * have a lot of HP.
1388 */
1389 class monte_carlo_combat_matrix : public combat_matrix
1390 {
1391 public:
1392 monte_carlo_combat_matrix(unsigned int a_max_hp,
1393 unsigned int b_max_hp,
1394 unsigned int a_hp,
1395 unsigned int b_hp,
1396 const summary_t& a_summary,
1397 const summary_t& b_summary,
1398 bool a_slows,
1399 bool b_slows,
1400 unsigned int a_damage,
1401 unsigned int b_damage,
1402 unsigned int a_slow_damage,
1403 unsigned int b_slow_damage,
1404 int a_drain_percent,
1405 int b_drain_percent,
1406 int a_drain_constant,
1407 int b_drain_constant,
1408 unsigned int rounds,
1409 double a_hit_chance,
1410 double b_hit_chance,
1411 std::vector<combat_slice> a_split,
1412 std::vector<combat_slice> b_split,
1413 double a_initially_slowed_chance,
1414 double b_initially_slowed_chance);
1415
1416 void simulate();
1417
1418 void extract_results(
1419 summary_t& summary_a, summary_t& summary_b) override;
1420
1421 double get_a_hit_probability() const;
1422 double get_b_hit_probability() const;
1423
1424 private:
1425 static const unsigned int NUM_ITERATIONS = 5000u;
1426
1427 std::vector<double> a_initial_;
1428 std::vector<double> b_initial_;
1429 std::vector<double> a_initial_slowed_;
1430 std::vector<double> b_initial_slowed_;
1431 std::vector<combat_slice> a_split_;
1432 std::vector<combat_slice> b_split_;
1433 unsigned int rounds_;
1434 double a_hit_chance_;
1435 double b_hit_chance_;
1436 double a_initially_slowed_chance_;
1437 double b_initially_slowed_chance_;
1438 unsigned int iterations_a_hit_ = 0u;
1439 unsigned int iterations_b_hit_ = 0u;
1440
1441 unsigned int calc_blows_a(unsigned int a_hp) const;
1442 unsigned int calc_blows_b(unsigned int b_hp) const;
1443 static void divide_all_elements(std::vector<double>& vec, double divisor);
1444 static void scale_probabilities(
1445 const std::vector<double>& source, std::vector<double>& target, double divisor, unsigned int singular_hp);
1446 };
1447
monte_carlo_combat_matrix(unsigned int a_max_hp,unsigned int b_max_hp,unsigned int a_hp,unsigned int b_hp,const summary_t & a_summary,const summary_t & b_summary,bool a_slows,bool b_slows,unsigned int a_damage,unsigned int b_damage,unsigned int a_slow_damage,unsigned int b_slow_damage,int a_drain_percent,int b_drain_percent,int a_drain_constant,int b_drain_constant,unsigned int rounds,double a_hit_chance,double b_hit_chance,std::vector<combat_slice> a_split,std::vector<combat_slice> b_split,double a_initially_slowed_chance,double b_initially_slowed_chance)1448 monte_carlo_combat_matrix::monte_carlo_combat_matrix(unsigned int a_max_hp,
1449 unsigned int b_max_hp,
1450 unsigned int a_hp,
1451 unsigned int b_hp,
1452 const summary_t& a_summary,
1453 const summary_t& b_summary,
1454 bool a_slows,
1455 bool b_slows,
1456 unsigned int a_damage,
1457 unsigned int b_damage,
1458 unsigned int a_slow_damage,
1459 unsigned int b_slow_damage,
1460 int a_drain_percent,
1461 int b_drain_percent,
1462 int a_drain_constant,
1463 int b_drain_constant,
1464 unsigned int rounds,
1465 double a_hit_chance,
1466 double b_hit_chance,
1467 std::vector<combat_slice> a_split,
1468 std::vector<combat_slice> b_split,
1469 double a_initially_slowed_chance,
1470 double b_initially_slowed_chance)
1471 : combat_matrix(a_max_hp,
1472 b_max_hp,
1473 a_hp,
1474 b_hp,
1475 a_summary,
1476 b_summary,
1477 a_slows,
1478 b_slows,
1479 a_damage,
1480 b_damage,
1481 a_slow_damage,
1482 b_slow_damage,
1483 a_drain_percent,
1484 b_drain_percent,
1485 a_drain_constant,
1486 b_drain_constant)
1487 , a_split_(a_split)
1488 , b_split_(b_split)
1489 , rounds_(rounds)
1490 , a_hit_chance_(a_hit_chance)
1491 , b_hit_chance_(b_hit_chance)
1492 , a_initially_slowed_chance_(a_initially_slowed_chance)
1493 , b_initially_slowed_chance_(b_initially_slowed_chance)
1494 {
1495 scale_probabilities(a_summary[0], a_initial_, 1.0 - a_initially_slowed_chance, a_hp);
1496 scale_probabilities(a_summary[1], a_initial_slowed_, a_initially_slowed_chance, a_hp);
1497 scale_probabilities(b_summary[0], b_initial_, 1.0 - b_initially_slowed_chance, b_hp);
1498 scale_probabilities(b_summary[1], b_initial_slowed_, b_initially_slowed_chance, b_hp);
1499
1500 clear();
1501 }
1502
simulate()1503 void monte_carlo_combat_matrix::simulate()
1504 {
1505 randomness::rng& rng = randomness::rng::default_instance();
1506
1507 for(unsigned int i = 0u; i < NUM_ITERATIONS; ++i) {
1508 bool a_hit = false;
1509 bool b_hit = false;
1510 bool a_slowed = rng.get_random_bool(a_initially_slowed_chance_);
1511 bool b_slowed = rng.get_random_bool(b_initially_slowed_chance_);
1512 const std::vector<double>& a_initial = a_slowed ? a_initial_slowed_ : a_initial_;
1513 const std::vector<double>& b_initial = b_slowed ? b_initial_slowed_ : b_initial_;
1514 unsigned int a_hp = rng.get_random_element(a_initial.begin(), a_initial.end());
1515 unsigned int b_hp = rng.get_random_element(b_initial.begin(), b_initial.end());
1516 unsigned int a_strikes = calc_blows_a(a_hp);
1517 unsigned int b_strikes = calc_blows_b(b_hp);
1518
1519 for(unsigned int j = 0u; j < rounds_ && a_hp > 0u && b_hp > 0u; ++j) {
1520 for(unsigned int k = 0u; k < std::max(a_strikes, b_strikes); ++k) {
1521 if(k < a_strikes) {
1522 if(rng.get_random_bool(a_hit_chance_)) {
1523 // A hits B
1524 unsigned int damage = a_slowed ? a_slow_damage_ : a_damage_;
1525 damage = std::min(damage, b_hp);
1526 b_hit = true;
1527 b_slowed |= a_slows_;
1528
1529 int drain_amount = (a_drain_percent_ * static_cast<signed>(damage) / 100 + a_drain_constant_);
1530 a_hp = utils::clamp(a_hp + drain_amount, 1u, a_max_hp_);
1531
1532 b_hp -= damage;
1533
1534 if(b_hp == 0u) {
1535 // A killed B
1536 break;
1537 }
1538 }
1539 }
1540
1541 if(k < b_strikes) {
1542 if(rng.get_random_bool(b_hit_chance_)) {
1543 // B hits A
1544 unsigned int damage = b_slowed ? b_slow_damage_ : b_damage_;
1545 damage = std::min(damage, a_hp);
1546 a_hit = true;
1547 a_slowed |= b_slows_;
1548
1549 int drain_amount = (b_drain_percent_ * static_cast<signed>(damage) / 100 + b_drain_constant_);
1550 b_hp = utils::clamp(b_hp + drain_amount, 1u, b_max_hp_);
1551
1552 a_hp -= damage;
1553
1554 if(a_hp == 0u) {
1555 // B killed A
1556 break;
1557 }
1558 }
1559 }
1560 }
1561 }
1562
1563 iterations_a_hit_ += a_hit ? 1 : 0;
1564 iterations_b_hit_ += b_hit ? 1 : 0;
1565
1566 record_monte_carlo_result(a_hp, b_hp, a_slowed, b_slowed);
1567 }
1568 }
1569
1570 /**
1571 * Otherwise the same as in probability_combat_matrix, but this needs to divide the values
1572 * by the number of iterations.
1573 */
extract_results(summary_t & summary_a,summary_t & summary_b)1574 void monte_carlo_combat_matrix::extract_results(
1575 summary_t& summary_a, summary_t& summary_b)
1576 {
1577 // Reset the summaries.
1578 summary_a[0] = std::vector<double>(num_rows());
1579 summary_b[0] = std::vector<double>(num_cols());
1580
1581 if(plane_used(A_SLOWED)) {
1582 summary_a[1] = std::vector<double>(num_rows());
1583 }
1584
1585 if(plane_used(B_SLOWED)) {
1586 summary_b[1] = std::vector<double>(num_cols());
1587 }
1588
1589 for(unsigned p = 0; p < NUM_PLANES; ++p) {
1590 if(!plane_used(p)) {
1591 continue;
1592 }
1593
1594 // A is slow in planes 1 and 3.
1595 unsigned dst_a = (p & 1) ? 1u : 0u;
1596 // B is slow in planes 2 and 3.
1597 unsigned dst_b = (p & 2) ? 1u : 0u;
1598 sum(p, summary_a[dst_a], summary_b[dst_b]);
1599 }
1600
1601 divide_all_elements(summary_a[0], static_cast<double>(NUM_ITERATIONS));
1602 divide_all_elements(summary_b[0], static_cast<double>(NUM_ITERATIONS));
1603
1604 if(plane_used(A_SLOWED)) {
1605 divide_all_elements(summary_a[1], static_cast<double>(NUM_ITERATIONS));
1606 }
1607
1608 if(plane_used(B_SLOWED)) {
1609 divide_all_elements(summary_b[1], static_cast<double>(NUM_ITERATIONS));
1610 }
1611 }
1612
get_a_hit_probability() const1613 double monte_carlo_combat_matrix::get_a_hit_probability() const
1614 {
1615 return static_cast<double>(iterations_a_hit_) / static_cast<double>(NUM_ITERATIONS);
1616 }
1617
get_b_hit_probability() const1618 double monte_carlo_combat_matrix::get_b_hit_probability() const
1619 {
1620 return static_cast<double>(iterations_b_hit_) / static_cast<double>(NUM_ITERATIONS);
1621 }
1622
calc_blows_a(unsigned int a_hp) const1623 unsigned int monte_carlo_combat_matrix::calc_blows_a(unsigned int a_hp) const
1624 {
1625 auto it = a_split_.begin();
1626 while(it != a_split_.end() && it->end_hp <= a_hp) {
1627 ++it;
1628 }
1629
1630 if(it == a_split_.end()) {
1631 --it;
1632 }
1633
1634 return it->strikes;
1635 }
1636
calc_blows_b(unsigned int b_hp) const1637 unsigned int monte_carlo_combat_matrix::calc_blows_b(unsigned int b_hp) const
1638 {
1639 auto it = b_split_.begin();
1640 while(it != b_split_.end() && it->end_hp <= b_hp) {
1641 ++it;
1642 }
1643
1644 if(it == b_split_.end()) {
1645 --it;
1646 }
1647
1648 return it->strikes;
1649 }
1650
scale_probabilities(const std::vector<double> & source,std::vector<double> & target,double divisor,unsigned int singular_hp)1651 void monte_carlo_combat_matrix::scale_probabilities(
1652 const std::vector<double>& source, std::vector<double>& target, double divisor, unsigned int singular_hp)
1653 {
1654 if(divisor == 0.0) {
1655 // Happens if the "target" HP distribution vector isn't used,
1656 // in which case it's not necessary to scale the probabilities.
1657 return;
1658 }
1659
1660 if(source.empty()) {
1661 target.resize(singular_hp + 1u, 0.0);
1662 target[singular_hp] = 1.0;
1663 } else {
1664 std::transform(
1665 source.begin(), source.end(), std::back_inserter(target), [=](double prob) { return prob / divisor; });
1666 }
1667
1668 assert(std::abs(std::accumulate(target.begin(), target.end(), 0.0) - 1.0) < 0.001);
1669 }
1670
divide_all_elements(std::vector<double> & vec,double divisor)1671 void monte_carlo_combat_matrix::divide_all_elements(std::vector<double>& vec, double divisor)
1672 {
1673 for(double& e : vec) {
1674 e /= divisor;
1675 }
1676 }
1677
1678 } // end anon namespace
1679
combatant(const battle_context_unit_stats & u,const combatant * prev)1680 combatant::combatant(const battle_context_unit_stats& u, const combatant* prev)
1681 : hp_dist(u.max_hp + 1, 0.0)
1682 , untouched(0.0)
1683 , poisoned(0.0)
1684 , slowed(0.0)
1685 , u_(u)
1686 {
1687 // We inherit current state from previous combatant.
1688 if(prev) {
1689 summary[0] = prev->summary[0];
1690 summary[1] = prev->summary[1];
1691 hp_dist = prev->hp_dist;
1692 untouched = prev->untouched;
1693 poisoned = prev->poisoned;
1694 slowed = prev->slowed;
1695 } else {
1696 hp_dist[std::min(u.hp, u.max_hp)] = 1.0;
1697 untouched = 1.0;
1698 poisoned = u.is_poisoned ? 1.0 : 0.0;
1699 slowed = u.is_slowed ? 1.0 : 0.0;
1700
1701 // If we're already slowed, create summary[1] so that probability calculation code
1702 // knows that we're slowed.
1703 if(u.is_slowed) {
1704 summary[0].resize(u.max_hp + 1, 0.0);
1705 summary[1] = hp_dist;
1706 }
1707 }
1708 }
1709
1710 // Copy constructor (except use this copy of battle_context_unit_stats)
combatant(const combatant & that,const battle_context_unit_stats & u)1711 combatant::combatant(const combatant& that, const battle_context_unit_stats& u)
1712 : hp_dist(that.hp_dist)
1713 , untouched(that.untouched)
1714 , poisoned(that.poisoned)
1715 , slowed(that.slowed)
1716 , u_(u)
1717 {
1718 summary[0] = that.summary[0];
1719 summary[1] = that.summary[1];
1720 }
1721
1722 namespace
1723 {
1724 enum class attack_prediction_mode { probability_calculation, monte_carlo_simulation };
1725
forced_levelup(std::vector<double> & hp_dist)1726 void forced_levelup(std::vector<double>& hp_dist)
1727 {
1728 /* If we survive the combat, we will level up. So the probability
1729 of death is unchanged, but all other cases get merged into the
1730 fully healed case. */
1731 auto i = hp_dist.begin();
1732 // Skip to the second value.
1733 for(++i; i != hp_dist.end(); ++i) {
1734 *i = 0;
1735 }
1736
1737 // Full heal unless dead.
1738 hp_dist.back() = 1 - hp_dist.front();
1739 }
1740
conditional_levelup(std::vector<double> & hp_dist,double kill_prob)1741 void conditional_levelup(std::vector<double>& hp_dist, double kill_prob)
1742 {
1743 /* If we kill, we will level up. So then the damage we had becomes
1744 less probable since it's now conditional on us not leveling up.
1745 This doesn't apply to the probability of us dying, of course. */
1746 double scalefactor = 0;
1747 const double chance_to_survive = 1 - hp_dist.front();
1748 if(chance_to_survive > DBL_MIN) {
1749 scalefactor = 1 - kill_prob / chance_to_survive;
1750 }
1751
1752 auto i = hp_dist.begin();
1753 // Skip to the second value.
1754 for(++i; i != hp_dist.end(); ++i) {
1755 *i *= scalefactor;
1756 }
1757
1758 // Full heal if leveled up.
1759 hp_dist.back() += kill_prob;
1760 }
1761
1762 /* Calculates the probability that we will be poisoned after the fight. Parameters:
1763 * initial_prob: how likely we are to be poisoned before the fight.
1764 * enemy_gives: true if the enemy poisons us.
1765 * prob_touched: probability the enemy touches us.
1766 * prob_stay_alive: probability we survive the fight alive.
1767 * kill_heals: true if killing the enemy heals the poison (in other words, we get a level-up).
1768 * prob_kill: probability we kill the enemy.
1769 */
calculate_probability_of_debuff(double initial_prob,bool enemy_gives,double prob_touched,double prob_stay_alive,bool kill_heals,double prob_kill)1770 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 {
1772 assert(initial_prob >= 0.0 && initial_prob <= 1.0);
1773 /* In the add-on "Legend of the Invincibles", the "distant attack" ability sets the probability of being touched to zero.
1774 With some optimizing compilers, the probability can somehow get slightly negative, see bug #2342.
1775 Ensure that it gets non-negative. */
1776 prob_touched = std::max(prob_touched, 0.0);
1777 // Prob_stay_alive can get slightly negative because of a rounding error, so ensure that it gets non-negative.
1778 prob_stay_alive = std::max(prob_stay_alive, 0.0);
1779 // Prob_kill can creep a bit above 100 % if the AI simulates an unit being attacked by multiple units in a row, due to rounding error.
1780 // Likewise, it can get slightly negative if the unit already has negative HP.
1781 // Simply limit it to suitable range.
1782 prob_kill = utils::clamp(prob_kill, 0.0, 1.0);
1783
1784 // Probability we are already debuffed and the enemy doesn't hit us.
1785 const double prob_already_debuffed_not_touched = initial_prob * (1.0 - prob_touched);
1786 // Probability we are already debuffed and the enemy hits us.
1787 const double prob_already_debuffed_touched = initial_prob * prob_touched;
1788 // Probability we aren't debuffed and the enemy doesn't hit us.
1789 // const double prob_initially_healthy_not_touched = (1.0 - initial_prob) * (1.0 - prob_touched);
1790 // Probability we aren't debuffed and the enemy hits us.
1791 const double prob_initially_healthy_touched = (1.0 - initial_prob) * prob_touched;
1792
1793 // Probability to survive if the enemy doesn't hit us.
1794 const double prob_survive_if_not_hit = 1.0;
1795 // Probability to survive if the enemy hits us.
1796 const double prob_survive_if_hit = prob_touched > 0.0 ? (prob_stay_alive - (1.0 - prob_touched)) / prob_touched : 1.0;
1797
1798 // Probability to kill if we don't survive the fight.
1799 // const double prob_kill_if_not_survive = 0.0;
1800 // Probability to kill if we survive the fight.
1801 const double prob_kill_if_survive = prob_stay_alive > 0.0 ? prob_kill / prob_stay_alive : 0.0;
1802
1803 // Probability to be debuffed after the fight, calculated in multiple stages.
1804 double prob_debuff = 0.0;
1805
1806 if(!kill_heals) {
1807 prob_debuff += prob_already_debuffed_not_touched;
1808 } else {
1809 prob_debuff += prob_already_debuffed_not_touched * (1.0 - prob_survive_if_not_hit * prob_kill_if_survive);
1810 }
1811
1812 if(!kill_heals) {
1813 prob_debuff += prob_already_debuffed_touched;
1814 } else {
1815 prob_debuff += prob_already_debuffed_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1816 }
1817
1818 // "Originally not debuffed, not hit" situation never results in us getting debuffed. Skipping.
1819
1820 if(!enemy_gives) {
1821 // Do nothing.
1822 } else if(!kill_heals) {
1823 prob_debuff += prob_initially_healthy_touched;
1824 } else {
1825 prob_debuff += prob_initially_healthy_touched * (1.0 - prob_survive_if_hit * prob_kill_if_survive);
1826 }
1827
1828 return prob_debuff;
1829 }
1830
1831 // Rounds a probability that's extremely close to 0 or 1 to exactly 0 or 1.
round_prob_if_close_to_sure(double & prob)1832 void round_prob_if_close_to_sure(double& prob)
1833 {
1834 if(prob < 1.0e-9) {
1835 prob = 0.0;
1836 } else if(prob > 1.0 - 1.0e-9) {
1837 prob = 1.0;
1838 }
1839 }
1840
1841 /**
1842 * Returns the smallest HP we could possibly have based on the provided
1843 * hit point distribution.
1844 */
min_hp(const std::vector<double> & hp_dist,unsigned def)1845 unsigned min_hp(const std::vector<double>& hp_dist, unsigned def)
1846 {
1847 const unsigned size = hp_dist.size();
1848
1849 // Look for a nonzero entry.
1850 for(unsigned i = 0; i != size; ++i) {
1851 if(hp_dist[i] != 0.0) {
1852 return i;
1853 }
1854 }
1855
1856 // Either the distribution is empty or is full of zeros, so
1857 // return the default.
1858 return def;
1859 }
1860
1861 /**
1862 * Returns a number that approximates the complexity of the fight,
1863 * for the purpose of determining if it's faster to calculate exact
1864 * probabilities or to run a Monte Carlo simulation.
1865 * Ignores the numbers of rounds and strikes because these slow down
1866 * both calculation modes.
1867 */
fight_complexity(unsigned int num_slices,unsigned int opp_num_slices,const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats)1868 unsigned int fight_complexity(unsigned int num_slices,
1869 unsigned int opp_num_slices,
1870 const battle_context_unit_stats& stats,
1871 const battle_context_unit_stats& opp_stats)
1872 {
1873 return num_slices * opp_num_slices * ((stats.slows || opp_stats.is_slowed) ? 2 : 1)
1874 * ((opp_stats.slows || stats.is_slowed) ? 2 : 1) * stats.max_hp * opp_stats.max_hp;
1875 }
1876
1877 /**
1878 * Returns the plane index for the slow/no-slow state of the combatants.
1879 */
plane_index(const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats)1880 unsigned int plane_index(const battle_context_unit_stats& stats,
1881 const battle_context_unit_stats& opp_stats)
1882 {
1883 return (stats.is_slowed ? 1 : 0) | (opp_stats.is_slowed ? 2 : 0);
1884 }
1885
1886 // Combat without chance of death, berserk, slow or drain is simple.
no_death_fight(const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats,unsigned strikes,unsigned opp_strikes,std::vector<double> & hp_dist,std::vector<double> & opp_hp_dist,double & self_not_hit,double & opp_not_hit,bool levelup_considered)1887 void no_death_fight(const battle_context_unit_stats& stats,
1888 const battle_context_unit_stats& opp_stats,
1889 unsigned strikes,
1890 unsigned opp_strikes,
1891 std::vector<double>& hp_dist,
1892 std::vector<double>& opp_hp_dist,
1893 double& self_not_hit,
1894 double& opp_not_hit,
1895 bool levelup_considered)
1896 {
1897 // Our strikes.
1898 // If we were killed in an earlier fight, we don't get to attack.
1899 // (Most likely case: we are a first striking defender subject to a series
1900 // of attacks.)
1901 const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1902 const double hit_chance = (stats.chance_to_hit / 100.0) * alive_prob;
1903
1904 if(opp_hp_dist.empty()) {
1905 // Starts with a known HP, so Pascal's triangle.
1906 opp_hp_dist = std::vector<double>(opp_stats.max_hp + 1);
1907 opp_hp_dist[opp_stats.hp] = 1.0;
1908
1909 for(unsigned int i = 0; i < strikes; ++i) {
1910 for(int j = i; j >= 0; j--) {
1911 unsigned src_index = opp_stats.hp - j * stats.damage;
1912 double move = opp_hp_dist[src_index] * hit_chance;
1913 opp_hp_dist[src_index] -= move;
1914 opp_hp_dist[src_index - stats.damage] += move;
1915 }
1916
1917 opp_not_hit *= 1.0 - hit_chance;
1918 }
1919 } else {
1920 // HP could be spread anywhere, iterate through whole thing.
1921 for(unsigned int i = 0; i < strikes; ++i) {
1922 for(unsigned int j = stats.damage; j < opp_hp_dist.size(); ++j) {
1923 double move = opp_hp_dist[j] * hit_chance;
1924 opp_hp_dist[j] -= move;
1925 opp_hp_dist[j - stats.damage] += move;
1926 }
1927 opp_not_hit *= 1.0 - hit_chance;
1928 }
1929 }
1930
1931 // Opponent's strikes
1932 // If opponent was killed in an earlier fight, they don't get to attack.
1933 const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1934 const double opp_hit_chance = (opp_stats.chance_to_hit / 100.0) * opp_alive_prob;
1935
1936 if(hp_dist.empty()) {
1937 // Starts with a known HP, so Pascal's triangle.
1938 hp_dist = std::vector<double>(stats.max_hp + 1);
1939 hp_dist[stats.hp] = 1.0;
1940 for(unsigned int i = 0; i < opp_strikes; ++i) {
1941 for(int j = i; j >= 0; j--) {
1942 unsigned src_index = stats.hp - j * opp_stats.damage;
1943 double move = hp_dist[src_index] * opp_hit_chance;
1944 hp_dist[src_index] -= move;
1945 hp_dist[src_index - opp_stats.damage] += move;
1946 }
1947
1948 self_not_hit *= 1.0 - opp_hit_chance;
1949 }
1950 } else {
1951 // HP could be spread anywhere, iterate through whole thing.
1952 for(unsigned int i = 0; i < opp_strikes; ++i) {
1953 for(unsigned int j = opp_stats.damage; j < hp_dist.size(); ++j) {
1954 double move = hp_dist[j] * opp_hit_chance;
1955 hp_dist[j] -= move;
1956 hp_dist[j - opp_stats.damage] += move;
1957 }
1958
1959 self_not_hit *= 1.0 - opp_hit_chance;
1960 }
1961 }
1962
1963 if(!levelup_considered) {
1964 return;
1965 }
1966
1967 if(stats.experience + opp_stats.level >= stats.max_experience) {
1968 forced_levelup(hp_dist);
1969 }
1970
1971 if(opp_stats.experience + stats.level >= opp_stats.max_experience) {
1972 forced_levelup(opp_hp_dist);
1973 }
1974 }
1975
1976 // Combat with <= 1 strike each is simple, too.
one_strike_fight(const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats,unsigned strikes,unsigned opp_strikes,std::vector<double> & hp_dist,std::vector<double> & opp_hp_dist,double & self_not_hit,double & opp_not_hit,bool levelup_considered)1977 void one_strike_fight(const battle_context_unit_stats& stats,
1978 const battle_context_unit_stats& opp_stats,
1979 unsigned strikes,
1980 unsigned opp_strikes,
1981 std::vector<double>& hp_dist,
1982 std::vector<double>& opp_hp_dist,
1983 double& self_not_hit,
1984 double& opp_not_hit,
1985 bool levelup_considered)
1986 {
1987 // If we were killed in an earlier fight, we don't get to attack.
1988 // (Most likely case: we are a first striking defender subject to a series
1989 // of attacks.)
1990 double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1991 if(stats.hp == 0) {
1992 alive_prob = 0.0;
1993 }
1994 const double hit_chance = (stats.chance_to_hit / 100.0) * alive_prob;
1995
1996 if(opp_hp_dist.empty()) {
1997 opp_hp_dist = std::vector<double>(opp_stats.max_hp + 1);
1998 if(strikes == 1 && opp_stats.hp > 0) {
1999 opp_hp_dist[opp_stats.hp] = 1.0 - hit_chance;
2000 opp_hp_dist[std::max<int>(opp_stats.hp - stats.damage, 0)] = hit_chance;
2001 opp_not_hit *= 1.0 - hit_chance;
2002 } else {
2003 opp_hp_dist[opp_stats.hp] = 1.0;
2004 }
2005 } else {
2006 if(strikes == 1) {
2007 for(unsigned int i = 1; i < opp_hp_dist.size(); ++i) {
2008 double move = opp_hp_dist[i] * hit_chance;
2009 opp_hp_dist[i] -= move;
2010 opp_hp_dist[std::max<int>(i - stats.damage, 0)] += move;
2011 }
2012
2013 opp_not_hit *= 1.0 - hit_chance;
2014 }
2015 }
2016
2017 // If we killed opponent, it won't attack us.
2018 const double opp_attack_prob = (1.0 - opp_hp_dist[0]) * alive_prob;
2019 const double opp_hit_chance = (opp_stats.chance_to_hit / 100.0) * opp_attack_prob;
2020
2021 if(hp_dist.empty()) {
2022 hp_dist = std::vector<double>(stats.max_hp + 1);
2023 if(opp_strikes == 1 && stats.hp > 0) {
2024 hp_dist[stats.hp] = 1.0 - opp_hit_chance;
2025 hp_dist[std::max<int>(stats.hp - opp_stats.damage, 0)] = opp_hit_chance;
2026 self_not_hit *= 1.0 - opp_hit_chance;
2027 } else {
2028 hp_dist[stats.hp] = 1.0;
2029 }
2030 } else {
2031 if(opp_strikes == 1) {
2032 for(unsigned int i = 1; i < hp_dist.size(); ++i) {
2033 double move = hp_dist[i] * opp_hit_chance;
2034 hp_dist[i] -= move;
2035 hp_dist[std::max<int>(i - opp_stats.damage, 0)] += move;
2036 }
2037
2038 self_not_hit *= 1.0 - opp_hit_chance;
2039 }
2040 }
2041
2042 if(!levelup_considered) {
2043 return;
2044 }
2045
2046 if(stats.experience + opp_stats.level >= stats.max_experience) {
2047 forced_levelup(hp_dist);
2048 } else if(stats.experience + game_config::kill_xp(opp_stats.level) >= stats.max_experience) {
2049 conditional_levelup(hp_dist, opp_hp_dist[0]);
2050 }
2051
2052 if(opp_stats.experience + stats.level >= opp_stats.max_experience) {
2053 forced_levelup(opp_hp_dist);
2054 } else if(opp_stats.experience + game_config::kill_xp(stats.level) >= opp_stats.max_experience) {
2055 conditional_levelup(opp_hp_dist, hp_dist[0]);
2056 }
2057 }
2058
2059 /* The parameters "split", "opp_split", "initially_slowed_chance" and
2060 "opp_initially_slowed_chance" are ignored in the probability calculation mode. */
complex_fight(attack_prediction_mode mode,const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats,unsigned strikes,unsigned opp_strikes,summary_t & summary,summary_t & opp_summary,double & self_not_hit,double & opp_not_hit,bool levelup_considered,std::vector<combat_slice> split,std::vector<combat_slice> opp_split,double initially_slowed_chance,double opp_initially_slowed_chance)2061 void complex_fight(attack_prediction_mode mode,
2062 const battle_context_unit_stats& stats,
2063 const battle_context_unit_stats& opp_stats,
2064 unsigned strikes,
2065 unsigned opp_strikes,
2066 summary_t& summary,
2067 summary_t& opp_summary,
2068 double& self_not_hit,
2069 double& opp_not_hit,
2070 bool levelup_considered,
2071 std::vector<combat_slice> split,
2072 std::vector<combat_slice> opp_split,
2073 double initially_slowed_chance,
2074 double opp_initially_slowed_chance)
2075 {
2076 unsigned int rounds = std::max<unsigned int>(stats.rounds, opp_stats.rounds);
2077 unsigned max_attacks = std::max(strikes, opp_strikes);
2078
2079 debug(("A gets %u attacks, B %u.\n", strikes, opp_strikes));
2080
2081 unsigned int a_damage = stats.damage, a_slow_damage = stats.slow_damage;
2082 unsigned int b_damage = opp_stats.damage, b_slow_damage = opp_stats.slow_damage;
2083
2084 // To simulate stoning, we set to amount which kills, and re-adjust after.
2085 /** @todo FIXME: This doesn't work for rolling calculations, just first battle.
2086 It also does not work if combined with (percentage) drain. */
2087 if(stats.petrifies) {
2088 a_damage = a_slow_damage = opp_stats.max_hp;
2089 }
2090
2091 if(opp_stats.petrifies) {
2092 b_damage = b_slow_damage = stats.max_hp;
2093 }
2094
2095 const double original_self_not_hit = self_not_hit;
2096 const double original_opp_not_hit = opp_not_hit;
2097 const double hit_chance = stats.chance_to_hit / 100.0;
2098 const double opp_hit_chance = opp_stats.chance_to_hit / 100.0;
2099 double self_hit = 0.0;
2100 double opp_hit = 0.0;
2101 double self_hit_unknown = 1.0; // Probability we don't yet know if A will be hit or not
2102 double opp_hit_unknown = 1.0; // Ditto for B
2103
2104 // Prepare the matrix that will do our calculations.
2105 std::unique_ptr<combat_matrix> m;
2106 if(mode == attack_prediction_mode::probability_calculation) {
2107 debug(("Using exact probability calculations.\n"));
2108
2109 probability_combat_matrix* pm = new probability_combat_matrix(stats.max_hp, opp_stats.max_hp, stats.hp,
2110 opp_stats.hp, summary, opp_summary, stats.slows, opp_stats.slows,
2111 a_damage, b_damage, a_slow_damage, b_slow_damage,
2112 stats.drain_percent, opp_stats.drain_percent, stats.drain_constant, opp_stats.drain_constant);
2113 m.reset(pm);
2114
2115 do {
2116 for(unsigned int i = 0; i < max_attacks; ++i) {
2117 if(i < strikes) {
2118 debug(("A strikes\n"));
2119 double b_already_dead = pm->dead_prob_b();
2120 pm->receive_blow_b(hit_chance);
2121 pm->dump();
2122
2123 double first_hit = hit_chance * opp_hit_unknown;
2124 opp_hit += first_hit;
2125 opp_hit_unknown -= first_hit;
2126 double both_were_alive = (1.0 - b_already_dead) * (1.0 - pm->dead_prob_a());
2127 double this_hit_killed_b = both_were_alive != 0.0 ? (pm->dead_prob_b() - b_already_dead) / both_were_alive : 1.0;
2128 self_hit_unknown *= (1.0 - this_hit_killed_b);
2129 }
2130 if(i < opp_strikes) {
2131 debug(("B strikes\n"));
2132 double a_already_dead = pm->dead_prob_a();
2133 pm->receive_blow_a(opp_hit_chance);
2134 pm->dump();
2135
2136 double first_hit = opp_hit_chance * self_hit_unknown;
2137 self_hit += first_hit;
2138 self_hit_unknown -= first_hit;
2139 double both_were_alive = (1.0 - a_already_dead) * (1.0 - pm->dead_prob_b());
2140 double this_hit_killed_a = both_were_alive != 0.0 ? (pm->dead_prob_a() - a_already_dead) / both_were_alive : 1.0;
2141 opp_hit_unknown *= (1.0 - this_hit_killed_a);
2142 }
2143 }
2144
2145 debug(("Combat ends:\n"));
2146 pm->dump();
2147 } while(--rounds && pm->dead_prob() < 0.99);
2148
2149 self_hit = std::min(self_hit, 1.0);
2150 opp_hit = std::min(opp_hit, 1.0);
2151
2152 self_not_hit = original_self_not_hit * (1.0 - self_hit);
2153 opp_not_hit = original_opp_not_hit * (1.0 - opp_hit);
2154
2155 if(stats.slows) {
2156 /* The calculation method for the "not hit" probability above is incorrect if either unit can slow.
2157 * Because a slowed unit deals less damage, it is more likely for the slowing unit to be alive if it
2158 * has hit the other unit. In that situation, the "not hit" probability can no longer be calculated
2159 * with simple addition.
2160 * Instead, just fetch the probability from the combat matrix.
2161 */
2162 unsigned int plane = plane_index(stats, opp_stats);
2163 double not_hit = pm->col_sum(plane, opp_stats.hp) + ((plane & 1) ? 0.0 : pm->col_sum(plane | 1, opp_stats.hp));
2164 opp_not_hit = original_opp_not_hit * not_hit;
2165 }
2166 if(opp_stats.slows) {
2167 unsigned int plane = plane_index(stats, opp_stats);
2168 double not_hit = pm->row_sum(plane, stats.hp) + ((plane & 2) ? 0.0 : pm->row_sum(plane | 2, stats.hp));
2169 self_not_hit = original_self_not_hit * not_hit;
2170 }
2171 } else {
2172 debug(("Using Monte Carlo simulation.\n"));
2173
2174 monte_carlo_combat_matrix* mcm = new monte_carlo_combat_matrix(stats.max_hp, opp_stats.max_hp, stats.hp,
2175 opp_stats.hp, summary, opp_summary, stats.slows, opp_stats.slows,
2176 a_damage, b_damage, a_slow_damage, b_slow_damage,
2177 stats.drain_percent, opp_stats.drain_percent, stats.drain_constant, opp_stats.drain_constant, rounds,
2178 hit_chance, opp_hit_chance, split, opp_split, initially_slowed_chance, opp_initially_slowed_chance);
2179 m.reset(mcm);
2180
2181 mcm->simulate();
2182 debug(("Combat ends:\n"));
2183 mcm->dump();
2184
2185 self_not_hit = 1.0 - mcm->get_a_hit_probability();
2186 opp_not_hit = 1.0 - mcm->get_b_hit_probability();
2187 }
2188
2189 if(stats.petrifies) {
2190 m->remove_petrify_distortion_a(stats.damage, stats.slow_damage, opp_stats.hp);
2191 }
2192
2193 if(opp_stats.petrifies) {
2194 m->remove_petrify_distortion_b(opp_stats.damage, opp_stats.slow_damage, stats.hp);
2195 }
2196
2197 if(levelup_considered) {
2198 if(stats.experience + opp_stats.level >= stats.max_experience) {
2199 m->forced_levelup_a();
2200 } else if(stats.experience + game_config::kill_xp(opp_stats.level) >= stats.max_experience) {
2201 m->conditional_levelup_a();
2202 }
2203
2204 if(opp_stats.experience + stats.level >= opp_stats.max_experience) {
2205 m->forced_levelup_b();
2206 } else if(opp_stats.experience + game_config::kill_xp(stats.level) >= opp_stats.max_experience) {
2207 m->conditional_levelup_b();
2208 }
2209 }
2210
2211 // We extract results separately, then combine.
2212 m->extract_results(summary, opp_summary);
2213 }
2214
2215 /**
2216 * Chooses the best of the various known combat calculations for the current
2217 * situation.
2218 */
do_fight(const battle_context_unit_stats & stats,const battle_context_unit_stats & opp_stats,unsigned strikes,unsigned opp_strikes,summary_t & summary,summary_t & opp_summary,double & self_not_hit,double & opp_not_hit,bool levelup_considered)2219 void do_fight(const battle_context_unit_stats& stats,
2220 const battle_context_unit_stats& opp_stats,
2221 unsigned strikes,
2222 unsigned opp_strikes,
2223 summary_t& summary,
2224 summary_t& opp_summary,
2225 double& self_not_hit,
2226 double& opp_not_hit,
2227 bool levelup_considered)
2228 {
2229 // Optimization only works in the simple cases (no slow, no drain,
2230 // no petrify, no berserk, and no slowed results from an earlier combat).
2231 if(!stats.slows && !opp_stats.slows && !stats.drains && !opp_stats.drains && !stats.petrifies
2232 && !opp_stats.petrifies && stats.rounds == 1 && opp_stats.rounds == 1 && summary[1].empty()
2233 && opp_summary[1].empty())
2234 {
2235 if(strikes <= 1 && opp_strikes <= 1) {
2236 one_strike_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2237 opp_not_hit, levelup_considered);
2238 } else if(strikes * stats.damage < min_hp(opp_summary[0], opp_stats.hp)
2239 && opp_strikes * opp_stats.damage < min_hp(summary[0], stats.hp)) {
2240 no_death_fight(stats, opp_stats, strikes, opp_strikes, summary[0], opp_summary[0], self_not_hit,
2241 opp_not_hit, levelup_considered);
2242 } else {
2243 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes,
2244 summary, opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2245 std::vector<combat_slice>(), 0.0, 0.0);
2246 }
2247 } else {
2248 complex_fight(attack_prediction_mode::probability_calculation, stats, opp_stats, strikes, opp_strikes, summary,
2249 opp_summary, self_not_hit, opp_not_hit, levelup_considered, std::vector<combat_slice>(),
2250 std::vector<combat_slice>(), 0.0, 0.0);
2251 }
2252 }
2253
2254 /**
2255 * Initializes a hit point summary (assumed empty) based on the source.
2256 * Only the part of the source from begin_hp up to (not including) end_hp
2257 * is kept, and all values get scaled by prob.
2258 */
init_slice_summary(std::vector<double> & dst,const std::vector<double> & src,unsigned begin_hp,unsigned end_hp,double prob)2259 void init_slice_summary(
2260 std::vector<double>& dst, const std::vector<double>& src, unsigned begin_hp, unsigned end_hp, double prob)
2261 {
2262 if(src.empty()) {
2263 // Nothing to do.
2264 return;
2265 }
2266
2267 const unsigned size = src.size();
2268 // Avoid going over the end of the vector.
2269 if(end_hp > size) {
2270 end_hp = size;
2271 }
2272
2273 // Initialize the destination.
2274 dst.resize(size, 0.0);
2275 for(unsigned i = begin_hp; i < end_hp; ++i) {
2276 dst[i] = src[i] / prob;
2277 }
2278 }
2279
2280 /**
2281 * Merges the summary results of simulation into an overall summary.
2282 * This uses prob to reverse the scaling that was done in init_slice_summary().
2283 */
merge_slice_summary(std::vector<double> & dst,const std::vector<double> & src,double prob)2284 void merge_slice_summary(std::vector<double>& dst, const std::vector<double>& src, double prob)
2285 {
2286 const unsigned size = src.size();
2287
2288 // Make sure we have enough space.
2289 if(dst.size() < size) {
2290 dst.resize(size, 0.0);
2291 }
2292
2293 // Merge the data.
2294 for(unsigned i = 0; i != size; ++i) {
2295 dst[i] += src[i] * prob;
2296 }
2297 }
2298
2299 } // end anon namespace
2300
2301 // Two man enter. One man leave!
2302 // ... Or maybe two. But definitely not three.
2303 // Of course, one could be a woman. Or both.
2304 // And either could be non-human, too.
2305 // Um, ok, it was a stupid thing to say.
fight(combatant & opponent,bool levelup_considered)2306 void combatant::fight(combatant& opponent, bool levelup_considered)
2307 {
2308 // If defender has firststrike and we don't, reverse.
2309 if(opponent.u_.firststrike && !u_.firststrike) {
2310 opponent.fight(*this, levelup_considered);
2311 return;
2312 }
2313
2314 #ifdef ATTACK_PREDICTION_DEBUG
2315 printf("A:\n");
2316 dump(u_);
2317 printf("B:\n");
2318 dump(opponent.u_);
2319 #endif
2320
2321 #if 0
2322 std::vector<double> prev = summary[0], opp_prev = opponent.summary[0];
2323 complex_fight(opponent, 1);
2324 std::vector<double> res = summary[0], opp_res = opponent.summary[0];
2325 summary[0] = prev;
2326 opponent.summary[0] = opp_prev;
2327 #endif
2328
2329 // The chance so far of not being hit this combat:
2330 double self_not_hit = 1.0;
2331 double opp_not_hit = 1.0;
2332
2333 // The probability of being already dead before the fight begins:
2334 double self_already_dead = hp_dist[0];
2335 double opp_already_dead = opponent.hp_dist[0];
2336
2337 // If incoming slow probabilities are extremely close to 0 or 1, round them to exactly 0 or 1 (bug #3321)
2338 round_prob_if_close_to_sure(slowed);
2339 round_prob_if_close_to_sure(opponent.slowed);
2340
2341 // If we've fought before and we have swarm, we might have to split the
2342 // calculation by number of attacks.
2343 const std::vector<combat_slice> split = split_summary(u_, summary);
2344 const std::vector<combat_slice> opp_split = split_summary(opponent.u_, opponent.summary);
2345
2346 bool use_monte_carlo_simulation =
2347 fight_complexity(split.size(), opp_split.size(), u_, opponent.u_) > MONTE_CARLO_SIMULATION_THRESHOLD
2348 && preferences::damage_prediction_allow_monte_carlo_simulation();
2349
2350 if(use_monte_carlo_simulation) {
2351 // A very complex fight. Use Monte Carlo simulation instead of exact
2352 // probability calculations.
2353 complex_fight(attack_prediction_mode::monte_carlo_simulation, u_, opponent.u_, u_.num_blows,
2354 opponent.u_.num_blows, summary, opponent.summary, self_not_hit, opp_not_hit, levelup_considered, split,
2355 opp_split, slowed, opponent.slowed);
2356 } else if(split.size() == 1 && opp_split.size() == 1) {
2357 // No special treatment due to swarm is needed. Ignore the split.
2358 do_fight(u_, opponent.u_, u_.num_blows, opponent.u_.num_blows, summary, opponent.summary, self_not_hit,
2359 opp_not_hit, levelup_considered);
2360 } else {
2361 // Storage for the accumulated hit point distributions.
2362 summary_t summary_result, opp_summary_result;
2363 // The chance of not being hit becomes an accumulated chance:
2364 self_not_hit = 0.0;
2365 opp_not_hit = 0.0;
2366
2367 // Loop through all the potential combat situations.
2368 for(unsigned s = 0; s != split.size(); ++s) {
2369 for(unsigned t = 0; t != opp_split.size(); ++t) {
2370 const double sit_prob = split[s].prob * opp_split[t].prob;
2371
2372 // Create summaries for this potential combat situation.
2373 summary_t sit_summary, sit_opp_summary;
2374 init_slice_summary(sit_summary[0], summary[0], split[s].begin_hp, split[s].end_hp, split[s].prob);
2375 init_slice_summary(sit_summary[1], summary[1], split[s].begin_hp, split[s].end_hp, split[s].prob);
2376 init_slice_summary(sit_opp_summary[0], opponent.summary[0], opp_split[t].begin_hp, opp_split[t].end_hp,
2377 opp_split[t].prob);
2378 init_slice_summary(sit_opp_summary[1], opponent.summary[1], opp_split[t].begin_hp, opp_split[t].end_hp,
2379 opp_split[t].prob);
2380
2381 // Scale the "not hit" chance for this situation by the chance that
2382 // this situation applies.
2383 double sit_self_not_hit = sit_prob;
2384 double sit_opp_not_hit = sit_prob;
2385
2386 do_fight(u_, opponent.u_, split[s].strikes, opp_split[t].strikes, sit_summary, sit_opp_summary,
2387 sit_self_not_hit, sit_opp_not_hit, levelup_considered);
2388
2389 // Collect the results.
2390 self_not_hit += sit_self_not_hit;
2391 opp_not_hit += sit_opp_not_hit;
2392 merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
2393 merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
2394 merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
2395 merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
2396 }
2397 }
2398
2399 // Swap in the results.
2400 summary[0].swap(summary_result[0]);
2401 summary[1].swap(summary_result[1]);
2402 opponent.summary[0].swap(opp_summary_result[0]);
2403 opponent.summary[1].swap(opp_summary_result[1]);
2404 }
2405
2406 #if 0
2407 assert(summary[0].size() == res.size());
2408 assert(opponent.summary[0].size() == opp_res.size());
2409 for(unsigned int i = 0; i < summary[0].size(); ++i) {
2410 if(std::fabs(summary[0][i] - res[i]) > 0.000001) {
2411 std::cerr << "Mismatch for " << i << " hp: " << summary[0][i] << " should have been " << res[i] << "\n";
2412 assert(false);
2413 }
2414 }
2415 for(unsigned int i = 0; i < opponent.summary[0].size(); ++i) {
2416 if(std::fabs(opponent.summary[0][i] - opp_res[i]) > 0.000001) {
2417 std::cerr << "Mismatch for " << i << " hp: " << opponent.summary[0][i] << " should have been " << opp_res[i] << "\n";
2418 assert(false);
2419 }
2420 }
2421 #endif
2422
2423 // Combine summary into distribution.
2424 if(summary[1].empty()) {
2425 hp_dist = summary[0];
2426 } else {
2427 const unsigned size = summary[0].size();
2428 hp_dist.resize(size);
2429 for(unsigned int i = 0; i < size; ++i)
2430 hp_dist[i] = summary[0][i] + summary[1][i];
2431 }
2432
2433 if(opponent.summary[1].empty()) {
2434 opponent.hp_dist = opponent.summary[0];
2435 } else {
2436 const unsigned size = opponent.summary[0].size();
2437 opponent.hp_dist.resize(size);
2438 for(unsigned int i = 0; i < size; ++i)
2439 opponent.hp_dist[i] = opponent.summary[0][i] + opponent.summary[1][i];
2440 }
2441
2442 // Chance that we / they were touched this time.
2443 double touched = 1.0 - self_not_hit;
2444 double opp_touched = 1.0 - opp_not_hit;
2445
2446 poisoned = calculate_probability_of_debuff(poisoned, opponent.u_.poisons, touched, 1.0 - hp_dist[0],
2447 u_.experience + game_config::kill_xp(opponent.u_.level) >= u_.max_experience, opponent.hp_dist[0] - opp_already_dead);
2448 opponent.poisoned = calculate_probability_of_debuff(opponent.poisoned, u_.poisons, opp_touched, 1.0 - opponent.hp_dist[0],
2449 opponent.u_.experience + game_config::kill_xp(u_.level) >= opponent.u_.max_experience, hp_dist[0] - self_already_dead);
2450
2451 /* The slowed probability depends on in how many rounds
2452 * the combatant happened to be slowed.
2453 * We need to recalculate it based on the HP distribution.
2454 */
2455 slowed = std::min(std::accumulate(summary[1].begin(), summary[1].end(), 0.0), 1.0);
2456 opponent.slowed = std::min(std::accumulate(opponent.summary[1].begin(), opponent.summary[1].end(), 0.0), 1.0);
2457
2458 if(u_.experience + opponent.u_.level >= u_.max_experience) {
2459 // We'll level up after the battle -> slow/poison will go away
2460 poisoned = 0.0;
2461 slowed = 0.0;
2462 }
2463 if(opponent.u_.experience + u_.level >= opponent.u_.max_experience) {
2464 opponent.poisoned = 0.0;
2465 opponent.slowed = 0.0;
2466 }
2467
2468 untouched *= self_not_hit;
2469 opponent.untouched *= opp_not_hit;
2470 }
2471
average_hp(unsigned int healing) const2472 double combatant::average_hp(unsigned int healing) const
2473 {
2474 double total = 0;
2475
2476 // Since sum of probabilities is 1.0, we can just tally weights.
2477 for(unsigned int i = 1; i < hp_dist.size(); ++i) {
2478 total += hp_dist[i] * std::min<unsigned int>(i + healing, u_.max_hp);
2479 }
2480
2481 return total;
2482 }
2483
2484 /* ** The stand-alone program ** */
2485
2486 #if defined(BENCHMARK) || defined(CHECK)
2487 // We create a significant number of nasty-to-calculate units,
2488 // and test each one against the others.
2489 static const unsigned int NUM_UNITS = 50;
2490
2491 #ifdef ATTACK_PREDICTION_DEBUG
list_combatant(const battle_context_unit_stats & stats,unsigned fighter)2492 void list_combatant(const battle_context_unit_stats& stats, unsigned fighter)
2493 {
2494 std::ostringstream ss;
2495
2496 // TODO: swarm_max? not strikes?
2497 ss << "#" << fighter << ": " << stats.swarm_max << "-" << stats.damage << "; "
2498 << stats.chance_to_hit << "% chance to hit; ";
2499
2500 if(stats.drains) {
2501 ss << "drains, ";
2502 }
2503
2504 if(stats.slows) {
2505 ss << "slows, ";
2506 }
2507
2508 if(stats.rounds > 1) {
2509 ss << "berserk, ";
2510 }
2511
2512 if(stats.swarm) {
2513 ss << "swarm(" << stats.num_blows << "), ";
2514 }
2515
2516 if(stats.firststrike) {
2517 ss << "firststrike, ";
2518 }
2519
2520 ss << "max hp = " << stats.max_hp << "\n";
2521
2522 std::cout << ss.rdbuf() << std::endl;
2523 }
2524 #else
list_combatant(const battle_context_unit_stats &,unsigned)2525 void list_combatant(const battle_context_unit_stats&, unsigned)
2526 {
2527 }
2528 #endif
2529
2530 #ifdef HUMAN_READABLE
print(const char label[],unsigned int battle,unsigned int fighter) const2531 void combatant::print(const char label[], unsigned int battle, unsigned int fighter) const
2532 {
2533 std::ostringstream ss;
2534
2535 // TODO: add this to the stream... no idea how to convert it properly...
2536 printf("#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ", battle, fighter, label,
2537 static_cast<int>(strlen(label)) - 12, ':', u_.swarm_max, u_.damage, u_.hp, u_.chance_to_hit, untouched * 100.0);
2538
2539 if(u_.drains) {
2540 ss << "drains, ";
2541 }
2542
2543 if(u_.slows) {
2544 ss << "slows, ";
2545 }
2546
2547 if(u_.rounds > 1) {
2548 ss << "berserk, ";
2549 }
2550
2551 if(u_.swarm) {
2552 ss << "swarm, ";
2553 }
2554
2555 if(u_.firststrike) {
2556 ss << "firststrike, ";
2557 }
2558
2559 std::cout << ss.rdbuf() << std::endl;
2560
2561 // TODO: add to stream
2562 printf("maxhp=%zu ", hp_dist.size() - 1);
2563
2564 int num_outputs = 0;
2565 for(unsigned int i = 0; i < hp_dist.size(); ++i) {
2566 if(hp_dist[i] != 0.0) {
2567 if(num_outputs++ % 6 == 0) {
2568 printf("\n\t");
2569 } else {
2570 printf(" ");
2571 }
2572
2573 printf("%2u: %5.2f", i, hp_dist[i] * 100);
2574 }
2575 }
2576
2577 printf("\n");
2578 }
2579 #elif defined(CHECK)
print(const char label[],unsigned int battle,unsigned int) const2580 void combatant::print(const char label[], unsigned int battle, unsigned int /*fighter*/) const
2581 {
2582 std::ostringstream ss;
2583
2584 printf("#%u: %s: %d %u %u %2g%% ", battle, label, u_.damage, u_.swarm_max, u_.hp,
2585 static_cast<float>(u_.chance_to_hit));
2586
2587 if(u_.drains) {
2588 ss << "drains, ";
2589 }
2590
2591 if(u_.slows) {
2592 ss << "slows, ";
2593 }
2594
2595 if(u_.rounds > 1) {
2596 ss << "berserk, ";
2597 }
2598
2599 if(u_.swarm) {
2600 ss << "swarm, ";
2601 }
2602
2603 if(u_.firststrike) {
2604 ss << "firststrike, ";
2605 }
2606
2607 std::cout << ss.rdbuf() << std::endl;
2608
2609 // TODO: add to stream
2610 printf("maxhp=%zu ", hp_dist.size() - 1);
2611 printf(" %.2f", untouched);
2612 for(unsigned int i = 0; i < hp_dist.size(); ++i) {
2613 printf(" %.2f", hp_dist[i] * 100);
2614 }
2615
2616 printf("\n");
2617 }
2618 #else // ... BENCHMARK
print(const char[],unsigned int,unsigned int) const2619 void combatant::print(const char /*label*/[], unsigned int /*battle*/, unsigned int /*fighter*/) const
2620 {
2621 }
2622 #endif
2623
reset()2624 void combatant::reset()
2625 {
2626 for(unsigned int i = 0; i < hp_dist.size(); ++i) {
2627 hp_dist[i] = 0.0;
2628 }
2629
2630 untouched = 1.0;
2631 poisoned = u_.is_poisoned ? 1.0 : 0.0;
2632 slowed = u_.is_slowed ? 1.0 : 0.0;
2633 summary[0] = std::vector<double>();
2634 summary[1] = std::vector<double>();
2635 }
2636
run(unsigned specific_battle)2637 static void run(unsigned specific_battle)
2638 {
2639 using std::chrono::duration_cast;
2640 using std::chrono::microseconds;
2641
2642 // N^2 battles
2643 struct battle_context_unit_stats* stats[NUM_UNITS];
2644 struct combatant* u[NUM_UNITS];
2645 unsigned int i, j, k, battle = 0;
2646 std::chrono::high_resolution_clock::time_point start, end;
2647
2648 for(i = 0; i < NUM_UNITS; ++i) {
2649 unsigned alt = i + 74; // To offset some cycles.
2650 // To get somewhat realistic performance data, try to approximate
2651 // hit point ranges for mainline units (say 25-60 max hitpoints?)
2652 unsigned max_hp = (i * 2) % 23 + (i * 3) % 14 + 25;
2653 unsigned hp = (alt * 5) % max_hp + 1;
2654 stats[i] = new battle_context_unit_stats(alt % 8 + 2, // damage
2655 (alt % 19 + 3) / 4, // number of strikes
2656 hp, max_hp,
2657 (i % 6) * 10 + 30, // hit chance
2658 (i % 13) % 4 == 0, // drains
2659 (i % 11) % 3 == 0, // slows
2660 false, // slowed
2661 i % 7 == 0, // berserk
2662 (i % 17) / 2 == 0, // firststrike
2663 i % 5 == 0); // swarm
2664 u[i] = new combatant(*stats[i]);
2665 list_combatant(*stats[i], i + 1);
2666 }
2667
2668 start = std::chrono::high_resolution_clock::now();
2669 // Go through all fights with two attackers (j and k attacking i).
2670 for(i = 0; i < NUM_UNITS; ++i) {
2671 for(j = 0; j < NUM_UNITS; ++j) {
2672 if(i == j) {
2673 continue;
2674 }
2675
2676 for(k = 0; k < NUM_UNITS; ++k) {
2677 if(i == k || j == k) {
2678 continue;
2679 }
2680
2681 ++battle;
2682 if(specific_battle && battle != specific_battle) {
2683 continue;
2684 }
2685
2686 // Fight!
2687 u[j]->fight(*u[i]);
2688 u[k]->fight(*u[i]);
2689 // Results.
2690 u[i]->print("Defender", battle, i + 1);
2691 u[j]->print("Attacker #1", battle, j + 1);
2692 u[k]->print("Attacker #2", battle, k + 1);
2693 // Start the next fight fresh.
2694 u[i]->reset();
2695 u[j]->reset();
2696 u[k]->reset();
2697 }
2698 }
2699 }
2700
2701 end = std::chrono::high_resolution_clock::now();
2702
2703 auto total = end - start;
2704
2705 #ifdef BENCHMARK
2706 printf("Total time for %u combats was %lf\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2),
2707 static_cast<double>(duration_cast<microseconds>(total).count()) / 1000000.0);
2708 printf("Time per calc = %li us\n", static_cast<long>(duration_cast<microseconds>(total).count())
2709 / (NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2)));
2710 #else
2711 printf("Total combats: %u\n", NUM_UNITS * (NUM_UNITS - 1) * (NUM_UNITS - 2));
2712 #endif
2713
2714 for(i = 0; i < NUM_UNITS; ++i) {
2715 delete u[i];
2716 delete stats[i];
2717 }
2718
2719 exit(0);
2720 }
2721
parse_unit(char *** argv)2722 static battle_context_unit_stats* parse_unit(char*** argv)
2723 {
2724 // There are four required parameters.
2725 int add_to_argv = 4;
2726 int damage = atoi((*argv)[1]);
2727 int num_attacks = atoi((*argv)[2]);
2728 int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
2729 int hit_chance = atoi((*argv)[4]);
2730
2731 // Parse the optional (fifth) parameter.
2732 bool drains = false, slows = false, slowed = false, berserk = false, firststrike = false, swarm = false;
2733 if((*argv)[5] && atoi((*argv)[5]) == 0) {
2734 // Optional parameter is present.
2735 ++add_to_argv;
2736
2737 char* max = strstr((*argv)[5], "maxhp=");
2738 if(max) {
2739 max_hp = atoi(max + strlen("maxhp="));
2740 if(max_hp < hitpoints) {
2741 std::cerr << "maxhp must be at least hitpoints." << std::endl;
2742 exit(1);
2743 }
2744 }
2745
2746 if(strstr((*argv)[5], "drain")) {
2747 if(!max) {
2748 std::cerr << "WARNING: drain specified without maxhp; assuming uninjured." << std::endl;
2749 }
2750
2751 drains = true;
2752 }
2753
2754 if(strstr((*argv)[5], "slows")) {
2755 slows = true;
2756 }
2757
2758 if(strstr((*argv)[5], "slowed")) {
2759 slowed = true;
2760 }
2761
2762 if(strstr((*argv)[5], "berserk")) {
2763 berserk = true;
2764 }
2765
2766 if(strstr((*argv)[5], "firststrike")) {
2767 firststrike = true;
2768 }
2769
2770 if(strstr((*argv)[5], "swarm")) {
2771 if(!max) {
2772 std::cerr << "WARNING: swarm specified without maxhp; assuming uninjured." << std::endl;
2773 }
2774
2775 swarm = true;
2776 }
2777 }
2778
2779 // Update argv.
2780 *argv += add_to_argv;
2781
2782 // Construct the stats and return.
2783 return new battle_context_unit_stats(
2784 damage, num_attacks, hitpoints, max_hp, hit_chance, drains, slows, slowed, berserk, firststrike, swarm);
2785 }
2786
main(int argc,char * argv[])2787 int main(int argc, char* argv[])
2788 {
2789 battle_context_unit_stats *def_stats, *att_stats[20];
2790 combatant *def, *att[20];
2791 unsigned int i;
2792
2793 if(argc < 3)
2794 run(argv[1] ? atoi(argv[1]) : 0);
2795
2796 if(argc < 9) {
2797 std::cerr
2798 << "Usage: " << argv[0] << " [<battle>]\n\t" << argv[0] << " "
2799 << "<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] "
2800 << "<damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ..."
2801 << std::endl;
2802 exit(1);
2803 }
2804
2805 def_stats = parse_unit(&argv);
2806 def = new combatant(*def_stats);
2807 for(i = 0; argv[1] && i < 19; ++i) {
2808 att_stats[i] = parse_unit(&argv);
2809 att[i] = new combatant(*att_stats[i]);
2810 }
2811
2812 att[i] = nullptr;
2813
2814 for(i = 0; att[i]; ++i) {
2815 debug(("Fighting next attacker\n"));
2816 att[i]->fight(*def);
2817 }
2818
2819 def->print("Defender", 0, 0);
2820 for(i = 0; att[i]; ++i) {
2821 att[i]->print("Attacker", 0, i + 1);
2822 }
2823
2824 for(i = 0; att[i]; ++i) {
2825 delete att[i];
2826 delete att_stats[i];
2827 }
2828
2829 delete def;
2830 delete def_stats;
2831
2832 return 0;
2833 }
2834
2835 #endif // Standalone program
2836