Hello,

some time ago I've worked on improving the "Damage Calculations" dialog that is shown when the user is considering to attack an enemy unit. I've also tweaked the code to improve the selection of the defense weapon when a unit is attacked. Unfortunatly, I had to stop development for a while since I've been kept busy with other projects (my apologies to Rusty for disappearing). Now that I've got
some time, here's a resume of the modifications:


--------------------------------------------------
Damage Calculations dialog:

The new dialog shows the damage computations and the hit points distribution of
the units after the battle. Here's a sketch:

Damage Calculations

           Attacker                                     Defender
Base damage 10 Base damage 4
Morning                     +25%          Morning                       -25%
Attacker resistance vs blade * 0.5
Damage per strike           12
Summary                     12-3 (40%)    Damage per strike             2
Chance of being unscathed 9.0% Summary 2-2 (70%) Chance of being unscathed 21.6%


   Expected Battle Result (hp)                  Expected Battle Result (hp)
------------------------------------- -------------------------------------- | 51|== | 9.0%| | 39|==== |21.6%| | 49|=========== |42.0%| | 27|========= |43.2%| | 47|============ |49.0%| | 15|====== |28.8%| ------------------------------------- | 3|= | 6.4%| --------------------------------------


--------------------------------------------------
Battle simulator:

Rusty made a great battle simulator that can predict all the possible outcomes
of a battle. The simulator handles drain, slow, first strike, berserk and
swarm. I use this simulator to obtain the hit points distribution of the units
after the battle. However, the simulator can also be used by the AI to
accuratly predict the outcome of a battle (rather than relying on 50 dry runs
like it is done currently). Remarkably, Rusty's simulator can "chain" many
attacks against a defender unit. Example: pikeman attacks wolfrider, then
shaman attacks wolfrider. After the first battle, the wolfrider will have
different possible HP values (e.g. 24, 10, 0 [bogus values]). The simulator
takes those values (and slow state) into account when it computes the possible
states after the second battle. The final HP values of the wolfrider could
thus be (24, 15, 10, 5, 0). Since the simulator computes the probability that
each unit remain unscathed (not touched by any blow), it is possible to get
the probability of a unit getting poisoned/slowed by an attack.

Rusty spent much time optimizing his simulator. I *believe* that using the
simulator is faster than performing 50 dry runs, but I've conducted no
benchmarks. Rusty is the guy who would know.

Possible caveat: due to the necessity of handling "slow + drain" properly,
Rusty had to use a slower implementation that involves a two-dimensions
matrix. The length and width of this matrix is determined by the maximum HP of the units that fight. So, while the computations are fast for units that have
~75 HP (?), they will get much slower as the health of the units increase
[O(n^2)]. Hence, if Wesnoth is to allow units that have lots of HP, the
simulator will probably be too slow for heavy usage by the AI (perhaps the
current approximation code can be used in those cases). Also, the simulator is
slower to simulate battles involving 'berserk'.


--------------------------------------------------
"battle_context" object:

To create the new dialog, I had to get the HP distributions and present the
different factors influencing the damage computations. In Wesnoth SVN code (not my code), evaluate_battle_stats() is used to get the strings explaining those factors and get the XX/XX/XX probability outcome string. However, I had a number
of issues with evaluate_battle_stats() and its associated "battle_stats"
object:

Firstly, before I designed the new dialog, I wanted to improve the heuristic
used to select the weapon used on defense. evaluate_battle_stats() does not make
this easy since some information such as the amount of damage dealt by the
attacker / defender are computed after the defense weapon has been chosen.

Secondly, the information contained in "battle_stats" is derived from the
current tactical situation (in other words, evaluate_battle_stats() looks at the map and sees that it is day, that a leadership bonus applies to the attacker, etc). However, I considered using Rusty's simulator eventually to compute the outcome of many attacks on a single defender (when the AI is planning). For the
second and subsequent attacks, the tactical situation is not the same as the
"former" tactical situation (perhaps a unit will have leveled and provide
leadership, etc). Thus, I wanted to be able to manually set some factors like the leadership bonus and the time of day both for the attacker and the defender. Currently, evaluate_battle_stats() provides support for overriding the terrain
used for the attacker (attacker_terrain_override) but it is not possible to
override other factors.

Finally, I wasn't fond of the way evaluate_battle_stats() mixes GUI stuff with the battle computations. If 'strings' isn't NULL, evaluate_battle_stats() fills it out with strings describing the computations. Of course, this approach may avoid code duplication. However I feel it would be cleaner if there was a clear
separation between the GUI and the actual computations.

For these reasons, at the time I started writing the new dialog code, I used
the "battle_context" object I had made to fix the (perceived) issues mentionned above. "battle_context" is designed to replace "battle_stats" altogether. Here's
a quick rundown of the API:

The "battle_context" object has some fields that are mandatory and some fields
that are optional. Initially, the user sets the mandatory field with

void init(const gamemap& map, std::vector<team>& teams, std::map<gamemap::location,unit>& units, const gamestatus& status, const gamemap::location& attacker_loc, const gamemap::location& defender_loc,
         const attack_type& attacker_weapon);

The arguments are equivalent to the arguments used with evaluate_battle_stats().

Then, the user may optionally specify the leadership/slowed/poisoned/time of
day/backstab state of the attacker and the defender, by setting those fields
directly in the "battle_context" object.

Afterward, the user calls compute_battle_stats() to compute the battle
statistics. This method first looks at the optional fields and obtains the value of any unitialized field by observing the current situation on the battlefield, like evaluate_battle_stats() does. Then, the method determines which weapon will be used on defense, if any. To do this, the method generates a "battle_context" object for each defense weapon that matches the attacker weapon's range. Each of those "battle_context" objects computes the stats of the attacker and defender
weapons used (damage dealt, number of strikes, drain amount, etc).

Then, an heuristic function is used to determine which is the best defense
weapon. The heuristic compares the current "best" defense weapon with the next available defense weapon and specifies which is the best weapon. At the end of the process, the original "battle_context" object contains the complete stats of the battle with the best defense weapon. Since the heuristic function has access to the complete battle stats when it compares two defense weapons, it can make an enlightened choice. For instance, the heuristic function could elicit not to
use a low-damage poisoning attack if the attacker is already poisoned or is
undead. I know that some developers do not want a clever heuristic though :)
For now, the heuristic of "battle_context" is compatible with the heuristic used
in evaluate_battle_stats().

Obviously the "battle_context" object is slower than evaluate_battle_stats() due to the extra work to select the defender's weapon. However, in most cases, only one defense weapon is available. I expect "battle_context" to be perhaps 2 times
slower than evaluate_battle_stats() (just guessing, I didn't benchmark).
"battle_context" doesn't fill out 'strings' with the strings describing the
compution it makes. However, all the necessary information can be obtained by
looking at the fields of "battle_context".

Last minute note: "battle_context" no longer exactly matches the actual
computations done by evaluate_battle_stats() since Xan has been modifying the code extensively today. I've updated my code so that it compiles with the new interfaces. However, some of Xan's stuff is a work in progress and is likely to change in the next few days. Because of this I didn't update my code to reflect
Xan's changes on backstab, charge, swarm and steadfast.


--------------------------------------------------
"battle_prediction_preview_pane" object:

I modified attack_enemy() to compute the battle stats with "attack_context", in addition to the usual evaluate_battle_stats(). The logic doesn't change much:

// If set to 1, the new dialog is shown, otherwise the old dialog is shown.
#if 1
attack_prediction_displayer ap_displayer(gui_, bc_vector);
std::vector<gui::dialog_button> buttons;
buttons.push_back(gui::dialog_button(&ap_displayer, _("Damage Calculations")));
#else
attack_calculations_displayer calc_displayer(gui_,stats);
std::vector<gui::dialog_button> buttons;
buttons.push_back(gui::dialog_button(&calc_displayer,_("Damage Calculations")));
#endif

"battle_prediction_preview_pane" is the object used to show the dialog. It
generates the graphics, obtains the strings describing the computations and
displays the dialog (straightforward code).

Some notes about the graphics. HP values >= initial HP are shown in green, 0 HP
is shown red, otherwise it's yellow. At most 10 lines are displayed in the
graphs. The lines with the highest probability values are retained when the
limit is exceeded. Thus, when two scuttlefishes or berseckers fight, it's
possible that some values with a low probability are not shown.


--------------------------------------------------
Code modifications:

Rusty made a file called "attack_prediction.cpp" that contains his simulator. I've modified the file to extract the header "attack_prediction.hpp" from it.
"Makefile.am" was modified to add "attack_prediction.cpp". The file
"playturn.cpp" was modified to contain the dialog code as described above. The files "actions.{cpp|hpp}" were modified to add the "battle_context" code. The
file "font.hpp" was modified to add a prototype for the function
draw_text_line() (I needed this to draw text in a SDL surface directly).


--------------------------------------------------
Summary:

The damage computations dialog code shouldn't cause problems. My
"battle_context" code probably will, since it aims at replacing existing code.
The question is wheter you guys are happy with evaluate_battle_stats() or if
you'd prefer "battle_context". In the former case, some work would have to be spent on reworking the dialog so that it uses evaluate_battle_stats() (I guess it would be possible to add "battle_context" just for the dialog but that would be a maintenance nightmare). Otherwise, evaluate_battle_stats() would have to be progressively phased out. The functions attack() and attack_analysis::analyze() use it and would need to be updated. In the process, analyze() could make use of
Rusty's simulator to get rid of the dry runs and enhance the accuracy of the
predictions (for instance, the current dry runs do not currently take 'berserk'
into account as far as I can see).

Last minute note: I spoke with Xan regarding my "battle_context" code and it
seems there is hope that it might be integrated. Therefore I've posted my patch on Gna since Xan is updating evaluate_battle_stats() and I'm updating my patch in response to those changes. I'll try to keep my code compiling as the various interfaces are updated. However, the sooner my patch is accepted (or rejected),
the less time I'll spend maintaining :) Hence here is the code. It doesn't
replace "battle_stats" in the AI and attack() yet, but if the developers give me the authorisation, I'll perform the conversion to battle_context thoroughly
in the code.


I await your comments, thanks for your time!
Laurent Birtz


_______________________________________________
Wesnoth-dev mailing list
[email protected]
https://mail.gna.org/listinfo/wesnoth-dev

Reply via email to