attack_prediction.cpp

Go to the documentation of this file.
00001 /* $Id: attack_prediction.cpp 24850 2008-03-19 16:55:39Z brunowolff $ */
00002 /*
00003    Copyright (C) 2006 - 2008 by Rusty Russell <rusty@rustcorp.com.au>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 
00014    Full algorithm by Yogin.  Typing and optimization by Rusty.
00015 
00016    This code has lots of debugging.  It is there for a reason:
00017    this code is kinda tricky.  Do not remove it.
00018 */
00019 
00020 //! @file attack_prediction.cpp
00021 //! Simulate combat to calculate attacks. Standalone program, benchmark.
00022 
00023 #include "attack_prediction.hpp"
00024 
00025 #include <cassert>
00026 #include <cstring> // For memset
00027 #include <vector>
00028 
00029 
00030 // Compile with -O3 -DBENCHMARK for speed testing,
00031 // -DCHECK for testing correctness
00032 // (run tools/wesnoth-attack-sim.c --check on output)
00033 #if !defined(BENCHMARK) && !defined(CHECK)
00034 #include "util.hpp"
00035 #else
00036 #include <time.h>
00037 #include <sys/time.h>
00038 #include <stdio.h>
00039 #include <stdlib.h>
00040 #define maximum(a,b) ((a) > (b) ? (a) : (b))
00041 #define minimum(a,b) ((a) < (b) ? (a) : (b))
00042 #endif
00043 
00044 #ifdef ATTACK_PREDICTION_DEBUG
00045 #define debug(x) printf x
00046 #else
00047 #define debug(x)
00048 #endif
00049 
00050 namespace
00051 {
00052 //! A matrix of A's hitpoints vs B's hitpoints.
00053 struct prob_matrix
00054 {
00055     // Simple matrix, both known HP.
00056     prob_matrix(unsigned int a_max_hp, unsigned int b_max_hp,
00057                 bool a_slows, bool b_slows,
00058                 unsigned int a_hp, unsigned int b_hp,
00059                 const std::vector<double> a_summary[2],
00060                 const std::vector<double> b_summary[2]);
00061 
00062     ~prob_matrix();
00063 
00064     // A hits B.
00065     void receive_blow_b(unsigned damage, unsigned slow_damage, double hit_chance,
00066                         bool a_slows, bool a_drains);
00067 
00068     // B hits A.  Why can't they just get along?
00069     void receive_blow_a(unsigned damage, unsigned slow_damage, double hit_chance,
00070                         bool b_slows, bool b_drains);
00071 
00072     // We lied: actually did less damage, adjust matrix.
00073     void remove_stone_distortion_a(unsigned damage, unsigned slow_damage, unsigned b_hp);
00074     void remove_stone_distortion_b(unsigned damage, unsigned slow_damage, unsigned a_hp);
00075 
00076     // Its over, and here's the bill.
00077     void extract_results(std::vector<double> summary_a[2],
00078                          std::vector<double> summary_b[2]);
00079 
00080     // What's the chance one is dead?
00081     double dead_prob() const;
00082 
00083     void dump() const;
00084 
00085     // We need four matrices, or "planes", reflecting the possible
00086     // "slowed" states (neither slowed, A slowed, B slowed, both slowed).
00087     enum {
00088         NEITHER_SLOWED,
00089         A_SLOWED,
00090         B_SLOWED,
00091         BOTH_SLOWED
00092     };
00093 
00094 private:
00095     // This gives me 10% speed improvement over std::vector<> (g++4.0.3 x86)
00096     double *new_arr(unsigned int size);
00097 
00098     double &val(unsigned plane, unsigned row, unsigned col);
00099     const double &val(unsigned plane, unsigned row, unsigned col) const;
00100 
00101     // Move this much from src to dst.  Returns true if anything transferred.
00102     void xfer(unsigned dst_plane, unsigned src_plane,
00103               unsigned row_dst, unsigned col_dst,
00104               unsigned row_src, unsigned col_src,
00105               double prob);
00106 
00107     // Shift columns on this plane (b taking damage).  Returns min col.
00108     void shift_cols(unsigned dst, unsigned src,
00109                     unsigned damage, double prob, bool drain);
00110 
00111     // Shift rows on this plane (a taking damage).  Returns new min row.
00112     void shift_rows(unsigned dst, unsigned src,
00113                     unsigned damage, double prob, bool drain);
00114 
00115     //! @todo FIXME: rename using _ at end.
00116     unsigned int rows, cols;
00117     double *plane[4];
00118 
00119     // For optimization, we keep track of the lower row/col we need to consider
00120     unsigned int min_row[4], min_col[4];
00121 };
00122 
00123 prob_matrix::prob_matrix(unsigned int a_max_hp, unsigned int b_max_hp,
00124                          bool a_slows, bool b_slows,
00125                          unsigned int a_hp, unsigned int b_hp,
00126                          const std::vector<double> a_summary[2],
00127                          const std::vector<double> b_summary[2])
00128     : rows(a_max_hp+1), cols(b_max_hp+1)
00129 {
00130     if (!a_summary[0].empty()) {
00131         // A has fought before.  Do we need a slow plane for it?
00132         if (!a_summary[1].empty())
00133             b_slows = true;
00134         // Don't handle both being reused.
00135         assert(b_summary[0].empty());
00136     }
00137     if (!b_summary[0].empty()) {
00138         // B has fought before.  Do we need a slow plane for it?
00139         if (!b_summary[1].empty())
00140             a_slows = true;
00141     }
00142 
00143     plane[NEITHER_SLOWED] = new_arr(rows*cols);
00144     if (b_slows)
00145         plane[A_SLOWED] = new_arr(rows*cols);
00146     else
00147         plane[A_SLOWED] = NULL;
00148     if (a_slows)
00149         plane[B_SLOWED] = new_arr(rows*cols);
00150     else
00151         plane[B_SLOWED] = NULL;
00152     if (a_slows && b_slows)
00153         plane[BOTH_SLOWED] = new_arr(rows*cols);
00154     else
00155         plane[BOTH_SLOWED] = NULL;
00156 
00157     min_row[NEITHER_SLOWED] = a_hp - 1;
00158     min_col[NEITHER_SLOWED] = b_hp - 1;
00159     min_row[A_SLOWED] = min_row[B_SLOWED] = min_row[BOTH_SLOWED] = rows;
00160     min_col[A_SLOWED] = min_col[B_SLOWED] = min_col[BOTH_SLOWED] = cols;
00161 
00162     // Transfer HP distribution from A?
00163     if (!a_summary[0].empty()) {
00164         // @todo FIXME: Can optimize here.
00165         min_row[NEITHER_SLOWED] = 0;
00166         min_row[A_SLOWED] = 0;
00167         min_col[A_SLOWED] = b_hp - 1;
00168         for (unsigned int row = 0; row < a_summary[0].size(); row++)
00169             val(NEITHER_SLOWED, row, b_hp) = a_summary[0][row];
00170         if (!a_summary[1].empty()) {
00171             for (unsigned int row = 0; row < a_summary[1].size(); row++)
00172                 val(A_SLOWED, row, b_hp) = a_summary[1][row];
00173         }
00174         debug(("A has fought before\n"));
00175         dump();
00176     } else if (!b_summary[0].empty()) {
00177         min_col[NEITHER_SLOWED] = 0;
00178         min_col[B_SLOWED] = 0;
00179         min_row[B_SLOWED] = a_hp - 1;
00180         for (unsigned int col = 0; col < b_summary[0].size(); col++)
00181             val(NEITHER_SLOWED, a_hp, col) = b_summary[0][col];
00182         if (!b_summary[1].empty()) {
00183             for (unsigned int col = 0; col < b_summary[1].size(); col++)
00184                 val(B_SLOWED, a_hp, col) = b_summary[1][col];
00185         }
00186         debug(("B has fought before\n"));
00187         dump();
00188     } else {
00189         // If a unit has drain it might end with more HP than before.
00190         // Make sure we don't access the matrix in invalid positions.
00191         a_hp = minimum<unsigned int>(a_hp, rows - 1);
00192         b_hp = minimum<unsigned int>(b_hp, cols - 1);
00193         val(NEITHER_SLOWED, a_hp, b_hp) = 1.0;
00194     }
00195 }
00196 
00197 prob_matrix::~prob_matrix()
00198 {
00199     delete[] plane[NEITHER_SLOWED];
00200     delete[] plane[A_SLOWED];
00201     delete[] plane[B_SLOWED];
00202     delete[] plane[BOTH_SLOWED];
00203 }
00204 
00205 // Allocate a new probability array, initialized to 0.
00206 double *prob_matrix::new_arr(unsigned int size)
00207 {
00208     double *arr = new double[size];
00209     memset(arr, 0, sizeof(double) * size);
00210     return arr;
00211 }
00212 
00213 double &prob_matrix::val(unsigned p, unsigned row, unsigned col)
00214 {
00215     assert(row < rows);
00216     assert(col < cols);
00217     return plane[p][row * cols + col];
00218 }
00219 
00220 const double &prob_matrix::val(unsigned p, unsigned row, unsigned col) const
00221 {
00222     assert(row < rows);
00223     assert(col < cols);
00224     return plane[p][row * cols + col];
00225 }
00226 
00227 #ifdef CHECK
00228 void prob_matrix::dump() const
00229 {
00230     unsigned int row, col, m;
00231     const char *names[]
00232         = { "NEITHER_SLOWED", "A_SLOWED", "B_SLOWED", "BOTH_SLOWED" };
00233 
00234     for (m = 0; m < 4; m++) {
00235         if (!plane[m])
00236             continue;
00237         debug(("%s:\n", names[m]));
00238         for (row = 0; row < rows; row++) {
00239             debug(("  "));
00240             for (col = 0; col < cols; col++)
00241                 debug(("%4.3g ", val(m, row, col)*100));
00242             debug(("\n"));
00243         }
00244     }
00245 }
00246 #else
00247 void prob_matrix::dump() const
00248 {
00249 }
00250 #endif
00251 
00252 // xfer, shift_cols and shift_rows use up most of our time.  Careful!
00253 void prob_matrix::xfer(unsigned dst_plane, unsigned src_plane,
00254                        unsigned row_dst, unsigned col_dst,
00255                        unsigned row_src, unsigned col_src,
00256                        double prob)
00257 {
00258     double &src = val(src_plane, row_src, col_src);
00259     if (src != 0.0) {
00260         double diff = src * prob;
00261         src -= diff;
00262 
00263         // This is here for drain.
00264         if (col_dst >= cols)
00265             col_dst = cols - 1;
00266         if (row_dst >= rows)
00267             row_dst = rows - 1;
00268 
00269         val(dst_plane, row_dst, col_dst) += diff;
00270 
00271         debug(("Shifted %4.3g from %s(%u,%u) to %s(%u,%u)\n",
00272                diff, src_plane == NEITHER_SLOWED ? ""
00273                : src_plane == A_SLOWED ? "[A_SLOWED]"
00274                : src_plane == B_SLOWED ? "[B_SLOWED]"
00275                : src_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
00276                row_src, col_src,
00277                dst_plane == NEITHER_SLOWED ? ""
00278                : dst_plane == A_SLOWED ? "[A_SLOWED]"
00279                : dst_plane == B_SLOWED ? "[B_SLOWED]"
00280                : dst_plane == BOTH_SLOWED ? "[BOTH_SLOWED]" : "INVALID",
00281                row_dst, col_dst));
00282     }
00283 }
00284 
00285 void prob_matrix::shift_cols(unsigned dst, unsigned src,
00286                              unsigned damage, double prob, bool drain)
00287 {
00288     unsigned int row, col;
00289     unsigned int shift = drain ? 1 : 31; // Avoids a branch.
00290 
00291     if (damage >= cols)
00292         damage = cols - 1;
00293 
00294     // Loop backwards so we write drain behind us, for when src == dst.
00295     for (row = rows - 1; row > min_row[src]; row--) {
00296         // These are all going to die (move to col 0).
00297         for (col = 1; col <= damage; col++)
00298             xfer(dst, src, row+(col>>shift), 0, row, col, prob);
00299         for (col = damage+1; col < cols; col++)
00300             xfer(dst, src, row+(damage>>shift), col - damage, row, col, prob);
00301     }
00302 }
00303 
00304 void prob_matrix::shift_rows(unsigned dst, unsigned src,
00305                              unsigned damage, double prob, bool drain)
00306 {
00307     unsigned int row, col;
00308     unsigned int shift = drain ? 1 : 31; // Avoids a branch.
00309 
00310     if (damage >= rows)
00311         damage = rows - 1;
00312 
00313     // Loop downwards so if we drain, we write behind us.
00314     for (col = cols - 1; col > min_col[src]; col--) {
00315         // These are all going to die (move to row 0).
00316         for (row = 1; row <= damage; row++)
00317             xfer(dst, src, 0, col+(row>>shift), row, col, prob);
00318         for (row = damage+1; row < rows; row++)
00319             xfer(dst, src, row - damage, col+(damage>>shift), row, col, prob);
00320     }
00321 }
00322 
00323 // Shift prob_matrix to reflect probability 'hit_chance'
00324 // that damage (up to) 'damage' is done to 'b'.
00325 void prob_matrix::receive_blow_b(unsigned damage, unsigned slow_damage, double hit_chance,
00326                                  bool a_slows, bool a_drains)
00327 {
00328     int src, dst;
00329 
00330     // Walk backwards so we don't copy already-copied matrix planes.
00331     for (src = 3; src >=0; src--) {
00332         unsigned int actual_damage;
00333 
00334         if (!plane[src])
00335             continue;
00336 
00337         // If A slows us, we go from 0=>2, 1=>3, 2=>2 3=>3.
00338         if (a_slows)
00339             dst = (src|2);
00340         else
00341             dst = src;
00342 
00343         // A is slow in planes 1 and 3.
00344         if (src & 1)
00345             actual_damage = slow_damage;
00346         else
00347             actual_damage = damage;
00348 
00349         shift_cols(dst, src, actual_damage, hit_chance, a_drains);
00350         if (min_col[src] < damage)
00351             min_col[dst] = 0;
00352         else if (min_col[src] - damage < min_col[dst])
00353             min_col[dst] = min_col[src] - damage;
00354         if (min_row[src] < min_row[dst])
00355             min_row[dst] = min_row[src];
00356     }
00357 }
00358 
00359 // We lied: actually did less damage, adjust matrix.
00360 void prob_matrix::remove_stone_distortion_a(unsigned damage, unsigned slow_damage,
00361                                             unsigned b_hp)
00362 {
00363     for (int p = 0; p < 4; p++) {
00364         if (!plane[p])
00365             continue;
00366 
00367         // A is slow in planes 1 and 3.
00368         if (p & 1) {
00369             if (b_hp > slow_damage)
00370                 for (unsigned int row = 0; row < rows; row++)
00371                     xfer(p, p, row, b_hp - slow_damage, row, 0, 1.0);
00372         } else {
00373             if (b_hp > damage)
00374                 for (unsigned int row = 0; row < rows; row++)
00375                     xfer(p, p, row, b_hp - damage, row, 0, 1.0);
00376         }
00377     }
00378 }
00379 
00380 void prob_matrix::remove_stone_distortion_b(unsigned damage, unsigned slow_damage,
00381                                             unsigned a_hp)
00382 {
00383     for (int p = 0; p < 4; p++) {
00384         if (!plane[p])
00385             continue;
00386 
00387         // B is slow in planes 2 and 3.
00388         if (p & 2) {
00389             if (a_hp > slow_damage)
00390                 for (unsigned int col = 0; col < cols; col++)
00391                     xfer(p, p, a_hp - slow_damage, col, 0, col, 1.0);
00392         } else {
00393             if (a_hp > damage)
00394                 for (unsigned int col = 0; col < cols; col++)
00395                     xfer(p, p, a_hp - damage, col, 0, col, 1.0);
00396         }
00397     }
00398 }
00399 
00400 void prob_matrix::extract_results(std::vector<double> summary_a[2],
00401                                   std::vector<double> summary_b[2])
00402 {
00403     unsigned int p, row, col;
00404 
00405     summary_a[0] = std::vector<double>(rows);
00406     summary_b[0] = std::vector<double>(cols);
00407 
00408     if (plane[A_SLOWED])
00409         summary_a[1] = std::vector<double>(rows);
00410     if (plane[B_SLOWED])
00411         summary_b[1] = std::vector<double>(cols);
00412 
00413     for (p = 0; p < 4; p++) {
00414         int dst_a, dst_b;
00415         if (!plane[p])
00416             continue;
00417 
00418         // A is slow in planes 1 and 3.
00419         dst_a = (p & 1);
00420         // B is slow in planes 2 and 3.
00421         dst_b = !!(p & 2);
00422         for (row = 0; row < rows; row++) {
00423             for (col = 0; col < cols; col++) {
00424                 summary_a[dst_a][row] += val(p, row, col);
00425                 summary_b[dst_b][col] += val(p, row, col);
00426             }
00427         }
00428     }
00429 }
00430 
00431 // What's the chance one is dead?
00432 double prob_matrix::dead_prob() const
00433 {
00434     unsigned int p, row, col;
00435     double prob = 0.0;
00436 
00437     for (p = 0; p < 4; p++) {
00438         if (!plane[p])
00439             continue;
00440         // We might count 0,0 twice, but that is always 0 anyway.
00441         for (row = min_row[p]; row < rows; row++)
00442             prob += val(p, row, 0);
00443         for (col = min_col[p]; col < cols; col++)
00444             prob += val(p, 0, col);
00445     }
00446     return prob;
00447 }
00448 
00449 // Shift matrix to reflect probability 'hit_chance'
00450 // that damage (up to) 'damage' is done to 'a'.
00451 void prob_matrix::receive_blow_a(unsigned damage, unsigned slow_damage, double hit_chance,
00452                                  bool b_slows, bool b_drains)
00453 {
00454     int src, dst;
00455 
00456     // Walk backwards so we don't copy already-copied matrix planes.
00457     for (src = 3; src >=0; src--) {
00458         unsigned actual_damage;
00459 
00460         if (!plane[src])
00461             continue;
00462 
00463         // If B slows us, we go from 0=>1, 1=>1, 2=>3 3=>3.
00464         if (b_slows)
00465             dst = (src|1);
00466         else
00467             dst = src;
00468 
00469         // B is slow in planes 2 and 3.
00470         if (src & 2)
00471             actual_damage = slow_damage;
00472         else
00473             actual_damage = damage;
00474 
00475         shift_rows(dst, src, actual_damage, hit_chance, b_drains);
00476         if (min_row[src] < damage)
00477             min_row[dst] = 0;
00478         else if (min_row[src] - damage < min_row[dst])
00479             min_row[dst] = min_row[src] - damage;
00480         if (min_col[src] < min_col[dst])
00481             min_col[dst] = min_col[src];
00482     }
00483 }
00484 
00485 } // end anon namespace
00486 
00487 unsigned combatant::hp_dist_size(const battle_context::unit_stats &u, const combatant *prev)
00488 {
00489     // Our summary must be as big as previous one.
00490     if (prev) {
00491         return prev->hp_dist.size();
00492     }
00493 
00494     // If this unit drains, HP can increase, so alloc full array.
00495     if (u.drains) {
00496         return u.max_hp + 1;
00497     }
00498     return u.hp+1;
00499 }
00500 
00501 combatant::combatant(const battle_context::unit_stats &u, const combatant *prev)
00502     : hp_dist(hp_dist_size(u, prev)),
00503       u_(u),
00504       hit_chances_(u.num_blows, u.chance_to_hit / 100.0)
00505 {
00506     // We inherit current state from previous combatant.
00507     if (prev) {
00508         summary[0] = prev->summary[0];
00509         summary[1] = prev->summary[1];
00510         poisoned = prev->poisoned;
00511         untouched = prev->untouched;
00512         slowed = prev->slowed;
00513     } else {
00514         untouched = 1.0;
00515         poisoned = u.is_poisoned ? 1.0 : 0.0;
00516         slowed = u.is_slowed ? 1.0 : 0.0;
00517     }
00518 }
00519 
00520 // Copy constructor (except use this copy of unit_stats)
00521 combatant::combatant(const combatant &that, const battle_context::unit_stats &u)
00522     : hp_dist(that.hp_dist), untouched(that.untouched), poisoned(that.poisoned), slowed(that.slowed), u_(u), hit_chances_(that.hit_chances_)
00523 {
00524         summary[0] = that.summary[0];
00525         summary[1] = that.summary[1];
00526 }
00527 
00528 
00529 
00530 // For swarm, whether we get an attack depends on HP distribution
00531 // from previous combat.  So we roll this into our P(hitting),
00532 // since no attack is equivalent to missing.
00533 void combatant::adjust_hitchance()
00534 {
00535     if (summary[0].empty() || u_.swarm_min == u_.swarm_max)
00536         return;
00537 
00538     hit_chances_ = std::vector<double>(u_.swarm_max);
00539     double alive_prob;
00540 
00541     if (summary[1].empty())
00542         alive_prob = 1 - summary[0][0];
00543     else
00544         alive_prob = 1 - summary[0][0] - summary[1][0];
00545 
00546     unsigned int i;
00547     for (i = 1; i <= u_.max_hp; i++) {
00548         double prob = 0.0;
00549         if(i < summary[0].size()) {
00550             prob = summary[0][i];
00551         }
00552         if (!summary[1].empty())
00553             prob += summary[1][i];
00554         for (unsigned int j = 0; j < u_.swarm_min + (u_.swarm_max -
00555                 static_cast<double>(u_.swarm_min)) * u_.hp / u_.max_hp; j++)
00556 
00557             hit_chances_[j] += prob * u_.chance_to_hit / 100.0 / alive_prob;
00558     }
00559 
00560     debug(("\nhit_chances_ (base %u%%):", u_.chance_to_hit));
00561     for (i = 0; i < u_.swarm_max; i++)
00562         debug((" %.2f", hit_chances_[i] * 100.0 + 0.5));
00563     debug(("\n"));
00564 }
00565 
00566 // Minimum HP we could possibly have.
00567 unsigned combatant::min_hp() const
00568 {
00569     if (summary[0].empty())
00570         return u_.hp;
00571 
00572     // We don't handle this (yet).
00573     assert(summary[1].empty());
00574 
00575     unsigned int i;
00576     for (i = 0; summary[0][i] == 0; i++) {};
00577     return i;
00578 }
00579 
00580 // Combat without chance of death, berserk, slow or drain is simple.
00581 void combatant::no_death_fight(combatant &opp)
00582 {
00583     if (summary[0].empty()) {
00584         // Starts with a known HP, so Pascal's triangle.
00585         summary[0] = std::vector<double>(u_.hp+1);
00586         summary[0][u_.hp] = 1.0;
00587         for (unsigned int i = 0; i < opp.hit_chances_.size(); i++) {
00588             for (int j = i; j >= 0; j--) {
00589                 double move = summary[0][u_.hp - j * opp.u_.damage] * opp.hit_chances_[i];
00590                 summary[0][u_.hp - j * opp.u_.damage] -= move;
00591                 summary[0][u_.hp - (j+1) * opp.u_.damage] += move;
00592             }
00593         }
00594     } else {
00595         // HP could be spread anywhere, iterate through whole thing.
00596         for (unsigned int i = 0; i < opp.hit_chances_.size(); i++) {
00597             for (unsigned int j = opp.u_.damage; j <= u_.hp; j++) {
00598                 double move = summary[0][j] * opp.hit_chances_[i];
00599                 summary[0][j] -= move;
00600                 summary[0][j - opp.u_.damage] += move;
00601             }
00602         }
00603     }
00604 
00605     if (opp.summary[0].empty()) {
00606         // Starts with a known HP, so Pascal's triangle.
00607         opp.summary[0] = std::vector<double>(opp.u_.hp+1);
00608         opp.summary[0][opp.u_.hp] = 1.0;
00609         for (unsigned int i = 0; i < hit_chances_.size(); i++) {
00610             for (int j = i; j >= 0; j--) {
00611                 double move = opp.summary[0][opp.u_.hp - j * u_.damage] * hit_chances_[i];
00612                 opp.summary[0][opp.u_.hp - j * u_.damage] -= move;
00613                 opp.summary[0][opp.u_.hp - (j+1) * u_.damage] += move;
00614             }
00615         }
00616     } else {
00617         // HP could be spread anywhere, iterate through whole thing.
00618         for (unsigned int i = 0; i < hit_chances_.size(); i++) {
00619             for (unsigned int j = u_.damage; j <= opp.u_.hp; j++) {
00620                 double move = opp.summary[0][j] * hit_chances_[i];
00621                 opp.summary[0][j] -= move;
00622                 opp.summary[0][j - u_.damage] += move;
00623             }
00624         }
00625     }
00626 }
00627 
00628 // Combat with <= 1 strike each is simple, too.
00629 void combatant::one_strike_fight(combatant &opp)
00630 {
00631     if (opp.summary[0].empty()) {
00632         opp.summary[0] = std::vector<double>(opp.u_.hp+1);
00633         if (hit_chances_.size() == 1) {
00634             opp.summary[0][opp.u_.hp] = 1.0 - hit_chances_[0];
00635             opp.summary[0][maximum<int>(opp.u_.hp - u_.damage, 0)] = hit_chances_[0];
00636         } else {
00637             assert(hit_chances_.size() == 0);
00638             opp.summary[0][opp.u_.hp] = 1.0;
00639         }
00640     } else {
00641         if (hit_chances_.size() == 1) {
00642             for (unsigned int i = 1; i < opp.summary[0].size(); i++) {
00643                 double move = opp.summary[0][i] * hit_chances_[0];
00644                 opp.summary[0][i] -= move;
00645                 opp.summary[0][maximum<int>(i - u_.damage, 0)] += move;
00646             }
00647         }
00648     }
00649 
00650     // If we killed opponent, it won't attack us.
00651     double opp_alive_prob = 1.0 - opp.summary[0][0];
00652     if (summary[0].empty()) {
00653         summary[0] = std::vector<double>(u_.hp+1);
00654         if (opp.hit_chances_.size() == 1) {
00655             summary[0][u_.hp] = 1.0 - opp.hit_chances_[0] * opp_alive_prob;
00656             summary[0][maximum<int>(u_.hp - opp.u_.damage, 0)] = opp.hit_chances_[0] * opp_alive_prob;
00657         } else {
00658             assert(opp.hit_chances_.size() == 0);
00659             summary[0][u_.hp] = 1.0;
00660         }
00661     } else {
00662         if (opp.hit_chances_.size() == 1) {
00663             for (unsigned int i = 1; i < summary[0].size(); i++) {
00664                 double move = summary[0][i] * opp.hit_chances_[0] * opp_alive_prob;
00665                 summary[0][i] -= move;
00666                 summary[0][maximum<int>(i - opp.u_.damage, 0)] += move;
00667             }
00668         }
00669     }
00670 }
00671 
00672 void combatant::complex_fight(combatant &opp, unsigned int rounds)
00673 {
00674     prob_matrix m(hp_dist.size()-1, opp.hp_dist.size()-1,
00675                   u_.slows && !opp.u_.is_slowed, opp.u_.slows && !u_.is_slowed,
00676                   u_.hp, opp.u_.hp, summary, opp.summary);
00677 
00678     unsigned max_attacks = maximum(hit_chances_.size(), opp.hit_chances_.size());
00679 
00680     debug(("A gets %u attacks, B %u\n", hit_chances_.size(), opp.hit_chances_.size()));
00681 
00682     unsigned int a_damage = u_.damage, a_slow_damage = u_.slow_damage;
00683     unsigned int b_damage = opp.u_.damage, b_slow_damage = opp.u_.slow_damage;
00684 
00685     // To simulate stoning, we set to amount which kills, and re-adjust after.
00686     //! @todo FIXME: This doesn't work for rolling calculations, just first battle.
00687     if (u_.stones)
00688         a_damage = a_slow_damage = opp.u_.max_hp;
00689     if (opp.u_.stones)
00690         b_damage = b_slow_damage = u_.max_hp;
00691 
00692     do {
00693         for (unsigned int i = 0; i < max_attacks; i++) {
00694             if (i < hit_chances_.size()) {
00695                 debug(("A strikes\n"));
00696                 m.receive_blow_b(a_damage, a_slow_damage, hit_chances_[i],
00697                                  u_.slows && !opp.u_.is_slowed, u_.drains);
00698                 m.dump();
00699             }
00700             if (i < opp.hit_chances_.size()) {
00701                 debug(("B strikes\n"));
00702                 m.receive_blow_a(b_damage, b_slow_damage, opp.hit_chances_[i],
00703                                  opp.u_.slows && !u_.is_slowed, opp.u_.drains);
00704                 m.dump();
00705             }
00706         }
00707 
00708         debug(("Combat ends:\n"));
00709         m.dump();
00710     } while (--rounds && m.dead_prob() < 0.99);
00711 
00712     if (u_.stones)
00713         m.remove_stone_distortion_a(u_.damage, u_.slow_damage, opp.u_.hp);
00714     if (opp.u_.stones)
00715         m.remove_stone_distortion_b(opp.u_.damage, opp.u_.slow_damage, u_.hp);
00716 
00717     // We extract results separately, then combine.
00718     m.extract_results(summary, opp.summary);
00719 }
00720 
00721 // Two man enter.  One man leave!
00722 // ... Or maybe two.  But definitely not three.
00723 // Of course, one could be a woman.  Or both.
00724 // And neither could be human, too.
00725 // Um, ok, it was a stupid thing to say.
00726 void combatant::fight(combatant &opp)
00727 {
00728     unsigned int rounds = maximum<unsigned int>(u_.rounds, opp.u_.rounds);
00729 
00730     // If defender has firststrike and we don't, reverse.
00731     if (opp.u_.firststrike && !u_.firststrike) {
00732         opp.fight(*this);
00733         return;
00734     }
00735 
00736 #ifdef ATTACK_PREDICTION_DEBUG
00737     printf("A:\n");
00738     u_.dump();
00739     printf("B:\n");
00740     opp.u_.dump();
00741 #endif
00742 
00743     // If we've fought before and we have swarm, we must adjust cth array.
00744     adjust_hitchance();
00745     opp.adjust_hitchance();
00746 
00747 #if 0
00748     std::vector<double> prev = summary[0], opp_prev = opp.summary[0];
00749     complex_fight(opp, 1);
00750     std::vector<double> res = summary[0], opp_res = opp.summary[0];
00751     summary[0] = prev;
00752     opp.summary[0] = opp_prev;
00753 #endif
00754 
00755     // Optimize the simple cases.
00756     if (rounds == 1 && !u_.slows && !opp.u_.slows &&
00757         !u_.drains && !opp.u_.drains && !u_.stones && !opp.u_.stones &&
00758         summary[1].empty() && opp.summary[1].empty()) {
00759         if (hit_chances_.size() <= 1 && opp.hit_chances_.size() <= 1) {
00760             one_strike_fight(opp);
00761         } else if (hit_chances_.size() * u_.damage < opp.min_hp() &&
00762             opp.hit_chances_.size() * opp.u_.damage < min_hp()) {
00763             no_death_fight(opp);
00764         } else {
00765             complex_fight(opp, rounds);
00766         }
00767     } else {
00768             complex_fight(opp, rounds);
00769     }
00770 
00771 #if 0
00772     assert(summary[0].size() == res.size());
00773     assert(opp.summary[0].size() == opp_res.size());
00774     for (unsigned int i = 0; i < summary[0].size(); i++) {
00775         if (fabs(summary[0][i] - res[i]) > 0.000001) {
00776             std::cerr << "Mismatch for " << i << " hp: " << summary[0][i] << " should have been " << res[i] << "\n";
00777             assert(0);
00778         }
00779     }
00780     for (unsigned int i = 0; i < opp.summary[0].size(); i++) {
00781         if (fabs(opp.summary[0][i] - opp_res[i])> 0.000001) {
00782             std::cerr << "Mismatch for " << i << " hp: " << opp.summary[0][i] << " should have been " << opp_res[i] << "\n";
00783             assert(0);
00784         }
00785     }
00786 #endif
00787 
00788     // Combine summary into distribution.
00789     if (summary[1].empty())
00790         hp_dist = summary[0];
00791     else {
00792         for (unsigned int i = 0; i < hp_dist.size(); i++)
00793             hp_dist[i] = summary[0][i] + summary[1][i];
00794     }
00795     if (opp.summary[1].empty())
00796         opp.hp_dist = opp.summary[0];
00797     else {
00798         for (unsigned int i = 0; i < opp.hp_dist.size(); i++)
00799             opp.hp_dist[i] = opp.summary[0][i] + opp.summary[1][i];
00800     }
00801 
00802     // Make sure we don't try to access the vectors out of bounds,
00803     // drain increases HPs so we determine the number of HP here
00804     // and make sure it stays within bounds
00805     const unsigned int hp = minimum<unsigned int>(u_.hp, hp_dist.size() - 1);
00806     const unsigned int opp_hp = minimum<unsigned int>(opp.u_.hp, opp.hp_dist.size() - 1);
00807 
00808     // Chance that we / they were touched this time.
00809     double touched = untouched - hp_dist[hp];
00810     double opp_touched = opp.untouched - opp.hp_dist[opp_hp];
00811     if (opp.u_.poisons)
00812         poisoned += (1 - poisoned) * touched;
00813     if (u_.poisons)
00814         opp.poisoned += (1 - opp.poisoned) * opp_touched;
00815 
00816     if (opp.u_.slows)
00817         slowed += (1 - slowed) * touched;
00818     if (u_.slows)
00819         opp.slowed += (1 - opp.slowed) * opp_touched;
00820 
00821     //! @todo FIXME: This is approximate: we could drain, then get hit.
00822     untouched = hp_dist[hp];
00823     opp.untouched = opp.hp_dist[opp_hp];
00824 }
00825 
00826 double combatant::average_hp(unsigned int healing) const
00827 {
00828     double total = 0;
00829 
00830     // Since sum of probabilities is 1.0, we can just tally weights.
00831     for (unsigned int i = 1; i < hp_dist.size(); i++) {
00832         total += hp_dist[i] * minimum<unsigned int>(i + healing, u_.max_hp);
00833     }
00834     return total;
00835 }
00836 
00837 #if defined(BENCHMARK) || defined(CHECK)
00838 // We create a significant number of nasty-to-calculate units,
00839 // and test each one against the others.
00840 #define NUM_UNITS 50
00841 
00842 // Stolen from glibc headers sys/time.h
00843 #define timer_sub(a, b, result)                           \
00844   do {                                        \
00845     (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;                 \
00846     (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;                  \
00847     if ((result)->tv_usec < 0) {                          \
00848       --(result)->tv_sec;                             \
00849       (result)->tv_usec += 1000000;                       \
00850     }                                         \
00851   } while (0)
00852 
00853 #ifdef CHECK
00854 void combatant::print(const char label[], unsigned int battle) const
00855 {
00856     printf("#%u: %s: %u %u %u %2g%% ", battle,
00857            label, damage_, base_num_attacks_, hp_, base_hit_chance_*100.0);
00858     if (drains_)
00859         printf("drains,");
00860     if (slows_)
00861         printf("slows,");
00862     if (berserk_)
00863         printf("berserk,");
00864     if (swarm_)
00865         printf("swarm,");
00866     if (firststrike_)
00867         printf("firststrike,");
00868     printf("maxhp=%u ", hp_dist.size()-1);
00869     printf(" %.2f", untouched);
00870     for (unsigned int i = 0; i < hp_dist.size(); i++)
00871         printf(" %.2f", hp_dist[i] * 100);
00872     printf("\n");
00873 }
00874 #else  // ... BENCHMARK
00875 void combatant::print(const char label[], unsigned int battle) const
00876 {
00877 }
00878 #endif
00879 
00880 static void run(unsigned specific_battle)
00881 {
00882     // N^2 battles
00883     struct combatant *u[NUM_UNITS];
00884     unsigned int i, j, k, battle = 0;
00885     struct timeval start, end, total;
00886 
00887     for (i = 0; i < NUM_UNITS; i++) {
00888         unsigned hp = 1 + ((i*3)%23);
00889         u[i] = new combatant(hp, hp + (i+7)%17, false);
00890         u[i]->set_weapon((i % 4) + 1, (i % 9) == 0, (i % 5) == 0,
00891                          ((i+4) % 4) == 0,
00892                          ((i+3) % 5) == 0);
00893         u[i]->set_effectiveness((i % 7) + 2, 0.3 + (i % 6)*0.1, (i % 8) == 0);
00894     }
00895 
00896     gettimeofday(&start, NULL);
00897     for (i = 0; i < NUM_UNITS; i++) {
00898         for (j = 0; j < NUM_UNITS; j++) {
00899             if (i == j)
00900                 continue;
00901             for (k = 0; k < NUM_UNITS; k++) {
00902                 double untouched;
00903                 if (i == k || j == k)
00904                     continue;
00905                 battle++;
00906                 if (specific_battle && battle != specific_battle)
00907                     continue;
00908                 u[j]->fight(*u[i]);
00909                 // We need this here, because swarm means
00910                 // out num hits can change.
00911                 u[i]->set_effectiveness((i % 7) + 2, 0.3 + (i % 6)*0.1,
00912                                         (i % 8) == 0);
00913                 u[k]->fight(*u[i]);
00914                 u[i]->print("Defender", battle);
00915                 u[j]->print("Attacker #1", battle);
00916                 u[k]->print("Attacker #2", battle);
00917                 u[i]->reset();
00918                 u[j]->reset();
00919                 u[k]->reset();
00920             }
00921         }
00922     }
00923     gettimeofday(&end, NULL);
00924 
00925     timer_sub(&end, &start, &total);
00926 
00927 #ifdef BENCHMARK
00928     printf("Total time for %i combats was %lu.%06lu\n",
00929            NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2), total.tv_sec, total.tv_usec);
00930     printf("Time per calc = %li us\n",
00931            ((end.tv_sec-start.tv_sec)*1000000 + (end.tv_usec-start.tv_usec))
00932            / (NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2)));
00933 #else
00934     printf("Total combats: %i\n", NUM_UNITS*(NUM_UNITS-1)*(NUM_UNITS-2));
00935 #endif
00936 
00937     for (i = 0; i < NUM_UNITS; i++) {
00938         delete u[i];
00939     }
00940 
00941     exit(0);
00942 }
00943 
00944 static combatant *parse_unit(char ***argv,
00945                              unsigned *damagep = NULL,
00946                              double *hit_chancep = NULL,
00947                              bool *slowsp = NULL)
00948 {
00949     unsigned damage, num_attacks, hp, max_hp, hit_chance;
00950     bool slows, slowed, drains, berserk, swarm, firststrike;
00951     combatant *u;
00952 
00953     damage = atoi((*argv)[1]);
00954     num_attacks = atoi((*argv)[2]);
00955     hp = max_hp = atoi((*argv)[3]);
00956     hit_chance = atoi((*argv)[4]);
00957     slows = false;
00958     slowed = false;
00959     drains = false;
00960     berserk = false;
00961     swarm = false;
00962     firststrike = false;
00963 
00964     if (damagep)
00965         *damagep = damage;
00966     if (hit_chancep)
00967         *hit_chancep = hit_chance/100.0;
00968     if (slowsp)
00969         *slowsp = slows;
00970 
00971     if ((*argv)[5] && atoi((*argv)[5]) == 0) {
00972         char *max = strstr((*argv)[5], "maxhp=");
00973 
00974         if (max) {
00975             max_hp = atoi(max + strlen("maxhp="));
00976             if (max_hp < hp) {
00977                 fprintf(stderr, "maxhp must be > hitpoints");
00978                 exit(1);
00979             }
00980         }
00981         if (strstr((*argv)[5], "drain")) {
00982             if (!max) {
00983                 fprintf(stderr, "drain needs maxhp set");
00984                 exit(1);
00985             }
00986             drains = true;
00987         }
00988         if (strstr((*argv)[5], "slows"))
00989             slows = true;
00990         if (strstr((*argv)[5], "slowed"))
00991             slowed = true;
00992         if (strstr((*argv)[5], "berserk"))
00993             berserk = true;
00994         if (strstr((*argv)[5], "firststrike"))
00995             firststrike = true;
00996         if (strstr((*argv)[5], "swarm")) {
00997             if (!max) {
00998                 fprintf(stderr, "swarm needs maxhp set");
00999                 exit(1);
01000             }
01001             swarm = true;
01002         }
01003         *argv += 5;
01004     } else {
01005         *argv += 4;
01006     }
01007     u = new combatant(hp, max_hp, slowed, true);
01008     u->set_weapon(num_attacks, drains, berserk, swarm, firststrike);
01009     u->set_effectiveness(damage, hit_chance/100.0, slows);
01010     return u;
01011 }
01012 
01013 int main(int argc, char *argv[])
01014 {
01015     combatant *def, *att[20];
01016     double hit_chance;
01017     unsigned damage;
01018     bool slows;
01019     unsigned int i;
01020 
01021     if (argc < 3)
01022         run(argv[1] ? atoi(argv[1]) : 0);
01023 
01024     if (argc < 9) {
01025         fprintf(stderr,"Usage: %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>] ...",
01026                 argv[0]);
01027         exit(1);
01028     }
01029 
01030     def = parse_unit(&argv, &damage, &hit_chance, &slows);
01031     for (i = 0; argv[1]; i++)
01032         att[i] = parse_unit(&argv);
01033     att[i] = NULL;
01034 
01035     for (i = 0; att[i]; i++) {
01036         // In case defender has swarm, effectiveness changes.
01037         debug(("Fighting next attacker\n"));
01038         def->set_effectiveness(damage, hit_chance, slows);
01039         att[i]->fight(*def);
01040     }
01041 
01042     def->print("Defender", 0);
01043     for (i = 0; att[i]; i++)
01044         att[i]->print("Attacker", 0);
01045 
01046     delete def;
01047     for (i = 0; att[i]; i++)
01048         delete att[i];
01049 
01050     return 0;
01051 }
01052 #endif // Standalone program

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs