The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
attack_prediction.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2016 by Rusty Russell <[email protected]>
3  Part of the Battle for Wesnoth Project http://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  This code has lots of debugging. It is there for a reason:
17  this code is kinda tricky. Do not remove it.
18 */
19 
20 /**
21  * @file
22  * Simulate combat to calculate attacks.
23  * This can be compiled as a stand-alone program to either verify
24  * correctness or to benchmark performance.
25  *
26  * Compile with -O3 -DBENCHMARK for speed testing, and with -DCHECK for
27  * testing correctness (redirect the output to a file, then compile
28  * utils/wesnoth-attack-sim.c and run that with the arguments
29  * --check <file name>).
30  * For either option, use -DHUMAN_READABLE if you want to see the results
31  * from each combat displayed in a prettier format (but no longer suitable
32  * for wesnoth-attack-sim.c).
33  */
34 
35 #include <cfloat>
36 
37 #include "attack_prediction.hpp"
38 
39 #include "actions/attack.hpp"
40 #include "game_config.hpp"
41 #include <array>
42 
43 #if defined(BENCHMARK) || defined(CHECK)
44 #include <time.h>
45 #include <sys/time.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 
49 // Set some default values so this file can stand alone.
50 namespace game_config {
51  int kill_experience = 8;
52  int tile_size = 72; // Not really needed, but it's used in image.hpp.
53 }
54 #endif
55 
56 #ifdef ATTACK_PREDICTION_DEBUG
57 #define debug(x) printf x
58 #else
59 #define debug(x)
60 #endif
61 
62 #ifdef ATTACK_PREDICTION_DEBUG
63 namespace {
64  /** Dumps the statistics of a unit on stdout. Remove it eventually. */
65  // Moved from attack/attack.cpp
66  void dump(const battle_context_unit_stats & stats)
67  {
68  printf("==================================\n");
69  printf("is_attacker: %d\n", static_cast<int>(stats.is_attacker));
70  printf("is_poisoned: %d\n", static_cast<int>(stats.is_poisoned));
71  printf("is_slowed: %d\n", static_cast<int>(stats.is_slowed));
72  printf("slows: %d\n", static_cast<int>(stats.slows));
73  printf("drains: %d\n", static_cast<int>(stats.drains));
74  printf("petrifies: %d\n", static_cast<int>(stats.petrifies));
75  printf("poisons: %d\n", static_cast<int>(stats.poisons));
76  printf("backstab_pos: %d\n", static_cast<int>(stats.backstab_pos));
77  printf("swarm: %d\n", static_cast<int>(stats.swarm));
78  printf("rounds: %u\n", stats.rounds);
79  printf("firststrike: %d\n", static_cast<int>(stats.firststrike));
80  printf("\n");
81  printf("hp: %u\n", stats.hp);
82  printf("max_hp: %u\n", stats.max_hp);
83  printf("chance_to_hit: %u\n", stats.chance_to_hit);
84  printf("damage: %d\n", stats.damage);
85  printf("slow_damage: %d\n", stats.slow_damage);
86  printf("drain_percent: %d\n", stats.drain_percent);
87  printf("drain_constant: %d\n", stats.drain_constant);
88  printf("num_blows: %u\n", stats.num_blows);
89  printf("swarm_min: %u\n", stats.swarm_min);
90  printf("swarm_max: %u\n", stats.swarm_max);
91  printf("\n");
92  }
93 }
94 #endif
95 
96 namespace
97 {
98 
99 /**
100  * A shorthand utility for forcing a value between minimum and maximum values.
101  */
102 template<typename T>
103 const T& limit(const T& val, const T& min, const T& max)
104 {
105  if ( val < min )
106  return min;
107  if ( val > max )
108  return max;
109  return val;
110 }
111 
112 /**
113  * A matrix of A's hitpoints vs B's hitpoints vs. their slowed states.
114  * This class is concerned only with the matrix implementation and
115  * implements functionality for shifting and retrieving probabilities
116  * (i.e. low-level stuff).
117  */
118 class prob_matrix
119 {
120  // Since this gets used very often (especially by the AI), it has
121  // been optimized for speed as a sparse matrix.
122 public:
123  prob_matrix(unsigned int a_max, unsigned int b_max,
124  bool need_a_slowed, bool need_b_slowed,
125  unsigned int a_cur, unsigned int b_cur,
126  const std::vector<double> a_initial[2],
127  const std::vector<double> b_initial[2]);
128 
129  ~prob_matrix();
130 
131  // Shift columns on this plane (b taking damage).
132  void shift_cols(unsigned dst, unsigned src, unsigned damage,
133  double prob, int drain_constant, int drain_percent);
134  // Shift rows on this plane (a taking damage).
135  void shift_rows(unsigned dst, unsigned src, unsigned damage,
136  double prob, int drain_constant, int drain_percent);
137 
138  /// Move a column (adding it to the destination).
139  void move_column(unsigned d_plane, unsigned s_plane,
140  unsigned d_col, unsigned s_col);
141  /// Move a row (adding it to the destination).
142  void move_row(unsigned d_plane, unsigned s_plane,
143  unsigned d_row, unsigned s_row);
144 
145  // Move values within a row (or column) to a specified column (or row).
146  void merge_col(unsigned d_plane, unsigned s_plane, unsigned col, unsigned d_row);
147  void merge_cols(unsigned d_plane, unsigned s_plane, unsigned d_row);
148  void merge_row(unsigned d_plane, unsigned s_plane, unsigned row, unsigned d_col);
149  void merge_rows(unsigned d_plane, unsigned s_plane, unsigned d_col);
150 
151  /// What is the chance that an indicated combatant (one of them) is at zero?
152  double prob_of_zero(bool check_a, bool check_b) const;
153  /// Sums the values in the specified plane.
154  void sum(unsigned plane, std::vector<double> & row_sums,
155  std::vector<double> & col_sums) const;
156 
157  /// Returns true if the specified plane might have data in it.
158  bool plane_used(unsigned p) const { return p < NUM_PLANES && plane_[p] != nullptr; }
159 
160  unsigned int num_rows() const { return rows_; }
161  unsigned int num_cols() const { return cols_; }
162 
163  // Debugging tool.
164  void dump() const;
165 
166  // We need four matrices, or "planes", reflecting the possible
167  // "slowed" states (neither slowed, A slowed, B slowed, both slowed).
168  enum {
169  NEITHER_SLOWED,
170  A_SLOWED,
171  B_SLOWED,
172  BOTH_SLOWED,
173  NUM_PLANES // Symbolic constant for the number of planes.
174  };
175 
176 private:
177  // This gives me 10% speed improvement over std::vector<> (g++4.0.3 x86)
178  double *new_plane();
179 
180  void initialize_plane(unsigned plane, unsigned a_cur, unsigned b_cur,
181  const std::vector<double> & a_initial,
182  const std::vector<double> & b_initial);
183  void initialize_row(unsigned plane, unsigned row, double row_prob,
184  unsigned b_cur, const std::vector<double> & b_initial);
185 
186  double &val(unsigned plane, unsigned row, unsigned col);
187  const double &val(unsigned plane, unsigned row, unsigned col) const;
188 
189  /// Transfers a portion (value * prob) of one value in the matrix to another.
190  void xfer(unsigned dst_plane, unsigned src_plane,
191  unsigned row_dst, unsigned col_dst,
192  unsigned row_src, unsigned col_src,
193  double prob);
194  /// Transfers one value in the matrix to another.
195  void xfer(unsigned dst_plane, unsigned src_plane,
196  unsigned row_dst, unsigned col_dst,
197  unsigned row_src, unsigned col_src);
198 
199  void shift_cols_in_row(unsigned dst, unsigned src, unsigned row,
200  const std::vector<unsigned> & cols,
201  unsigned damage, double prob, int drainmax,
202  int drain_constant, int drain_percent);
203  void shift_rows_in_col(unsigned dst, unsigned src, unsigned col,
204  const std::vector<unsigned> & rows,
205  unsigned damage, double prob, int drainmax,
206  int drain_constant, int drain_percent);
207 
208 private: // data
209  const unsigned int rows_, cols_;
210  std::array<double *, NUM_PLANES> plane_;
211 
212  // For optimization, we keep track of the rows and columns with data.
213  // (The matrices are likely going to be rather sparse, with data on a grid.)
214  std::array<std::set<unsigned>, NUM_PLANES> used_rows_, used_cols_;
215 };
216 
217 
218 /**
219  * Constructor.
220  * @param a_max The maximum value we will track for A.
221  * @param b_max The maximum value we will track for B.
222  * @param need_a_slowed Set to true if there might be transfers to a "slow" plane for A.
223  * @param need_b_slowed Set to true if there might be transfers to a "slow" plane for B.
224  * @param a_cur The current value for A. (Ignored if a_initial[0] is not empty.)
225  * @param b_cur The current value for B. (Ignored if b_initial[0] is not empty.)
226  * @param a_initial The initial distribution of values for A. Element [0] is for normal A. while [1] is for slowed A.
227  * @param b_initial The initial distribution of values for B. Element [0] is for normal B. while [1] is for slowed B.
228  */
229 prob_matrix::prob_matrix(unsigned int a_max, unsigned int b_max,
230  bool need_a_slowed, bool need_b_slowed,
231  unsigned int a_cur, unsigned int b_cur,
232  const std::vector<double> a_initial[2],
233  const std::vector<double> b_initial[2])
234  : rows_(a_max+1)
235  , cols_(b_max+1)
236  , plane_()
237  , used_rows_()
238  , used_cols_()
239 {
240  // Make sure we do not access the matrix in invalid positions.
241  a_cur = std::min<unsigned int>(a_cur, rows_ - 1);
242  b_cur = std::min<unsigned int>(b_cur, cols_ - 1);
243 
244  // It will be convenient to always consider row/col 0 to be used.
245  for ( unsigned plane = 0; plane != NUM_PLANES; ++plane ) {
246  used_rows_[plane].insert(0u);
247  used_cols_[plane].insert(0u);
248  }
249 
250  // We will need slowed planes if the initial vectors have them.
251  need_a_slowed = need_a_slowed || !a_initial[1].empty();
252  need_b_slowed = need_b_slowed || !b_initial[1].empty();
253 
254  // Allocate the needed planes.
255  plane_[NEITHER_SLOWED] = new_plane();
256  plane_[A_SLOWED] = !need_a_slowed ? nullptr : new_plane();
257  plane_[B_SLOWED] = !need_b_slowed ? nullptr : new_plane();
258  plane_[BOTH_SLOWED] = !(need_a_slowed && need_b_slowed) ? nullptr : new_plane();
259 
260  // Initialize the probability distribution.
261  initialize_plane(NEITHER_SLOWED, a_cur, b_cur, a_initial[0], b_initial[0]);
262  if ( !a_initial[1].empty() )
263  initialize_plane(A_SLOWED, a_cur, b_cur, a_initial[1], b_initial[0]);
264  if ( !b_initial[1].empty() )
265  initialize_plane(B_SLOWED, a_cur, b_cur, a_initial[0], b_initial[1]);
266  if ( !a_initial[1].empty() && !b_initial[1].empty() )
267  // Currently will not happen, but to be complete...
268  initialize_plane(BOTH_SLOWED, a_cur, b_cur, a_initial[1], b_initial[1]);
269 
270  // Some debugging messages.
271  if ( !a_initial[0].empty() ) {
272  debug(("A has fought before.\n"));
273  dump();
274  }
275  else if ( !b_initial[0].empty() ) {
276  debug(("B has fought before.\n"));
277  dump();
278  }
279 }
280 
281 prob_matrix::~prob_matrix()
282 {
283  delete[] plane_[NEITHER_SLOWED];
284  delete[] plane_[A_SLOWED];
285  delete[] plane_[B_SLOWED];
286  delete[] plane_[BOTH_SLOWED];
287 }
288 
289 /** Allocate a new probability array, initialized to 0. */
290 double *prob_matrix::new_plane()
291 {
292  unsigned int size = rows_ * cols_;
293  double *arr = new double[size];
294  memset(arr, 0, sizeof(double) * size);
295  return arr;
296 }
297 
298 /**
299  * Fills the indicated plane with its initial (non-zero) values.
300  * (Part of construction)
301  * @param plane The plane to initialize.
302  * @param a_cur The current value for A. (Ignored if a_initial is not empty.)
303  * @param b_cur The current value for B. (Ignored if b_initial is not empty.)
304  * @param a_initial The initial distribution of values for A for this plane.
305  * @param b_initial The initial distribution of values for B for this plane.
306  */
307 void prob_matrix::initialize_plane(unsigned plane, unsigned a_cur, unsigned b_cur,
308  const std::vector<double> & a_initial,
309  const std::vector<double> & b_initial)
310 {
311  if ( !a_initial.empty() ) {
312  unsigned row_count = std::min<unsigned>(a_initial.size(), rows_);
313  // The probabilities for each row are contained in a_initial.
314  for ( unsigned row = 0; row < row_count; ++row ) {
315  if ( a_initial[row] != 0.0 ) {
316  used_rows_[plane].insert(row);
317  initialize_row(plane, row, a_initial[row], b_cur, b_initial);
318  }
319  }
320  }
321  else {
322  used_rows_[plane].insert(a_cur);
323  // Only the row indicated by a_cur is a possibility.
324  initialize_row(plane, a_cur, 1.0, b_cur, b_initial);
325  }
326 }
327 
328 /**
329  * Fills the indicated row with its initial (non-zero) values.
330  * (Part of construction)
331  * @param plane The plane containing the row to initialize.
332  * @param row The row to initialize.
333  * @param row_prob The probability of A having the value for this row.
334  * @param b_cur The current value for B. (Ignored if b_initial is not empty.)
335  * @param b_initial The initial distribution of values for B for this plane.
336  */
337 void prob_matrix::initialize_row(unsigned plane, unsigned row, double row_prob,
338  unsigned b_cur, const std::vector<double> & b_initial)
339 {
340  if ( !b_initial.empty() ) {
341  unsigned col_count = std::min<unsigned>(b_initial.size(), cols_);
342  // The probabilities for each column are contained in b_initial.
343  for ( unsigned col = 0; col < col_count; ++col ) {
344  if ( b_initial[col] != 0.0 ) {
345  used_cols_[plane].insert(col);
346  val(plane, row, col) = row_prob * b_initial[col];
347  }
348  }
349  }
350  else {
351  // Only the column indicated by b_cur is a possibility.
352  used_cols_[plane].insert(b_cur);
353  val(plane, row, b_cur) = row_prob;
354  }
355 }
356 
357 double &prob_matrix::val(unsigned p, unsigned row, unsigned col)
358 {
359  assert(row < rows_);
360  assert(col < cols_);
361  return plane_[p][row * cols_ + col];
362 }
363 
364 const double &prob_matrix::val(unsigned p, unsigned row, unsigned col) const
365 {
366  assert(row < rows_);
367  assert(col < cols_);
368  return plane_[p][row * cols_ + col];
369 }
370 
371 // xfer, shift_cols and shift_rows use up most of our time. Careful!
372 /**
373  * Transfers a portion (value * prob) of one value in the matrix to another.
374  */
375 void prob_matrix::xfer(unsigned dst_plane, unsigned src_plane,
376  unsigned row_dst, unsigned col_dst,
377  unsigned row_src, unsigned col_src,
378  double prob)
379 {
380  double &src = val(src_plane, row_src, col_src);
381  if (src != 0.0) {
382  double diff = src * prob;
383  src -= diff;
384 
385  double &dst = val(dst_plane, row_dst, col_dst);
386  if ( dst == 0.0 ) {
387  // Track that this entry is now used.
388  used_rows_[dst_plane].insert(row_dst);
389  used_cols_[dst_plane].insert(col_dst);
390  }
391  dst += diff;
392 
393  debug(("Shifted %4.3g from %s(%u,%u) to %s(%u,%u).\n",
394  diff, src_plane == NEITHER_SLOWED ? ""
395  : src_plane == A_SLOWED ? "[A_SLOWED]"
396  : src_plane == B_SLOWED ? "[B_SLOWED]"
397  : src_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
398  row_src, col_src,
399  dst_plane == NEITHER_SLOWED ? ""
400  : dst_plane == A_SLOWED ? "[A_SLOWED]"
401  : dst_plane == B_SLOWED ? "[B_SLOWED]"
402  : dst_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
403  row_dst, col_dst));
404  }
405 }
406 
407 /**
408  * Transfers one value in the matrix to another.
409  */
410 void prob_matrix::xfer(unsigned dst_plane, unsigned src_plane,
411  unsigned row_dst, unsigned col_dst,
412  unsigned row_src, unsigned col_src)
413 {
414  if ( dst_plane == src_plane && row_dst == row_src && col_dst == col_src )
415  // Transferring to itself. Nothing to do.
416  return;
417 
418  double &src = val(src_plane, row_src, col_src);
419  if ( src != 0.0 ) {
420  debug(("Shifting %4.3g from %s(%u,%u) to %s(%u,%u).\n",
421  src, src_plane == NEITHER_SLOWED ? ""
422  : src_plane == A_SLOWED ? "[A_SLOWED]"
423  : src_plane == B_SLOWED ? "[B_SLOWED]"
424  : src_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
425  row_src, col_src,
426  dst_plane == NEITHER_SLOWED ? ""
427  : dst_plane == A_SLOWED ? "[A_SLOWED]"
428  : dst_plane == B_SLOWED ? "[B_SLOWED]"
429  : dst_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
430  row_dst, col_dst));
431 
432  double &dst = val(dst_plane, row_dst, col_dst);
433  if ( dst == 0.0 ) {
434  // Track that this entry is now used.
435  used_rows_[dst_plane].insert(row_dst);
436  used_cols_[dst_plane].insert(col_dst);
437  }
438  dst += src;
439  src = 0.0;
440  }
441 }
442 
443 /**
444  * Transfers a portion (value * prob) of the values in a row to another.
445  * Part of shift_cols().
446  */
447 void prob_matrix::shift_cols_in_row(unsigned dst, unsigned src, unsigned row,
448  const std::vector<unsigned> & cols,
449  unsigned damage, double prob, int drainmax,
450  int drain_constant, int drain_percent)
451 {
452  // Some conversions to (signed) int.
453  int row_i = static_cast<int>(row);
454  int max_row = static_cast<int>(rows_) - 1;
455 
456  // cols[0] is excluded since that should be 0, representing already dead.
457  unsigned col_x = 1;
458 
459  // Killing blows can have different drain amounts, so handle them first
460  for ( ; col_x < cols.size() && cols[col_x] < damage; ++col_x ) {
461  // These variables are not strictly necessary, but they make the
462  // calculation easier to parse.
463  int col_i = static_cast<int>(cols[col_x]);
464  int drain_amount = col_i*drain_percent/100 + drain_constant;
465  unsigned newrow = limit<int>(row_i + drain_amount, 1, max_row);
466  xfer(dst, src, newrow, 0, row, cols[col_x], prob);
467  }
468 
469  // The remaining columns use the specified drainmax.
470  unsigned newrow = limit<int>(row_i + drainmax, 1, max_row);
471  for ( ; col_x < cols.size(); ++col_x )
472  xfer(dst, src, newrow, cols[col_x] - damage, row, cols[col_x], prob);
473 }
474 
475 /**
476  * Transfers a portion (value * prob) of each column in a plane to another.
477  * Each column in the @a src plane gets shifted @a damage columns towards 0, and
478  * also shifted into the @a dst plane. In addition, the rows can shift if
479  * @a drain constant or @a drain_percent is nonzero.
480  */
481 void prob_matrix::shift_cols(unsigned dst, unsigned src, unsigned damage,
482  double prob, int drain_constant, int drain_percent)
483 {
484  int drainmax = (drain_percent*(static_cast<signed>(damage))/100+drain_constant);
485 
486  if(drain_constant || drain_percent) {
487  debug(("Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
488  }
489 
490  // Get lists of indices currently used in the source plane.
491  // (This needs to be cached since we might add indices while shifting.)
492  const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
493  const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
494 
495  // Loop downwards if we drain positive, but upwards if we drain negative,
496  // so we write behind us (for when src == dst).
497  if(drainmax > 0) {
498  // rows[0] is excluded since that should be 0, representing already dead.
499  for ( unsigned row_x = rows.size()-1; row_x != 0; --row_x )
500  shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax,
501  drain_constant, drain_percent);
502  } else {
503  // rows[0] is excluded since that should be 0, representing already dead.
504  for ( unsigned row_x = 1; row_x != rows.size(); ++row_x )
505  shift_cols_in_row(dst, src, rows[row_x], cols, damage, prob, drainmax,
506  drain_constant, drain_percent);
507  }
508 }
509 
510 /**
511  * Transfers a portion (value * prob) of the values in a column to another.
512  * Part of shift_rows().
513  */
514 void prob_matrix::shift_rows_in_col(unsigned dst, unsigned src, unsigned col,
515  const std::vector<unsigned> & rows,
516  unsigned damage, double prob, int drainmax,
517  int drain_constant, int drain_percent)
518 {
519  // Some conversions to (signed) int.
520  int col_i = static_cast<int>(col);
521  int max_col = static_cast<int>(cols_) - 1;
522 
523  // rows[0] is excluded since that should be 0, representing already dead.
524  unsigned row_x = 1;
525 
526  // Killing blows can have different drain amounts, so handle them first
527  for ( ; row_x < rows.size() && rows[row_x] < damage; ++row_x ) {
528  // These variables are not strictly necessary, but they make the
529  // calculation easier to parse.
530  int row_i = static_cast<int>(rows[row_x]);
531  int drain_amount = row_i*drain_percent/100 + drain_constant;
532  unsigned newcol = limit<int>(col_i + drain_amount, 1, max_col);
533  xfer(dst, src, 0, newcol, rows[row_x], col, prob);
534  }
535 
536  // The remaining rows use the specified drainmax.
537  unsigned newcol = limit<int>(col_i + drainmax, 1, max_col);
538  for ( ; row_x < rows.size(); ++row_x )
539  xfer(dst, src, rows[row_x] - damage, newcol, rows[row_x], col, prob);
540 }
541 
542 /**
543  * Transfers a portion (value * prob) of each row in a plane to another.
544  * Each row in the @a src plane gets shifted @a damage columns towards 0, and
545  * also shifted into the @a dst plane. In addition, the columns can shift if
546  * @a drain constant or @a drain_percent is nonzero.
547  */
548 void prob_matrix::shift_rows(unsigned dst, unsigned src, unsigned damage,
549  double prob, int drain_constant, int drain_percent)
550 {
551  int drainmax = (drain_percent*(static_cast<signed>(damage))/100+drain_constant);
552 
553  if(drain_constant || drain_percent) {
554  debug(("Drains %i (%i%% of %u plus %i)\n", drainmax, drain_percent, damage, drain_constant));
555  }
556 
557  // Get lists of indices currently used in the source plane.
558  // (This needs to be cached since we might add indices while shifting.)
559  const std::vector<unsigned> rows(used_rows_[src].begin(), used_rows_[src].end());
560  const std::vector<unsigned> cols(used_cols_[src].begin(), used_cols_[src].end());
561 
562  // Loop downwards if we drain positive, but upwards if we drain negative,
563  // so we write behind us (for when src == dst).
564  if(drainmax > 0) {
565  // cols[0] is excluded since that should be 0, representing already dead.
566  for ( unsigned col_x = cols.size()-1; col_x != 0; --col_x )
567  shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax,
568  drain_constant, drain_percent);
569  } else {
570  // cols[0] is excluded since that should be 0, representing already dead.
571  for ( unsigned col_x = 1; col_x != cols.size(); ++col_x )
572  shift_rows_in_col(dst, src, cols[col_x], rows, damage, prob, drainmax,
573  drain_constant, drain_percent);
574  }
575 }
576 
577 /**
578  * Move a column (adding it to the destination).
579  */
580 void prob_matrix::move_column(unsigned d_plane, unsigned s_plane,
581  unsigned d_col, unsigned s_col)
582 {
583  std::set<unsigned>::const_iterator rows_end = used_rows_[s_plane].end();
584  std::set<unsigned>::const_iterator row_it = used_rows_[s_plane].begin();
585 
586  // Transfer the data.
587  for ( ; row_it != rows_end; ++row_it )
588  xfer(d_plane, s_plane, *row_it, d_col, *row_it, s_col);
589 }
590 
591 /**
592  * Move a row (adding it to the destination).
593  */
594 void prob_matrix::move_row(unsigned d_plane, unsigned s_plane,
595  unsigned d_row, unsigned s_row)
596 {
597  std::set<unsigned>::const_iterator cols_end = used_cols_[s_plane].end();
598  std::set<unsigned>::const_iterator col_it = used_cols_[s_plane].begin();
599 
600  // Transfer the data.
601  for ( ; col_it != cols_end; ++col_it )
602  xfer(d_plane, s_plane, d_row, *col_it, s_row, *col_it);
603 }
604 
605 /**
606  * Move values in the specified column -- excluding row zero -- to the
607  * specified row in that column (possibly shifting planes in the process).
608  */
609 void prob_matrix::merge_col(unsigned d_plane, unsigned s_plane, unsigned col,
610  unsigned d_row)
611 {
612  std::set<unsigned>::const_iterator rows_end = used_rows_[s_plane].end();
613  std::set<unsigned>::const_iterator row_it = used_rows_[s_plane].begin();
614 
615  // Transfer the data, excluding row zero.
616  for ( ++row_it; row_it != rows_end; ++row_it )
617  xfer(d_plane, s_plane, d_row, col, *row_it, col);
618 }
619 
620 /**
621  * Move values within columns in the specified plane -- excluding row zero --
622  * to the specified row (possibly shifting planes in the process).
623  */
624 void prob_matrix::merge_cols(unsigned d_plane, unsigned s_plane, unsigned d_row)
625 {
626  std::set<unsigned>::const_iterator rows_end = used_rows_[s_plane].end();
627  std::set<unsigned>::const_iterator row_it = used_rows_[s_plane].begin();
628  std::set<unsigned>::const_iterator cols_end = used_cols_[s_plane].end();
629  std::set<unsigned>::const_iterator cols_begin = used_cols_[s_plane].begin();
630  std::set<unsigned>::const_iterator col_it;
631 
632  // Transfer the data, excluding row zero.
633  for ( ++row_it; row_it != rows_end; ++row_it )
634  for ( col_it = cols_begin; col_it != cols_end; ++col_it )
635  xfer(d_plane, s_plane, d_row, *col_it, *row_it, *col_it);
636 }
637 
638 /**
639  * Move values in the specified row -- excluding column zero -- to the
640  * specified column in that row (possibly shifting planes in the process).
641  */
642 void prob_matrix::merge_row(unsigned d_plane, unsigned s_plane, unsigned row,
643  unsigned d_col)
644 {
645  std::set<unsigned>::const_iterator cols_end = used_cols_[s_plane].end();
646  std::set<unsigned>::const_iterator col_it = used_cols_[s_plane].begin();
647 
648  // Transfer the data, excluding column zero.
649  for ( ++col_it; col_it != cols_end; ++col_it )
650  xfer(d_plane, s_plane, row, d_col, row, *col_it);
651 }
652 
653 
654 /**
655  * Move values within rows in the specified plane -- excluding column zero --
656  * to the specified column (possibly shifting planes in the process).
657  */
658 void prob_matrix::merge_rows(unsigned d_plane, unsigned s_plane, unsigned d_col)
659 {
660  std::set<unsigned>::const_iterator rows_end = used_rows_[s_plane].end();
661  std::set<unsigned>::const_iterator row_it = used_rows_[s_plane].begin();
662  std::set<unsigned>::const_iterator cols_end = used_cols_[s_plane].end();
663  std::set<unsigned>::const_iterator cols_begin = used_cols_[s_plane].begin();
664  // (excluding column zero)
665  ++cols_begin;
666  std::set<unsigned>::const_iterator col_it;
667 
668  // Transfer the data, excluding column zero.
669  for ( ; row_it != rows_end; ++row_it )
670  for ( col_it = cols_begin; col_it != cols_end; ++col_it )
671  xfer(d_plane, s_plane, *row_it, d_col, *row_it, *col_it);
672 }
673 
674 /**
675  * What is the chance that an indicated combatant (one of them) is at zero?
676  */
677 double prob_matrix::prob_of_zero(bool check_a, bool check_b) const
678 {
679  double prob = 0.0;
680 
681  for (unsigned p = 0; p < NUM_PLANES; ++p) {
682  if ( !plane_used(p) )
683  continue;
684 
685  // Column 0 is where b is at zero.
686  if ( check_b ) {
687  std::set<unsigned>::const_iterator rows_end = used_rows_[p].end();
688  std::set<unsigned>::const_iterator row_it = used_rows_[p].begin();
689  for ( ; row_it != rows_end; ++row_it )
690  prob += val(p, *row_it, 0);
691  }
692  // Row 0 is where a is at zero.
693  if ( check_a ) {
694  std::set<unsigned>::const_iterator cols_end = used_cols_[p].end();
695  std::set<unsigned>::const_iterator col_it = used_cols_[p].begin();
696  for ( ; col_it != cols_end; ++col_it )
697  prob += val(p, 0, *col_it);
698  }
699  // Theoretically, if checking both, we should subtract the chance that
700  // both are dead, but that chance is zero, so don't worry about it.
701  }
702  return prob;
703 }
704 
705 /**
706  * Sums the values in the specified plane.
707  * The sum of each row is added to the corresponding entry in row_sums.
708  * The sum of each column is added to the corresponding entry in col_sums.
709  */
710 void prob_matrix::sum(unsigned plane, std::vector<double> & row_sums,
711  std::vector<double> & col_sums) const
712 {
713  std::set<unsigned>::const_iterator rows_end = used_rows_[plane].end();
714  std::set<unsigned>::const_iterator row_it = used_rows_[plane].begin();
715  std::set<unsigned>::const_iterator cols_end = used_cols_[plane].end();
716  std::set<unsigned>::const_iterator cols_begin = used_cols_[plane].begin();
717  std::set<unsigned>::const_iterator col_it;
718 
719  for ( ; row_it != rows_end; ++row_it )
720  for ( col_it = cols_begin; col_it != cols_end; ++col_it ) {
721  const double & prob = val(plane, *row_it, *col_it);
722  row_sums[*row_it] += prob;
723  col_sums[*col_it] += prob;
724  }
725 }
726 
727 #if defined(CHECK) && defined(ATTACK_PREDICTION_DEBUG)
728 void prob_matrix::dump() const
729 {
730  unsigned int row, col, m;
731  const char *names[]
732  = { "NEITHER_SLOWED", "A_SLOWED", "B_SLOWED", "BOTH_SLOWED" };
733 
734  for (m = 0; m < NUM_PLANES; ++m) {
735  if ( !plane_used(m) )
736  continue;
737  debug(("%s:\n", names[m]));
738  for (row = 0; row < rows_; ++row) {
739  debug((" "));
740  for (col = 0; col < cols_; ++col)
741  debug(("%4.3g ", val(m, row, col)*100));
742  debug(("\n"));
743  }
744  }
745 }
746 #else
747 void prob_matrix::dump() const
748 {
749 }
750 #endif
751 
752 
753 /**
754  * A matrix for calculating the outcome of combat.
755  * This class is concerned with translating things that happen in combat
756  * to matrix operations that then get passed on to the (private) matrix
757  * implementation.
758  */
759 class combat_matrix : private prob_matrix
760 {
761 public:
762  combat_matrix(unsigned int a_max_hp, unsigned int b_max_hp,
763  unsigned int a_hp, unsigned int b_hp,
764  const std::vector<double> a_summary[2],
765  const std::vector<double> b_summary[2],
766  bool a_slows, bool b_slows,
767  unsigned int a_damage, unsigned int b_damage,
768  unsigned int a_slow_damage, unsigned int b_slow_damage,
769  int a_drain_percent, int b_drain_percent,
770  int a_drain_constant, int b_drain_constant);
771 
772  ~combat_matrix() {}
773 
774  // A hits B.
775  void receive_blow_b(double hit_chance);
776  // B hits A. Why can't they just get along?
777  void receive_blow_a(double hit_chance);
778 
779  // We lied: actually did less damage, adjust matrix.
780  void remove_petrify_distortion_a(unsigned damage, unsigned slow_damage, unsigned b_hp);
781  void remove_petrify_distortion_b(unsigned damage, unsigned slow_damage, unsigned a_hp);
782 
783  void forced_levelup_a();
784  void conditional_levelup_a();
785 
786  void forced_levelup_b();
787  void conditional_levelup_b();
788 
789  // Its over, and here's the bill.
790  void extract_results(std::vector<double> summary_a[2],
791  std::vector<double> summary_b[2]);
792 
793  /// What is the chance that one of the combatants is dead?
794  double dead_prob() const { return prob_of_zero(true, true); }
795  /// What is the chance that combatant 'a' is dead?
796  double dead_prob_a() const { return prob_of_zero(true, false); }
797  /// What is the chance that combatant 'b' is dead?
798  double dead_prob_b() const { return prob_of_zero(false, true); }
799 
800  void dump() const { prob_matrix::dump(); }
801 
802 private:
803  unsigned a_max_hp_;
804  bool a_slows_;
805  unsigned a_damage_;
806  unsigned a_slow_damage_;
807  int a_drain_percent_;
808  int a_drain_constant_;
809 
810  unsigned b_max_hp_;
811  bool b_slows_;
812  unsigned b_damage_;
813  unsigned b_slow_damage_;
814  int b_drain_percent_;
815  int b_drain_constant_;
816 };
817 
818 
819 /**
820  * Constructor.
821  * @param a_max_hp The maximum hit points for A.
822  * @param b_max_hp The maximum hit points for B.
823  * @param a_slows Set to true if A slows B when A hits B.
824  * @param b_slows Set to true if B slows A when B hits A.
825  * @param a_hp The current hit points for A. (Ignored if a_summary[0] is not empty.)
826  * @param b_hp The current hit points for B. (Ignored if b_summary[0] is not empty.)
827  * @param a_summary The hit point distribution for A (from previous combats). Element [0] is for normal A. while [1] is for slowed A.
828  * @param b_summary The hit point distribution for B (from previous combats). Element [0] is for normal B. while [1] is for slowed B.
829  */
830 combat_matrix::combat_matrix(unsigned int a_max_hp, unsigned int b_max_hp,
831  unsigned int a_hp, unsigned int b_hp,
832  const std::vector<double> a_summary[2],
833  const std::vector<double> b_summary[2],
834  bool a_slows, bool b_slows,
835  unsigned int a_damage, unsigned int b_damage,
836  unsigned int a_slow_damage, unsigned int b_slow_damage,
837  int a_drain_percent, int b_drain_percent,
838  int a_drain_constant, int b_drain_constant)
839  // The inversion of the order of the *_slows parameters here is intentional.
840  : prob_matrix(a_max_hp, b_max_hp, b_slows, a_slows, a_hp, b_hp, a_summary, b_summary),
841 
842  a_max_hp_(a_max_hp), a_slows_(a_slows), a_damage_(a_damage), a_slow_damage_(a_slow_damage),
843  a_drain_percent_(a_drain_percent), a_drain_constant_(a_drain_constant),
844 
845  b_max_hp_(b_max_hp), b_slows_(b_slows), b_damage_(b_damage), b_slow_damage_(b_slow_damage),
846  b_drain_percent_(b_drain_percent), b_drain_constant_(b_drain_constant)
847 {
848 }
849 
850 // Shift combat_matrix to reflect the probability 'hit_chance' that damage
851 // is done to 'b'.
852 void combat_matrix::receive_blow_b(double hit_chance)
853 {
854  // Walk backwards so we don't copy already-copied matrix planes.
855  unsigned src = NUM_PLANES;
856  while ( src-- != 0 ) {
857  if ( !plane_used(src) )
858  continue;
859 
860  // If A slows us, we go from 0=>2, 1=>3, 2=>2 3=>3.
861  int dst = a_slows_ ? src|2 : src;
862 
863  // A is slow in planes 1 and 3.
864  unsigned damage = src & 1 ? a_slow_damage_ : a_damage_;
865 
866  shift_cols(dst, src, damage, hit_chance, a_drain_constant_, a_drain_percent_);
867  }
868 }
869 
870 // Shift matrix to reflect probability 'hit_chance'
871 // that damage (up to) 'damage' is done to 'a'.
872 void combat_matrix::receive_blow_a(double hit_chance)
873 {
874  // Walk backwards so we don't copy already-copied matrix planes.
875  unsigned src = NUM_PLANES;
876  while ( src-- != 0 ) {
877  if ( !plane_used(src) )
878  continue;
879 
880  // If B slows us, we go from 0=>1, 1=>1, 2=>3 3=>3.
881  int dst = b_slows_ ? src|1 : src;
882 
883  // B is slow in planes 2 and 3.
884  unsigned damage = src & 2 ? b_slow_damage_ : b_damage_;
885 
886  shift_rows(dst, src, damage, hit_chance, b_drain_constant_, b_drain_percent_);
887  }
888 }
889 
890 // We lied: actually did less damage, adjust matrix.
891 void combat_matrix::remove_petrify_distortion_a(unsigned damage, unsigned slow_damage,
892  unsigned b_hp)
893 {
894  for (int p = 0; p < NUM_PLANES; ++p) {
895  if ( !plane_used(p) )
896  continue;
897 
898  // A is slow in planes 1 and 3.
899  unsigned actual_damage = (p & 1) ? slow_damage : damage;
900  if ( b_hp > actual_damage )
901  // B was actually petrified, not killed.
902  move_column(p, p, b_hp - actual_damage, 0);
903  }
904 }
905 
906 void combat_matrix::remove_petrify_distortion_b(unsigned damage, unsigned slow_damage,
907  unsigned a_hp)
908 {
909  for (int p = 0; p < NUM_PLANES; ++p) {
910  if ( !plane_used(p) )
911  continue;
912 
913  // B is slow in planes 2 and 3.
914  unsigned actual_damage = (p & 2) ? slow_damage : damage;
915  if ( a_hp > actual_damage )
916  // A was actually petrified, not killed.
917  move_row(p, p, a_hp - actual_damage, 0);
918  }
919 }
920 
921 void combat_matrix::forced_levelup_a()
922 {
923  /* Move all the values (except 0hp) of all the planes to the "fully healed"
924  row of the planes unslowed for A. */
925  for (int p = 0; p < NUM_PLANES; ++p) {
926  if ( plane_used(p) )
927  merge_cols(p & -2, p, a_max_hp_);
928  }
929 }
930 
931 void combat_matrix::forced_levelup_b()
932 {
933  /* Move all the values (except 0hp) of all the planes to the "fully healed"
934  column of planes unslowed for B. */
935  for (int p = 0; p < NUM_PLANES; ++p) {
936  if ( plane_used(p) )
937  merge_rows(p & -3, p, b_max_hp_);
938  }
939 }
940 
941 void combat_matrix::conditional_levelup_a()
942 {
943  /* Move the values of the first column (except 0hp) of all the
944  planes to the "fully healed" row of the planes unslowed for A. */
945  for (int p = 0; p < NUM_PLANES; ++p) {
946  if ( plane_used(p) )
947  merge_col(p & -2, p, 0, a_max_hp_);
948  }
949 }
950 
951 void combat_matrix::conditional_levelup_b()
952 {
953  /* Move the values of the first row (except 0hp) of all the
954  planes to the last column of the planes unslowed for B. */
955  for (int p = 0; p < NUM_PLANES; ++p) {
956  if ( plane_used(p) )
957  merge_row(p & -3, p, 0, b_max_hp_);
958  }
959 }
960 
961 void combat_matrix::extract_results(std::vector<double> summary_a[2],
962  std::vector<double> summary_b[2])
963 {
964  // Reset the summaries.
965  summary_a[0] = std::vector<double>(num_rows());
966  summary_b[0] = std::vector<double>(num_cols());
967 
968  if ( plane_used(A_SLOWED) )
969  summary_a[1] = std::vector<double>(num_rows());
970  if ( plane_used(B_SLOWED) )
971  summary_b[1] = std::vector<double>(num_cols());
972 
973  for (unsigned p = 0; p < NUM_PLANES; ++p) {
974  if ( !plane_used(p) )
975  continue;
976 
977  // A is slow in planes 1 and 3.
978  unsigned dst_a = (p & 1) ? 1u : 0u;
979  // B is slow in planes 2 and 3.
980  unsigned dst_b = (p & 2) ? 1u : 0u;
981  sum(p, summary_a[dst_a], summary_b[dst_b]);
982  }
983 }
984 
985 } // end anon namespace
986 
987 
988 /**
989  * A struct to describe one possible combat scenario.
990  * (Needed when the number of attacks can vary due to swarm.)
991  */
993 {
994  // The hit point range this slice covers.
995  unsigned begin_hp; // included in the range.
996  unsigned end_hp; // excluded from the range.
997 
998  // The probability of this slice.
999  double prob;
1000 
1001  // The number of strikes applicable with this slice.
1002  unsigned strikes;
1003 
1004 
1005  combat_slice(const std::vector<double> src_summary[2],
1006  unsigned begin, unsigned end, unsigned num_strikes);
1007  combat_slice(const std::vector<double> src_summary[2], unsigned num_strikes);
1008 };
1009 
1010 /**
1011  * Creates a slice from a summary, and associates a number of strikes.
1012  */
1013 combatant::combat_slice::combat_slice(const std::vector<double> src_summary[2],
1014  unsigned begin, unsigned end,
1015  unsigned num_strikes) :
1016  begin_hp(begin),
1017  end_hp(end),
1018  prob(0.0),
1019  strikes(num_strikes)
1020 {
1021  if ( src_summary[0].empty() ) {
1022  // No summary; this should be the only slice.
1023  prob = 1.0;
1024  return;
1025  }
1026 
1027  // Avoid accessing beyond the end of the vectors.
1028  if ( end > src_summary[0].size() )
1029  end = src_summary[0].size();
1030 
1031  // Sum the probabilities in the slice.
1032  for ( unsigned i = begin; i < end; ++i )
1033  prob += src_summary[0][i];
1034  if ( !src_summary[1].empty() )
1035  for ( unsigned i = begin; i < end; ++i )
1036  prob += src_summary[1][i];
1037 }
1038 
1039 
1040 /**
1041  * Creates a slice from the summaries, and associates a number of strikes.
1042  * This version of the constructor creates a slice consisting of everything.
1043  */
1044 combatant::combat_slice::combat_slice(const std::vector<double> src_summary[2],
1045  unsigned num_strikes) :
1046  begin_hp(0),
1047  end_hp(src_summary[0].size()),
1048  prob(1.0),
1049  strikes(num_strikes)
1050 {
1051 }
1052 
1053 
1055  : hp_dist(u.max_hp + 1, 0.0),
1056  untouched(0.0),
1057  poisoned(0.0),
1058  slowed(0.0),
1059  u_(u)
1060 {
1061  // We inherit current state from previous combatant.
1062  if (prev) {
1063  summary[0] = prev->summary[0];
1064  summary[1] = prev->summary[1];
1065  hp_dist = prev->hp_dist;
1066  untouched = prev->untouched;
1067  poisoned = prev->poisoned;
1068  slowed = prev->slowed;
1069  } else {
1070  hp_dist[std::min(u.hp, u.max_hp)] = 1.0;
1071  untouched = 1.0;
1072  poisoned = u.is_poisoned ? 1.0 : 0.0;
1073  slowed = u.is_slowed ? 1.0 : 0.0;
1074  }
1075 }
1076 
1077 // Copy constructor (except use this copy of battle_context_unit_stats)
1079  : hp_dist(that.hp_dist), untouched(that.untouched), poisoned(that.poisoned), slowed(that.slowed), u_(u)
1080 {
1081  summary[0] = that.summary[0];
1082  summary[1] = that.summary[1];
1083 }
1084 
1085 
1086 namespace {
1087  /**
1088  * Returns the number of hit points greater than cur_hp, and at most
1089  * stats.max_hp+1, at which the unit would get another attack because
1090  * of swarm.
1091  * Helper function for split_summary().
1092  */
1093  unsigned hp_for_next_attack(unsigned cur_hp,
1094  const battle_context_unit_stats & stats)
1095  {
1096  unsigned old_strikes = stats.calc_blows(cur_hp);
1097 
1098  // A formula would have to deal with rounding issues; instead
1099  // loop until we find more strikes.
1100  while ( ++cur_hp <= stats.max_hp )
1101  if ( stats.calc_blows(cur_hp) != old_strikes )
1102  break;
1103 
1104  return cur_hp;
1105  }
1106 } // end anon namespace
1107 
1108 /**
1109  * Split the combat by number of attacks per combatant (for swarm).
1110  * This also clears the current summaries.
1111  */
1112 std::vector<combatant::combat_slice> combatant::split_summary() const
1113 {
1114  std::vector<combat_slice> result;
1115 
1116  if ( u_.swarm_min == u_.swarm_max || summary[0].empty() )
1117  {
1118  // We use the same number of blows for all possibilities.
1119  result.push_back(combat_slice(summary, u_.num_blows));
1120  return result;
1121  }
1122 
1123  debug(("Slicing:\n"));
1124  // Loop through our slices.
1125  unsigned cur_end = 0;
1126  do {
1127  // Advance to the next slice.
1128  const unsigned cur_begin = cur_end;
1129  cur_end = hp_for_next_attack(cur_begin, u_);
1130 
1131  // Add this slice.
1132  combat_slice slice(summary, cur_begin, cur_end, u_.calc_blows(cur_begin));
1133  if ( slice.prob != 0.0 ) {
1134  result.push_back(slice);
1135  debug(("\t%2u-%2u hp; strikes: %u; probability: %6.2f\n",
1136  cur_begin, cur_end, slice.strikes, slice.prob*100.0));
1137  }
1138  } while ( cur_end <= u_.max_hp );
1139 
1140  return result;
1141 }
1142 
1143 
1144 namespace {
1145 
1146 void forced_levelup(std::vector<double> &hp_dist)
1147 {
1148  /* If we survive the combat, we will level up. So the probability
1149  of death is unchanged, but all other cases get merged into the
1150  fully healed case. */
1151  std::vector<double>::iterator i = hp_dist.begin();
1152  ++i; // Skip to the second value.
1153  for (; i != hp_dist.end(); ++i) *i = 0;
1154  // Full heal unless dead.
1155  hp_dist.back() = 1 - hp_dist.front();
1156 }
1157 
1158 void conditional_levelup(std::vector<double> &hp_dist, double kill_prob)
1159 {
1160  /* If we kill, we will level up. So then the damage we had becomes
1161  less probable since it's now conditional on us not leveling up.
1162  This doesn't apply to the probability of us dying, of course. */
1163  double scalefactor = 0;
1164  const double chance_to_survive = 1 - hp_dist.front();
1165  if(chance_to_survive > DBL_MIN) scalefactor = 1 - kill_prob / chance_to_survive;
1166  std::vector<double>::iterator i = hp_dist.begin();
1167  ++i; // Skip to the second value.
1168  for (; i != hp_dist.end(); ++i) *i *= scalefactor;
1169  // Full heal if leveled up.
1170  hp_dist.back() += kill_prob;
1171 }
1172 
1173 /**
1174  * Returns the smallest HP we could possibly have based on the provided
1175  * hit point distribution.
1176  */
1177 unsigned min_hp(const std::vector<double> & hp_dist, unsigned def)
1178 {
1179  const unsigned size = hp_dist.size();
1180 
1181  // Look for a nonzero entry.
1182  for ( unsigned i = 0; i != size; ++i )
1183  if ( hp_dist[i] != 0.0 )
1184  return i;
1185 
1186  // Either the distribution is empty or is full of zeros, so
1187  // return the default.
1188  return def;
1189 }
1190 
1191 // Combat without chance of death, berserk, slow or drain is simple.
1192 void no_death_fight(const battle_context_unit_stats &stats,
1193  const battle_context_unit_stats &opp_stats,
1194  unsigned strikes, unsigned opp_strikes,
1195  std::vector<double> & hp_dist,
1196  std::vector<double> & opp_hp_dist,
1197  double & self_not_hit, double & opp_not_hit,
1198  bool levelup_considered)
1199 {
1200  // Our strikes.
1201  // If we were killed in an earlier fight, we don't get to attack.
1202  // (Most likely case: we are a first striking defender subject to a series
1203  // of attacks.)
1204  const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1205  const double hit_chance = (stats.chance_to_hit/100.0) * alive_prob;
1206  if ( opp_hp_dist.empty() ) {
1207  // Starts with a known HP, so Pascal's triangle.
1208  opp_hp_dist = std::vector<double>(opp_stats.max_hp+1);
1209  opp_hp_dist[opp_stats.hp] = 1.0;
1210  for (unsigned int i = 0; i < strikes; ++i) {
1211  for (int j = i; j >= 0; j--) {
1212  unsigned src_index = opp_stats.hp - j*stats.damage;
1213  double move = opp_hp_dist[src_index] * hit_chance;
1214  opp_hp_dist[src_index] -= move;
1215  opp_hp_dist[src_index - stats.damage] += move;
1216  }
1217  opp_not_hit *= 1.0 - hit_chance;
1218  }
1219  } else {
1220  // HP could be spread anywhere, iterate through whole thing.
1221  for (unsigned int i = 0; i < strikes; ++i) {
1222  for (unsigned int j = stats.damage; j < opp_hp_dist.size(); ++j) {
1223  double move = opp_hp_dist[j] * hit_chance;
1224  opp_hp_dist[j] -= move;
1225  opp_hp_dist[j - stats.damage] += move;
1226  }
1227  opp_not_hit *= 1.0 - hit_chance;
1228  }
1229  }
1230 
1231  // Opponent's strikes
1232  // If opponent was killed in an earlier fight, they don't get to attack.
1233  const double opp_alive_prob = opp_hp_dist.empty() ? 1.0 : 1.0 - opp_hp_dist[0];
1234  const double opp_hit_chance = (opp_stats.chance_to_hit/100.0) * opp_alive_prob;
1235  if ( hp_dist.empty() ) {
1236  // Starts with a known HP, so Pascal's triangle.
1237  hp_dist = std::vector<double>(stats.max_hp+1);
1238  hp_dist[stats.hp] = 1.0;
1239  for (unsigned int i = 0; i < opp_strikes; ++i) {
1240  for (int j = i; j >= 0; j--) {
1241  unsigned src_index = stats.hp - j*opp_stats.damage;
1242  double move = hp_dist[src_index] * opp_hit_chance;
1243  hp_dist[src_index] -= move;
1244  hp_dist[src_index - opp_stats.damage] += move;
1245  }
1246  self_not_hit *= 1.0 - opp_hit_chance;
1247  }
1248  } else {
1249  // HP could be spread anywhere, iterate through whole thing.
1250  for (unsigned int i = 0; i < opp_strikes; ++i) {
1251  for (unsigned int j = opp_stats.damage; j < hp_dist.size(); ++j) {
1252  double move = hp_dist[j] * opp_hit_chance;
1253  hp_dist[j] -= move;
1254  hp_dist[j - opp_stats.damage] += move;
1255  }
1256  self_not_hit *= 1.0 - opp_hit_chance;
1257  }
1258  }
1259 
1260  if (!levelup_considered) return;
1261 
1262  if ( stats.experience + opp_stats.level >= stats.max_experience ) {
1263  forced_levelup(hp_dist);
1264  }
1265 
1266  if ( opp_stats.experience + stats.level >= opp_stats.max_experience ) {
1267  forced_levelup(opp_hp_dist);
1268  }
1269 }
1270 
1271 // Combat with <= 1 strike each is simple, too.
1272 void one_strike_fight(const battle_context_unit_stats &stats,
1273  const battle_context_unit_stats &opp_stats,
1274  unsigned strikes, unsigned opp_strikes,
1275  std::vector<double> & hp_dist,
1276  std::vector<double> & opp_hp_dist,
1277  double & self_not_hit, double & opp_not_hit,
1278  bool levelup_considered)
1279 {
1280  // If we were killed in an earlier fight, we don't get to attack.
1281  // (Most likely case: we are a first striking defender subject to a series
1282  // of attacks.)
1283  const double alive_prob = hp_dist.empty() ? 1.0 : 1.0 - hp_dist[0];
1284  const double hit_chance = (stats.chance_to_hit/100.0) * alive_prob;
1285  if ( opp_hp_dist.empty() ) {
1286  opp_hp_dist = std::vector<double>(opp_stats.max_hp+1);
1287  if ( strikes == 1 ) {
1288  opp_hp_dist[opp_stats.hp] = 1.0 - hit_chance;
1289  opp_hp_dist[std::max<int>(opp_stats.hp - stats.damage, 0)] = hit_chance;
1290  opp_not_hit *= 1.0 - hit_chance;
1291  } else {
1292  assert(strikes == 0);
1293  opp_hp_dist[opp_stats.hp] = 1.0;
1294  }
1295  } else {
1296  if ( strikes == 1 ) {
1297  for (unsigned int i = 1; i < opp_hp_dist.size(); ++i) {
1298  double move = opp_hp_dist[i] * hit_chance;
1299  opp_hp_dist[i] -= move;
1300  opp_hp_dist[std::max<int>(i - stats.damage, 0)] += move;
1301  }
1302  opp_not_hit *= 1.0 - hit_chance;
1303  }
1304  }
1305 
1306  // If we killed opponent, it won't attack us.
1307  const double opp_alive_prob = 1.0 - opp_hp_dist[0] / alive_prob;
1308  const double opp_hit_chance = (opp_stats.chance_to_hit/100.0) * opp_alive_prob;
1309  if ( hp_dist.empty() ) {
1310  hp_dist = std::vector<double>(stats.max_hp+1);
1311  if ( opp_strikes == 1 ) {
1312  hp_dist[stats.hp] = 1.0 - opp_hit_chance;
1313  hp_dist[std::max<int>(stats.hp - opp_stats.damage, 0)] = opp_hit_chance;
1314  self_not_hit *= 1.0 - opp_hit_chance;
1315  } else {
1316  assert(opp_strikes == 0);
1317  hp_dist[stats.hp] = 1.0;
1318  }
1319  } else {
1320  if ( opp_strikes == 1) {
1321  for (unsigned int i = 1; i < hp_dist.size(); ++i) {
1322  double move = hp_dist[i] * opp_hit_chance;
1323  hp_dist[i] -= move;
1324  hp_dist[std::max<int>(i - opp_stats.damage, 0)] += move;
1325  }
1326  self_not_hit *= 1.0 - opp_hit_chance;
1327  }
1328  }
1329 
1330  if (!levelup_considered) return;
1331 
1332  if ( stats.experience + opp_stats.level >= stats.max_experience ) {
1333  forced_levelup(hp_dist);
1334  } else if ( stats.experience + game_config::kill_xp(opp_stats.level) >= stats.max_experience ) {
1335  conditional_levelup(hp_dist, opp_hp_dist[0]);
1336  }
1337 
1338  if ( opp_stats.experience + stats.level >= opp_stats.max_experience ) {
1339  forced_levelup(opp_hp_dist);
1340  } else if ( opp_stats.experience + game_config::kill_xp(stats.level) >= opp_stats.max_experience ) {
1341  conditional_levelup(opp_hp_dist, hp_dist[0]);
1342  }
1343 }
1344 
1345 void complex_fight(const battle_context_unit_stats &stats,
1346  const battle_context_unit_stats &opp_stats,
1347  unsigned strikes, unsigned opp_strikes,
1348  std::vector<double> summary[2],
1349  std::vector<double> opp_summary[2],
1350  double & self_not_hit, double & opp_not_hit,
1351  bool levelup_considered)
1352 {
1353  unsigned int rounds = std::max<unsigned int>(stats.rounds, opp_stats.rounds);
1354  unsigned max_attacks = std::max(strikes, opp_strikes);
1355 
1356  debug(("A gets %u attacks, B %u.\n", strikes, opp_strikes));
1357 
1358  unsigned int a_damage = stats.damage, a_slow_damage = stats.slow_damage;
1359  unsigned int b_damage = opp_stats.damage, b_slow_damage = opp_stats.slow_damage;
1360 
1361  // To simulate stoning, we set to amount which kills, and re-adjust after.
1362  /** @todo FIXME: This doesn't work for rolling calculations, just first battle.
1363  It also does not work if combined with (percentage) drain. */
1364  if (stats.petrifies)
1365  a_damage = a_slow_damage = opp_stats.max_hp;
1366  if (opp_stats.petrifies)
1367  b_damage = b_slow_damage = stats.max_hp;
1368 
1369  // Prepare the matrix that will do our calculations.
1370  combat_matrix m(stats.max_hp, opp_stats.max_hp,
1371  stats.hp, opp_stats.hp, summary, opp_summary,
1372  stats.slows && !opp_stats.is_slowed,
1373  opp_stats.slows && !stats.is_slowed,
1374  a_damage, b_damage, a_slow_damage, b_slow_damage,
1375  stats.drain_percent, opp_stats.drain_percent,
1376  stats.drain_constant, opp_stats.drain_constant);
1377  const double hit_chance = stats.chance_to_hit / 100.0;
1378  const double opp_hit_chance = opp_stats.chance_to_hit / 100.0;
1379 
1380  do {
1381  for (unsigned int i = 0; i < max_attacks; ++i) {
1382  if ( i < strikes ) {
1383  debug(("A strikes\n"));
1384  opp_not_hit *= 1.0 - hit_chance*(1.0-m.dead_prob_a());
1385  m.receive_blow_b(hit_chance);
1386  m.dump();
1387  }
1388  if ( i < opp_strikes ) {
1389  debug(("B strikes\n"));
1390  self_not_hit *= 1.0 - opp_hit_chance*(1.0-m.dead_prob_b());
1391  m.receive_blow_a(opp_hit_chance);
1392  m.dump();
1393  }
1394  }
1395 
1396  debug(("Combat ends:\n"));
1397  m.dump();
1398  } while (--rounds && m.dead_prob() < 0.99);
1399 
1400  if (stats.petrifies)
1401  m.remove_petrify_distortion_a(stats.damage, stats.slow_damage, opp_stats.hp);
1402  if (opp_stats.petrifies)
1403  m.remove_petrify_distortion_b(opp_stats.damage, opp_stats.slow_damage, stats.hp);
1404 
1405  if (levelup_considered) {
1406  if ( stats.experience + opp_stats.level >= stats.max_experience ) {
1407  m.forced_levelup_a();
1408  } else if ( stats.experience + game_config::kill_xp(opp_stats.level) >= stats.max_experience ) {
1409  m.conditional_levelup_a();
1410  }
1411 
1412  if ( opp_stats.experience + stats.level >= opp_stats.max_experience ) {
1413  m.forced_levelup_b();
1414  } else if ( opp_stats.experience + game_config::kill_xp(stats.level) >= opp_stats.max_experience ) {
1415  m.conditional_levelup_b();
1416  }
1417  }
1418 
1419  // We extract results separately, then combine.
1420  m.extract_results(summary, opp_summary);
1421 }
1422 
1423 
1424 /**
1425  * Chooses the best of the various known combat calculations for the current
1426  * situation.
1427  */
1428 void do_fight(const battle_context_unit_stats &stats,
1429  const battle_context_unit_stats &opp_stats,
1430  unsigned strikes, unsigned opp_strikes,
1431  std::vector<double> summary[2], std::vector<double> opp_summary[2],
1432  double & self_not_hit, double & opp_not_hit,
1433  bool levelup_considered)
1434 {
1435  // Optimization only works in the simple cases (no slow, no drain,
1436  // no petrify, no berserk, and no slowed results from an earlier combat).
1437  if ( !stats.slows && !opp_stats.slows &&
1438  !stats.drains && !opp_stats.drains &&
1439  !stats.petrifies && !opp_stats.petrifies &&
1440  stats.rounds == 1 && opp_stats.rounds == 1 &&
1441  summary[1].empty() && opp_summary[1].empty() )
1442  {
1443  if ( strikes <= 1 && opp_strikes <= 1 )
1444  one_strike_fight(stats, opp_stats, strikes, opp_strikes,
1445  summary[0], opp_summary[0], self_not_hit, opp_not_hit,
1446  levelup_considered);
1447  else if ( strikes*stats.damage < min_hp(opp_summary[0], opp_stats.hp) &&
1448  opp_strikes*opp_stats.damage < min_hp(summary[0], stats.hp) )
1449  no_death_fight(stats, opp_stats, strikes, opp_strikes,
1450  summary[0], opp_summary[0], self_not_hit, opp_not_hit,
1451  levelup_considered);
1452  else
1453  complex_fight(stats, opp_stats, strikes, opp_strikes,
1454  summary, opp_summary, self_not_hit, opp_not_hit,
1455  levelup_considered);
1456  }
1457  else
1458  complex_fight(stats, opp_stats, strikes, opp_strikes,
1459  summary, opp_summary, self_not_hit, opp_not_hit,
1460  levelup_considered);
1461 }
1462 
1463 /**
1464  * Initializes a hit point summary (assumed empty) based on the source.
1465  * Only the part of the source from begin_hp up to (not including) end_hp
1466  * is kept, and all values get scaled by prob.
1467  */
1468 void init_slice_summary(std::vector<double> & dst, const std::vector<double> & src,
1469  unsigned begin_hp, unsigned end_hp, double prob)
1470 {
1471  if ( src.empty() )
1472  // Nothing to do.
1473  return;
1474 
1475  const unsigned size = src.size();
1476  // Avoid going over the end of the vector.
1477  if ( end_hp > size )
1478  end_hp = size;
1479 
1480  // Initialize the destination.
1481  dst.resize(size, 0.0);
1482  for ( unsigned i = begin_hp; i < end_hp; ++i )
1483  dst[i] = src[i] / prob;
1484 }
1485 
1486 /**
1487  * Merges the summary results of simulation into an overall summary.
1488  * This uses prob to reverse the scaling that was done in init_slice_summary().
1489  */
1490 void merge_slice_summary(std::vector<double> & dst, const std::vector<double> & src,
1491  double prob)
1492 {
1493  const unsigned size = src.size();
1494 
1495  // Make sure we have enough space.
1496  if ( dst.size() < size )
1497  dst.resize(size, 0.0);
1498 
1499  // Merge the data.
1500  for ( unsigned i = 0; i != size; ++i )
1501  dst[i] += src[i] * prob;
1502 }
1503 
1504 } // end anon namespace
1505 
1506 // Two man enter. One man leave!
1507 // ... Or maybe two. But definitely not three.
1508 // Of course, one could be a woman. Or both.
1509 // And either could be non-human, too.
1510 // Um, ok, it was a stupid thing to say.
1511 void combatant::fight(combatant &opp, bool levelup_considered)
1512 {
1513  // If defender has firststrike and we don't, reverse.
1514  if (opp.u_.firststrike && !u_.firststrike) {
1515  opp.fight(*this, levelup_considered);
1516  return;
1517  }
1518 
1519 #ifdef ATTACK_PREDICTION_DEBUG
1520  printf("A:\n");
1521  dump(u_);
1522  printf("B:\n");
1523  dump(opp.u_);
1524 #endif
1525 
1526 #if 0
1527  std::vector<double> prev = summary[0], opp_prev = opp.summary[0];
1528  complex_fight(opp, 1);
1529  std::vector<double> res = summary[0], opp_res = opp.summary[0];
1530  summary[0] = prev;
1531  opp.summary[0] = opp_prev;
1532 #endif
1533 
1534  // The chance so far of not being hit this combat:
1535  double self_not_hit = 1.0;
1536  double opp_not_hit = 1.0;
1537 
1538  // If we've fought before and we have swarm, we might have to split the
1539  // calculation by number of attacks.
1540  const std::vector<combat_slice> split = split_summary();
1541  const std::vector<combat_slice> opp_split = opp.split_summary();
1542 
1543  if ( split.size() == 1 && opp_split.size() == 1 )
1544  // No special treatment due to swarm is needed. Ignore the split.
1545  do_fight(u_, opp.u_, u_.num_blows, opp.u_.num_blows,
1546  summary, opp.summary, self_not_hit, opp_not_hit,
1547  levelup_considered);
1548  else
1549  {
1550  // Storage for the accumulated hit point distributions.
1551  std::vector<double> summary_result[2], opp_summary_result[2];
1552  // The chance of not being hit becomes an accumulated chance:
1553  self_not_hit = 0.0;
1554  opp_not_hit = 0.0;
1555 
1556  // Loop through all the potential combat situations.
1557  for ( unsigned s = 0; s != split.size(); ++s )
1558  for ( unsigned t = 0; t != opp_split.size(); ++t ) {
1559  const double sit_prob = split[s].prob * opp_split[t].prob;
1560 
1561  // Create summaries for this potential combat situation.
1562  std::vector<double> sit_summary[2], sit_opp_summary[2];
1563  init_slice_summary(sit_summary[0], summary[0], split[s].begin_hp,
1564  split[s].end_hp, split[s].prob);
1565  init_slice_summary(sit_summary[1], summary[1], split[s].begin_hp,
1566  split[s].end_hp, split[s].prob);
1567  init_slice_summary(sit_opp_summary[0], opp.summary[0],
1568  opp_split[t].begin_hp, opp_split[t].end_hp,
1569  opp_split[t].prob);
1570  init_slice_summary(sit_opp_summary[1], opp.summary[1],
1571  opp_split[t].begin_hp, opp_split[t].end_hp,
1572  opp_split[t].prob);
1573 
1574  // Scale the "not hit" chance for this situation by the chance that
1575  // this situation applies.
1576  double sit_self_not_hit = sit_prob;
1577  double sit_opp_not_hit = sit_prob;
1578 
1579  do_fight(u_, opp.u_, split[s].strikes, opp_split[t].strikes,
1580  sit_summary, sit_opp_summary, sit_self_not_hit,
1581  sit_opp_not_hit, levelup_considered);
1582 
1583  // Collect the results.
1584  self_not_hit += sit_self_not_hit;
1585  opp_not_hit += sit_opp_not_hit;
1586  merge_slice_summary(summary_result[0], sit_summary[0], sit_prob);
1587  merge_slice_summary(summary_result[1], sit_summary[1], sit_prob);
1588  merge_slice_summary(opp_summary_result[0], sit_opp_summary[0], sit_prob);
1589  merge_slice_summary(opp_summary_result[1], sit_opp_summary[1], sit_prob);
1590  }
1591 
1592  // Swap in the results.
1593  summary[0].swap(summary_result[0]);
1594  summary[1].swap(summary_result[1]);
1595  opp.summary[0].swap(opp_summary_result[0]);
1596  opp.summary[1].swap(opp_summary_result[1]);
1597  }
1598 
1599 #if 0
1600  assert(summary[0].size() == res.size());
1601  assert(opp.summary[0].size() == opp_res.size());
1602  for (unsigned int i = 0; i < summary[0].size(); ++i) {
1603  if (fabs(summary[0][i] - res[i]) > 0.000001) {
1604  std::cerr << "Mismatch for " << i << " hp: " << summary[0][i] << " should have been " << res[i] << "\n";
1605  assert(0);
1606  }
1607  }
1608  for (unsigned int i = 0; i < opp.summary[0].size(); ++i) {
1609  if (fabs(opp.summary[0][i] - opp_res[i])> 0.000001) {
1610  std::cerr << "Mismatch for " << i << " hp: " << opp.summary[0][i] << " should have been " << opp_res[i] << "\n";
1611  assert(0);
1612  }
1613  }
1614 #endif
1615 
1616  // Combine summary into distribution.
1617  if (summary[1].empty())
1618  hp_dist = summary[0];
1619  else {
1620  const unsigned size = summary[0].size();
1621  hp_dist.resize(size);
1622  for (unsigned int i = 0; i < size; ++i)
1623  hp_dist[i] = summary[0][i] + summary[1][i];
1624  }
1625  if (opp.summary[1].empty())
1626  opp.hp_dist = opp.summary[0];
1627  else {
1628  const unsigned size = opp.summary[0].size();
1629  opp.hp_dist.resize(size);
1630  for (unsigned int i = 0; i < size; ++i)
1631  opp.hp_dist[i] = opp.summary[0][i] + opp.summary[1][i];
1632  }
1633 
1634  // Chance that we / they were touched this time.
1635  double touched = 1.0 - self_not_hit;
1636  double opp_touched = 1.0 - opp_not_hit;
1637  if (opp.u_.poisons)
1638  poisoned += (1 - poisoned) * touched;
1639  if (u_.poisons)
1640  opp.poisoned += (1 - opp.poisoned) * opp_touched;
1641 
1642  if (opp.u_.slows)
1643  slowed += (1 - slowed) * touched;
1644  if (u_.slows)
1645  opp.slowed += (1 - opp.slowed) * opp_touched;
1646 
1647  untouched *= self_not_hit;
1648  opp.untouched *= opp_not_hit;
1649 }
1650 
1651 double combatant::average_hp(unsigned int healing) const
1652 {
1653  double total = 0;
1654 
1655  // Since sum of probabilities is 1.0, we can just tally weights.
1656  for (unsigned int i = 1; i < hp_dist.size(); ++i) {
1657  total += hp_dist[i] * std::min<unsigned int>(i + healing, u_.max_hp);
1658  }
1659  return total;
1660 }
1661 
1662  /* ** The stand-alone program ** */
1663 
1664 #if defined(BENCHMARK) || defined(CHECK)
1665 // We create a significant number of nasty-to-calculate units,
1666 // and test each one against the others.
1667 #define NUM_UNITS 50
1668 
1669 // Stolen from glibc headers sys/time.h
1670 #define timer_sub(a, b, result) \
1671  do { \
1672  (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
1673  (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
1674  if ((result)->tv_usec < 0) { \
1675  --(result)->tv_sec; \
1676  (result)->tv_usec += 1000000; \
1677  } \
1678  } while (0)
1679 
1680 
1681 #ifdef ATTACK_PREDICTION_DEBUG
1682 void list_combatant(const battle_context_unit_stats & stats, unsigned fighter)
1683 {
1684  printf("#%02u: %u-%d; %2uhp; %02u%% to hit; ",
1685  fighter, stats.swarm_max, stats.damage, stats.hp, stats.chance_to_hit);
1686  if ( stats.drains )
1687  printf("drains,");
1688  if ( stats.slows )
1689  printf("slows,");
1690  if ( stats.rounds > 1 )
1691  printf("berserk,");
1692  if ( stats.swarm )
1693  printf("swarm(%u),", stats.num_blows);
1694  if ( stats.firststrike )
1695  printf("firststrike,");
1696  printf("maxhp=%u\n", stats.max_hp);
1697 }
1698 #else
1699 void list_combatant(const battle_context_unit_stats &, unsigned)
1700 { }
1701 #endif
1702 
1703 
1704 #ifdef HUMAN_READABLE
1705 void combatant::print(const char label[], unsigned int battle, unsigned int fighter) const
1706 {
1707  printf("#%06u: (%02u) %s%*c %u-%d; %uhp; %02u%% to hit; %.2f%% unscathed; ",
1708  battle, fighter, label, int(strlen(label))-12, ':',
1710  if ( u_.drains )
1711  printf("drains,");
1712  if ( u_.slows )
1713  printf("slows,");
1714  if ( u_.rounds > 1 )
1715  printf("berserk,");
1716  if ( u_.swarm )
1717  printf("swarm,");
1718  if ( u_.firststrike )
1719  printf("firststrike,");
1720  printf("maxhp=%zu ", hp_dist.size()-1);
1721 
1722  int num_outputs = 0;
1723  for ( unsigned int i = 0; i < hp_dist.size(); ++i )
1724  if ( hp_dist[i] != 0.0 )
1725  {
1726  if ( num_outputs++ % 6 == 0 )
1727  printf("\n\t");
1728  else
1729  printf(" ");
1730  printf("%2u: %5.2f", i, hp_dist[i] * 100);
1731  }
1732 
1733  printf("\n");
1734 }
1735 #elif defined(CHECK)
1736 void combatant::print(const char label[], unsigned int battle, unsigned int /*fighter*/) const
1737 {
1738  printf("#%u: %s: %d %u %u %2g%% ", battle, label,
1739  u_.damage, u_.swarm_max, u_.hp, static_cast<float>(u_.chance_to_hit));
1740  if ( u_.drains )
1741  printf("drains,");
1742  if ( u_.slows )
1743  printf("slows,");
1744  if ( u_.rounds > 1 )
1745  printf("berserk,");
1746  if ( u_.swarm )
1747  printf("swarm,");
1748  if ( u_.firststrike )
1749  printf("firststrike,");
1750  printf("maxhp=%zu ", hp_dist.size()-1);
1751  printf(" %.2f", untouched);
1752  for (unsigned int i = 0; i < hp_dist.size(); ++i)
1753  printf(" %.2f", hp_dist[i] * 100);
1754  printf("\n");
1755 }
1756 #else // ... BENCHMARK
1757 void combatant::print(const char /*label*/[], unsigned int /*battle*/, unsigned int /*fighter*/) const
1758 {
1759 }
1760 #endif
1761 
1762 void combatant::reset()
1763 {
1764  for ( unsigned int i = 0; i < hp_dist.size(); ++i )
1765  hp_dist[i] = 0.0;
1766  untouched = 1.0;
1767  poisoned = u_.is_poisoned ? 1.0 : 0.0;
1768  slowed = u_.is_slowed ? 1.0 : 0.0;
1769  summary[0] = std::vector<double>();
1770  summary[1] = std::vector<double>();
1771 }
1772 
1773 
1774 static void run(unsigned specific_battle)
1775 {
1776  // N^2 battles
1777  struct battle_context_unit_stats *stats[NUM_UNITS];
1778  struct combatant *u[NUM_UNITS];
1779  unsigned int i, j, k, battle = 0;
1780  struct timeval start, end, total;
1781 
1782  for (i = 0; i < NUM_UNITS; ++i) {
1783  unsigned alt = i + 74; // To offset some cycles.
1784  // To get somewhat realistic performance data, try to approximate
1785  // hit point ranges for mainline units (say 25-60 max hitpoints?)
1786  unsigned max_hp = (i*2)%23 + (i*3)%14 + 25;
1787  unsigned hp = (alt*5)%max_hp + 1;
1788  stats[i] = new battle_context_unit_stats(alt%8 + 2, // damage
1789  (alt%19 + 3)/4, // number of strikes
1790  hp, max_hp,
1791  (i%6)*10 + 30, // hit chance
1792  (i%13)%4 == 0, // drains
1793  (i%11)%3 == 0, // slows
1794  false, // slowed
1795  i%7 == 0, // berserk
1796  (i%17)/2 == 0, // firststrike
1797  i%5 == 0); // swarm
1798  u[i] = new combatant(*stats[i]);
1799  list_combatant(*stats[i], i+1);
1800  }
1801 
1802  gettimeofday(&start, nullptr);
1803  // Go through all fights with two attackers (j and k attacking i).
1804  for (i = 0; i < NUM_UNITS; ++i) {
1805  for (j = 0; j < NUM_UNITS; ++j) {
1806  if (i == j)
1807  continue;
1808  for (k = 0; k < NUM_UNITS; ++k) {
1809  if (i == k || j == k)
1810  continue;
1811  ++battle;
1812  if (specific_battle && battle != specific_battle)
1813  continue;
1814  // Fight!
1815  u[j]->fight(*u[i]);
1816  u[k]->fight(*u[i]);
1817  // Results.
1818  u[i]->print("Defender", battle, i+1);
1819  u[j]->print("Attacker #1", battle, j+1);
1820  u[k]->print("Attacker #2", battle, k+1);
1821  // Start the next fight fresh.
1822  u[i]->reset();
1823  u[j]->reset();
1824  u[k]->reset();
1825  }
1826  }
1827  }
1828  gettimeofday(&end, nullptr);
1829 
1830  timer_sub(&end, &start, &total);
1831 
1832 #ifdef BENCHMARK
1833  printf("Total time for %i combats was %lu.%06lu\n",
1834  NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2), total.tv_sec, total.tv_usec);
1835  printf("Time per calc = %li us\n",
1836  ((end.tv_sec-start.tv_sec)*1000000 + (end.tv_usec-start.tv_usec))
1837  / (NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2)));
1838 #else
1839  printf("Total combats: %i\n", NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2));
1840 #endif
1841 
1842  for (i = 0; i < NUM_UNITS; ++i) {
1843  delete u[i];
1844  delete stats[i];
1845  }
1846 
1847  exit(0);
1848 }
1849 
1850 static battle_context_unit_stats *parse_unit(char ***argv)
1851 {
1852  // There are four required parameters.
1853  int add_to_argv = 4;
1854  int damage = atoi((*argv)[1]);
1855  int num_attacks = atoi((*argv)[2]);
1856  int hitpoints = atoi((*argv)[3]), max_hp = hitpoints;
1857  int hit_chance = atoi((*argv)[4]);
1858 
1859  // Parse the optional (fifth) parameter.
1860  bool drains = false, slows = false, slowed = false, berserk = false,
1861  firststrike = false, swarm = false;
1862  if ((*argv)[5] && atoi((*argv)[5]) == 0) {
1863  // Optional parameter is present.
1864  ++add_to_argv;
1865 
1866  char *max = strstr((*argv)[5], "maxhp=");
1867  if (max) {
1868  max_hp = atoi(max + strlen("maxhp="));
1869  if ( max_hp < hitpoints ) {
1870  fprintf(stderr, "maxhp must be at least hitpoints.");
1871  exit(1);
1872  }
1873  }
1874  if (strstr((*argv)[5], "drain")) {
1875  if (!max) {
1876  fprintf(stderr, "WARNING: drain specified without maxhp; assuming uninjured.\n");
1877  }
1878  drains = true;
1879  }
1880  if (strstr((*argv)[5], "slows"))
1881  slows = true;
1882  if (strstr((*argv)[5], "slowed"))
1883  slowed = true;
1884  if (strstr((*argv)[5], "berserk"))
1885  berserk = true;
1886  if (strstr((*argv)[5], "firststrike"))
1887  firststrike = true;
1888  if (strstr((*argv)[5], "swarm")) {
1889  if (!max) {
1890  fprintf(stderr, "WARNING: swarm specified without maxhp; assuming uninjured.\n");
1891  }
1892  swarm = true;
1893  }
1894  }
1895 
1896  // Update argv.
1897  *argv += add_to_argv;
1898 
1899  // Construct the stats and return.
1900  return new battle_context_unit_stats(damage, num_attacks, hitpoints, max_hp,
1901  hit_chance, drains, slows, slowed,
1902  berserk, firststrike, swarm);
1903 }
1904 
1905 int main(int argc, char *argv[])
1906 {
1907  battle_context_unit_stats *def_stats, *att_stats[20];
1908  combatant *def, *att[20];
1909  unsigned int i;
1910 
1911  if (argc < 3)
1912  run(argv[1] ? atoi(argv[1]) : 0);
1913 
1914  if (argc < 9) {
1915  fprintf(stderr, "Usage: %s [<battle>]\n"
1916  "\t%s <damage> <attacks> <hp> <hitprob> [drain,slows,slowed,swarm,firststrike,berserk,maxhp=<num>] <damage> <attacks> <hp> <hitprob> [drain,slows,slowed,berserk,firststrike,swarm,maxhp=<num>] ...\n",
1917  argv[0], argv[0]);
1918  exit(1);
1919  }
1920 
1921  def_stats = parse_unit(&argv);
1922  def = new combatant(*def_stats);
1923  for (i = 0; argv[1] && i < 19; ++i) {
1924  att_stats[i] = parse_unit(&argv);
1925  att[i] = new combatant(*att_stats[i]);
1926  }
1927  att[i] = nullptr;
1928 
1929  for (i = 0; att[i]; ++i) {
1930  debug(("Fighting next attacker\n"));
1931  att[i]->fight(*def);
1932  }
1933 
1934  def->print("Defender", 0, 0);
1935  for (i = 0; att[i]; ++i)
1936  att[i]->print("Attacker", 0, i+1);
1937 
1938  for (i = 0; att[i]; ++i) {
1939  delete att[i];
1940  delete att_stats[i];
1941  }
1942  delete def;
1943  delete def_stats;
1944 
1945  return 0;
1946 }
1947 #endif // Standalone program
int kill_xp(int level)
Definition: game_config.hpp:45
unsigned int calc_blows(unsigned new_hp) const
Calculates the number of blows we would have if we had new_hp.
Definition: attack.hpp:103
double untouched
Resulting chance we were not hit by this opponent (important if it poisons)
std::vector< double > hp_dist
Resulting probability distribution (might be not as large as max_hp)
unsigned int hp
Hitpoints of the unit at the beginning of the battle.
Definition: attack.hpp:72
std::vector< combat_slice > split_summary() const
Split the combat by number of attacks per combatant (for swarm).
Various functions that implement attacks and attack calculations.
GLuint const GLfloat * val
Definition: glew.h:2614
bool is_slowed
True if the unit is slowed at the beginning of the battle.
Definition: attack.hpp:55
bool slows
Attack slows opponent when it hits.
Definition: attack.hpp:56
GLenum src
Definition: glew.h:2392
int drain_constant
Base HP drained regardless of damage dealt.
Definition: attack.hpp:78
GLdouble GLdouble t
Definition: glew.h:1366
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:74
bool poisons
Attack poisons opponent when it hits.
Definition: attack.hpp:60
bool backstab_pos
True if the attacker is in position to backstab the defender (this is used to determine whether to ap...
Definition: attack.hpp:61
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:75
GLuint GLuint end
Definition: glew.h:1221
GLuint64EXT * result
Definition: glew.h:10727
unsigned int level
Definition: attack.hpp:69
GLint limit
Definition: glew.h:10112
const battle_context_unit_stats & u_
unsigned int rounds
Berserk special can force us to fight more than one round.
Definition: attack.hpp:71
A struct to describe one possible combat scenario.
unsigned int swarm_min
Minimum number of blows with swarm (equal to num_blows if swarm isn't used).
Definition: attack.hpp:80
GLenum GLenum dst
Definition: glew.h:2392
GLuint start
Definition: glew.h:1221
int kill_experience
Definition: game_config.cpp:43
combatant(const battle_context_unit_stats &u, const combatant *prev=nullptr)
Construct a combatant.
void fight(combatant &opponent, bool levelup_considered=true)
Simulate a fight! Can be called multiple times for cumulative calculations.
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:49
GLfloat GLfloat p
Definition: glew.h:12766
GLuint const GLuint * names
Definition: glew.h:2552
GLboolean reset
Definition: glew.h:3799
double slowed
Resulting chance we are slowed.
All combat-related info.
GLuint res
Definition: glew.h:9258
bool swarm
Attack has swarm special.
Definition: attack.hpp:65
int slow_damage
Effective damage if unit becomes slowed (== damage, if already slowed)
Definition: attack.hpp:76
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
Definition: fire_event.cpp:29
Game configuration data as global variables.
Definition: build_info.cpp:38
unsigned int experience
Definition: attack.hpp:68
size_t i
Definition: function.cpp:1057
bool firststrike
Attack has firststrike special.
Definition: attack.hpp:66
bool is_poisoned
True if the unit is poisoned at the beginning of the battle.
Definition: attack.hpp:54
#define debug(x)
int main(int argc, char **argv)
int drain_percent
Percentage of damage recovered as health.
Definition: attack.hpp:77
GLsizeiptr size
Definition: glew.h:1649
GLenum GLenum GLvoid * row
Definition: glew.h:3805
combat_slice(const std::vector< double > src_summary[2], unsigned begin, unsigned end, unsigned num_strikes)
Creates a slice from a summary, and associates a number of strikes.
const GLdouble * m
Definition: glew.h:6968
map_location prev
Definition: astarsearch.cpp:67
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:79
unsigned int max_hp
Maximum hitpoints of the unit.
Definition: attack.hpp:73
double average_hp(unsigned int healing=0) const
What's the average hp (weighted average of hp_dist).
std::vector< std::string > split(std::string const &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
bool is_attacker
True if the unit is the attacker.
Definition: attack.hpp:53
unsigned int max_experience
Definition: attack.hpp:68
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
bool petrifies
Attack petrifies opponent when it hits.
Definition: attack.hpp:58
GLdouble s
Definition: glew.h:1358
bool drains
Attack drains opponent when it hits.
Definition: attack.hpp:57
double poisoned
Resulting chance we are poisoned.
unsigned int swarm_max
Maximum number of blows with swarm (equal to num_blows if swarm isn't used).
Definition: attack.hpp:81
std::vector< double > summary[2]
Summary of matrix used to calculate last battle (unslowed & slowed).