The Battle for Wesnoth  1.15.9+dev
statistics.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Manage statistics: recruitments, recalls, kills, losses, etc.
18  */
19 
20 #include "game_board.hpp"
21 #include "statistics.hpp"
22 #include "log.hpp"
23 #include "resources.hpp" // Needed for teams, to get team save_id for a unit
25 #include "team.hpp" // Needed to get team save_id
26 #include "units/unit.hpp"
27 #include "units/types.hpp"
28 
29 #include <cmath>
30 
31 static lg::log_domain log_engine("engine");
32 #define DBG_NG LOG_STREAM(debug, log_engine)
33 #define ERR_NG LOG_STREAM(err, log_engine)
34 
35 namespace {
36 
37 // This variable is true whenever the statistics are mid-scenario.
38 // This means a new scenario shouldn't be added to the master stats record.
39 bool mid_scenario = false;
40 
41 typedef statistics::stats stats;
42 typedef std::map<std::string,stats> team_stats_t;
43 
44 std::string get_team_save_id(const unit & u)
45 {
46  assert(resources::gameboard);
47  return resources::gameboard->get_team(u.side()).save_id_or_number();
48 }
49 
50 struct scenario_stats
51 {
52  explicit scenario_stats(const std::string& name) :
53  team_stats(),
54  scenario_name(name)
55  {}
56 
57  explicit scenario_stats(const config& cfg);
58 
59  config write() const;
60  void write(config_writer &out) const;
61 
62  team_stats_t team_stats;
63  std::string scenario_name;
64 };
65 
66 scenario_stats::scenario_stats(const config& cfg) :
67  team_stats(),
68  scenario_name(cfg["scenario"])
69 {
70  for(const config &team : cfg.child_range("team")) {
71  team_stats[team["save_id"]] = stats(team);
72  }
73 }
74 
76 {
77  config res;
78  res["scenario"] = scenario_name;
79  for(team_stats_t::const_iterator i = team_stats.begin(); i != team_stats.end(); ++i) {
80  res.add_child("team",i->second.write());
81  }
82 
83  return res;
84 }
85 
86 void scenario_stats::write(config_writer &out) const
87 {
88  out.write_key_val("scenario", scenario_name);
89  for(team_stats_t::const_iterator i = team_stats.begin(); i != team_stats.end(); ++i) {
90  out.open_child("team");
91  i->second.write(out);
92  out.close_child("team");
93  }
94 }
95 
96 std::vector<scenario_stats> master_stats;
97 
98 } // end anon namespace
99 
100 static stats &get_stats(const std::string &save_id)
101 {
102  if(master_stats.empty()) {
103  master_stats.emplace_back(std::string());
104  }
105 
106  team_stats_t& team_stats = master_stats.back().team_stats;
107  return team_stats[save_id];
108 }
109 
110 static config write_str_int_map(const stats::str_int_map& m)
111 {
112  config res;
113  for(stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
114  std::string n = std::to_string(i->second);
115  if(res.has_attribute(n)) {
116  res[n] = res[n].str() + "," + i->first;
117  } else {
118  res[n] = i->first;
119  }
120  }
121 
122  return res;
123 }
124 
125 static void write_str_int_map(config_writer &out, const stats::str_int_map& m)
126 {
127  using reverse_map = std::multimap<int, std::string>;
128  reverse_map rev;
129  std::transform(
130  m.begin(), m.end(),
131  std::inserter(rev, rev.begin()),
132  [](const stats::str_int_map::value_type p) {
133  return std::pair(p.second, p.first);
134  }
135  );
136  reverse_map::const_iterator i = rev.begin(), j;
137  while(i != rev.end()) {
138  j = rev.upper_bound(i->first);
139  std::vector<std::string> vals;
140  std::transform(i, j, std::back_inserter(vals), [](const reverse_map::value_type& p) {
141  return p.second;
142  });
143  out.write_key_val(std::to_string(i->first), utils::join(vals));
144  i = j;
145  }
146 }
147 
148 static stats::str_int_map read_str_int_map(const config& cfg)
149 {
150  stats::str_int_map m;
151  for(const config::attribute &i : cfg.attribute_range()) {
152  try {
153  for(const std::string& val : utils::split(i.second)) {
154  m[val] = std::stoi(i.first);
155  }
156  } catch(const std::invalid_argument&) {
157  ERR_NG << "Invalid statistics entry; skipping\n";
158  }
159  }
160 
161  return m;
162 }
163 
164 static config write_battle_result_map(const stats::battle_result_map& m)
165 {
166  config res;
167  for(stats::battle_result_map::const_iterator i = m.begin(); i != m.end(); ++i) {
168  config& new_cfg = res.add_child("sequence");
169  new_cfg = write_str_int_map(i->second);
170  new_cfg["_num"] = i->first;
171  }
172 
173  return res;
174 }
175 
176 static void write_battle_result_map(config_writer &out, const stats::battle_result_map& m)
177 {
178  for(stats::battle_result_map::const_iterator i = m.begin(); i != m.end(); ++i) {
179  out.open_child("sequence");
180  write_str_int_map(out, i->second);
181  out.write_key_val("_num", i->first);
182  out.close_child("sequence");
183  }
184 }
185 
186 static stats::battle_result_map read_battle_result_map(const config& cfg)
187 {
188  stats::battle_result_map m;
189  for(const config &i : cfg.child_range("sequence"))
190  {
191  config item = i;
192  int key = item["_num"];
193  item.remove_attribute("_num");
194  m[key] = read_str_int_map(item);
195  }
196 
197  return m;
198 }
199 
200 static config write_by_cth_map(const stats::hitrate_map& m)
201 {
202  config res;
203  for(const auto& i : m) {
204  res.add_child("hitrate_map_entry", config {
205  "cth", i.first,
206  "stats", i.second.write()
207  });
208  }
209  return res;
210 }
211 
212 static void merge_battle_result_maps(stats::battle_result_map& a, const stats::battle_result_map& b);
214 {
215  stats::hitrate_map m;
216 
217  statistics::stats::battle_result_map merged = attacks;
218  merge_battle_result_maps(merged, defends);
219 
220  for(const auto& i : merged) {
221  int cth = i.first;
222  const statistics::stats::battle_sequence_frequency_map& frequency_map = i.second;
223  for(const auto& j : frequency_map) {
224  const std::string& res = j.first; // see attack_context::~attack_context()
225  const int occurrences = j.second;
226  unsigned int misses = std::count(res.begin(), res.end(), '0');
227  unsigned int hits = std::count(res.begin(), res.end(), '1');
228  if(misses + hits == 0) {
229  continue;
230  }
231  misses *= occurrences;
232  hits *= occurrences;
233  m[cth].strikes += misses + hits;
234  m[cth].hits += hits;
235  }
236  }
237 
238  return m;
239 }
240 
241 static stats::hitrate_map read_by_cth_map(const config& cfg)
242 {
243  stats::hitrate_map m;
244  for(const config &i : cfg.child_range("hitrate_map_entry")) {
245  m.emplace(i["cth"], statistics::stats::hitrate_t(i.child("stats")));
246  }
247  return m;
248 }
249 
250 static void merge_str_int_map(stats::str_int_map& a, const stats::str_int_map& b)
251 {
252  for(stats::str_int_map::const_iterator i = b.begin(); i != b.end(); ++i) {
253  a[i->first] += i->second;
254  }
255 }
256 
257 static void merge_battle_result_maps(stats::battle_result_map& a, const stats::battle_result_map& b)
258 {
259  for(stats::battle_result_map::const_iterator i = b.begin(); i != b.end(); ++i) {
260  merge_str_int_map(a[i->first],i->second);
261  }
262 }
263 
264 static void merge_cth_map(stats::hitrate_map& a, const stats::hitrate_map& b)
265 {
266  for(const auto& i : b) {
267  a[i.first].hits += i.second.hits;
268  a[i.first].strikes += i.second.strikes;
269  }
270 }
271 
272 static void merge_stats(stats& a, const stats& b)
273 {
274  DBG_NG << "Merging statistics\n";
275  merge_str_int_map(a.recruits,b.recruits);
276  merge_str_int_map(a.recalls,b.recalls);
277  merge_str_int_map(a.advanced_to,b.advanced_to);
278  merge_str_int_map(a.deaths,b.deaths);
279  merge_str_int_map(a.killed,b.killed);
280 
281  merge_cth_map(a.by_cth_inflicted,b.by_cth_inflicted);
282  merge_cth_map(a.by_cth_taken,b.by_cth_taken);
283 
284  merge_battle_result_maps(a.attacks_inflicted,b.attacks_inflicted);
285  merge_battle_result_maps(a.defends_inflicted,b.defends_inflicted);
286  merge_battle_result_maps(a.attacks_taken,b.attacks_taken);
287  merge_battle_result_maps(a.defends_taken,b.defends_taken);
288 
289  a.recruit_cost += b.recruit_cost;
290  a.recall_cost += b.recall_cost;
291 
292  a.damage_inflicted += b.damage_inflicted;
293  a.damage_taken += b.damage_taken;
294  a.expected_damage_inflicted += b.expected_damage_inflicted;
295  a.expected_damage_taken += b.expected_damage_taken;
296  // Only take the last value for this turn
297  a.turn_damage_inflicted = b.turn_damage_inflicted;
298  a.turn_damage_taken = b.turn_damage_taken;
299  a.turn_expected_damage_inflicted = b.turn_expected_damage_inflicted;
300  a.turn_expected_damage_taken = b.turn_expected_damage_taken;
301  a.turn_by_cth_inflicted = b.turn_by_cth_inflicted;
302  a.turn_by_cth_taken = b.turn_by_cth_taken;
303 
304 }
305 
306 namespace statistics
307 {
308 
309 stats::stats() :
310  recruits(),
311  recalls(),
312  advanced_to(),
313  deaths(),
314  killed(),
315  recruit_cost(0),
316  recall_cost(0),
317  attacks_inflicted(),
318  defends_inflicted(),
319  attacks_taken(),
320  defends_taken(),
321  damage_inflicted(0),
322  damage_taken(0),
323  turn_damage_inflicted(0),
324  turn_damage_taken(0),
325  by_cth_inflicted(),
326  by_cth_taken(),
327  turn_by_cth_inflicted(),
328  turn_by_cth_taken(),
329  expected_damage_inflicted(0),
330  expected_damage_taken(0),
331  turn_expected_damage_inflicted(0),
332  turn_expected_damage_taken(0),
333  save_id()
334 {}
335 
336 stats::stats(const config& cfg) :
337  recruits(),
338  recalls(),
339  advanced_to(),
340  deaths(),
341  killed(),
342  recruit_cost(0),
343  recall_cost(0),
346  attacks_taken(),
347  defends_taken(),
348  damage_inflicted(0),
349  damage_taken(0),
353  by_cth_taken(),
360  save_id()
361 {
362  read(cfg);
363 }
364 
366 {
367  config res;
368  res.add_child("recruits",write_str_int_map(recruits));
369  res.add_child("recalls",write_str_int_map(recalls));
370  res.add_child("advances",write_str_int_map(advanced_to));
371  res.add_child("deaths",write_str_int_map(deaths));
372  res.add_child("killed",write_str_int_map(killed));
375  res.add_child("attacks_taken",write_battle_result_map(attacks_taken));
376  res.add_child("defends_taken",write_battle_result_map(defends_taken));
377  // Don't serialize by_cth_inflicted / by_cth_taken; they're deserialized from attacks_inflicted/defends_inflicted.
378  res.add_child("turn_by_cth_inflicted", write_by_cth_map(turn_by_cth_inflicted));
379  res.add_child("turn_by_cth_taken", write_by_cth_map(turn_by_cth_taken));
380 
381  res["recruit_cost"] = recruit_cost;
382  res["recall_cost"] = recall_cost;
383 
384  res["damage_inflicted"] = damage_inflicted;
385  res["damage_taken"] = damage_taken;
386  res["expected_damage_inflicted"] = expected_damage_inflicted;
387  res["expected_damage_taken"] = expected_damage_taken;
388 
389  res["turn_damage_inflicted"] = turn_damage_inflicted;
390  res["turn_damage_taken"] = turn_damage_taken;
391  res["turn_expected_damage_inflicted"] = turn_expected_damage_inflicted;
392  res["turn_expected_damage_taken"] = turn_expected_damage_taken;
393 
394  res["save_id"] = save_id;
395 
396  return res;
397 }
398 
399 void stats::write(config_writer &out) const
400 {
401  out.open_child("recruits");
403  out.close_child("recruits");
404  out.open_child("recalls");
406  out.close_child("recalls");
407  out.open_child("advances");
409  out.close_child("advances");
410  out.open_child("deaths");
412  out.close_child("deaths");
413  out.open_child("killed");
415  out.close_child("killed");
416  out.open_child("attacks");
418  out.close_child("attacks");
419  out.open_child("defends");
421  out.close_child("defends");
422  out.open_child("attacks_taken");
424  out.close_child("attacks_taken");
425  out.open_child("defends_taken");
427  out.close_child("defends_taken");
428  // Don't serialize by_cth_inflicted / by_cth_taken; they're deserialized from attacks_inflicted/defends.
429  out.open_child("turn_by_cth_inflicted");
431  out.close_child("turn_by_cth_inflicted");
432  out.open_child("turn_by_cth_taken");
434  out.close_child("turn_by_cth_taken");
435 
436  out.write_key_val("recruit_cost", recruit_cost);
437  out.write_key_val("recall_cost", recall_cost);
438 
439  out.write_key_val("damage_inflicted", damage_inflicted);
440  out.write_key_val("damage_taken", damage_taken);
441  out.write_key_val("expected_damage_inflicted", expected_damage_inflicted);
442  out.write_key_val("expected_damage_taken", expected_damage_taken);
443 
444  out.write_key_val("turn_damage_inflicted", turn_damage_inflicted);
445  out.write_key_val("turn_damage_taken", turn_damage_taken);
446  out.write_key_val("turn_expected_damage_inflicted", turn_expected_damage_inflicted);
447  out.write_key_val("turn_expected_damage_taken", turn_expected_damage_taken);
448 
449  out.write_key_val("save_id", save_id);
450 }
451 
452 void stats::read(const config& cfg)
453 {
454  if (const config &c = cfg.child("recruits")) {
456  }
457  if (const config &c = cfg.child("recalls")) {
459  }
460  if (const config &c = cfg.child("advances")) {
462  }
463  if (const config &c = cfg.child("deaths")) {
465  }
466  if (const config &c = cfg.child("killed")) {
468  }
469  if (const config &c = cfg.child("recalls")) {
471  }
472  if (const config &c = cfg.child("attacks")) {
474  }
475  if (const config &c = cfg.child("defends")) {
477  }
478  if (const config &c = cfg.child("attacks_taken")) {
480  }
481  if (const config &c = cfg.child("defends_taken")) {
483  }
485  // by_cth_taken will be an empty map in old (pre-#4070) savefiles that don't have
486  // [attacks_taken]/[defends_taken] tags in their [statistics] tags
488  if (const config &c = cfg.child("turn_by_cth_inflicted")) {
490  }
491  if (const config &c = cfg.child("turn_by_cth_taken")) {
493  }
494 
495  recruit_cost = cfg["recruit_cost"].to_int();
496  recall_cost = cfg["recall_cost"].to_int();
497 
498  damage_inflicted = cfg["damage_inflicted"].to_long_long();
499  damage_taken = cfg["damage_taken"].to_long_long();
500  expected_damage_inflicted = cfg["expected_damage_inflicted"].to_long_long();
501  expected_damage_taken = cfg["expected_damage_taken"].to_long_long();
502 
503  turn_damage_inflicted = cfg["turn_damage_inflicted"].to_long_long();
504  turn_damage_taken = cfg["turn_damage_taken"].to_long_long();
505  turn_expected_damage_inflicted = cfg["turn_expected_damage_inflicted"].to_long_long();
506  turn_expected_damage_taken = cfg["turn_expected_damage_taken"].to_long_long();
507 
508  save_id = cfg["save_id"].str();
509 }
510 
512 {
513  if(!mid_scenario || master_stats.empty()) {
514  master_stats.emplace_back(name);
515  }
516 
517  mid_scenario = true;
518 }
519 
521 {
522  mid_scenario = false;
523 }
524 
526  const unit& d, int a_cth, int d_cth) :
527  attacker_type(a.type_id()),
528  defender_type(d.type_id()),
529  attacker_side(get_team_save_id(a)),
530  defender_side(get_team_save_id(d)),
531  chance_to_hit_defender(a_cth),
532  chance_to_hit_attacker(d_cth),
533  attacker_res(),
534  defender_res()
535 {
536 }
537 
539 {
540  std::string attacker_key = "s" + attacker_res;
541  std::string defender_key = "s" + defender_res;
542 
545 
548 }
549 
551 {
552  return get_stats(attacker_side);
553 }
554 
556 {
557  return get_stats(defender_side);
558 }
559 
560 void attack_context::attack_expected_damage(double attacker_inflict_, double defender_inflict_)
561 {
562  int attacker_inflict = std::round(attacker_inflict_ * stats::decimal_shift);
563  int defender_inflict = std::round(defender_inflict_ * stats::decimal_shift);
564  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
565  att_stats.expected_damage_inflicted += attacker_inflict;
566  att_stats.expected_damage_taken += defender_inflict;
567  def_stats.expected_damage_inflicted += defender_inflict;
568  def_stats.expected_damage_taken += attacker_inflict;
569  att_stats.turn_expected_damage_inflicted += attacker_inflict;
570  att_stats.turn_expected_damage_taken += defender_inflict;
571  def_stats.turn_expected_damage_inflicted += defender_inflict;
572  def_stats.turn_expected_damage_taken += attacker_inflict;
573 }
574 
575 
576 void attack_context::attack_result(hit_result res, int cth, int damage, int drain)
577 {
578  attacker_res.push_back(res == MISSES ? '0' : '1');
579  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
580 
581  if(res != MISSES) {
582  ++att_stats.by_cth_inflicted[cth].hits;
583  ++att_stats.turn_by_cth_inflicted[cth].hits;
584  ++def_stats.by_cth_taken[cth].hits;
585  ++def_stats.turn_by_cth_taken[cth].hits;
586  }
587  ++att_stats.by_cth_inflicted[cth].strikes;
588  ++att_stats.turn_by_cth_inflicted[cth].strikes;
589  ++def_stats.by_cth_taken[cth].strikes;
590  ++def_stats.turn_by_cth_taken[cth].strikes;
591 
592  if(res != MISSES) {
593  // handle drain
594  att_stats.damage_taken -= drain;
595  def_stats.damage_inflicted -= drain;
596  att_stats.turn_damage_taken -= drain;
597  def_stats.turn_damage_inflicted -= drain;
598 
599  att_stats.damage_inflicted += damage;
600  def_stats.damage_taken += damage;
601  att_stats.turn_damage_inflicted += damage;
602  def_stats.turn_damage_taken += damage;
603  }
604 
605  if(res == KILLS) {
606  ++att_stats.killed[defender_type];
607  ++def_stats.deaths[defender_type];
608  }
609 }
610 
611 void attack_context::defend_result(hit_result res, int cth, int damage, int drain)
612 {
613  defender_res.push_back(res == MISSES ? '0' : '1');
614  stats &att_stats = attacker_stats(), &def_stats = defender_stats();
615 
616  if(res != MISSES) {
617  ++def_stats.by_cth_inflicted[cth].hits;
618  ++def_stats.turn_by_cth_inflicted[cth].hits;
619  ++att_stats.by_cth_taken[cth].hits;
620  ++att_stats.turn_by_cth_taken[cth].hits;
621  }
622  ++def_stats.by_cth_inflicted[cth].strikes;
623  ++def_stats.turn_by_cth_inflicted[cth].strikes;
624  ++att_stats.by_cth_taken[cth].strikes;
625  ++att_stats.turn_by_cth_taken[cth].strikes;
626 
627  if(res != MISSES) {
628  //handle drain
629  def_stats.damage_taken -= drain;
630  att_stats.damage_inflicted -= drain;
631  def_stats.turn_damage_taken -= drain;
632  att_stats.turn_damage_inflicted -= drain;
633 
634  att_stats.damage_taken += damage;
635  def_stats.damage_inflicted += damage;
636  att_stats.turn_damage_taken += damage;
637  def_stats.turn_damage_inflicted += damage;
638  }
639 
640  if(res == KILLS) {
641  ++att_stats.deaths[attacker_type];
642  ++def_stats.killed[attacker_type];
643  }
644 }
645 
646 void recruit_unit(const unit& u)
647 {
648  stats& s = get_stats(get_team_save_id(u));
649  s.recruits[u.type().parent_id()]++;
650  s.recruit_cost += u.cost();
651 }
652 
653 void recall_unit(const unit& u)
654 {
655  stats& s = get_stats(get_team_save_id(u));
656  s.recalls[u.type_id()]++;
657  s.recall_cost += u.cost();
658 }
659 
660 void un_recall_unit(const unit& u)
661 {
662  stats& s = get_stats(get_team_save_id(u));
663  s.recalls[u.type_id()]--;
664  s.recall_cost -= u.cost();
665 }
666 
667 void un_recruit_unit(const unit& u)
668 {
669  stats& s = get_stats(get_team_save_id(u));
670  s.recruits[u.type().parent_id()]--;
671  s.recruit_cost -= u.cost();
672 }
673 
674 int un_recall_unit_cost(const unit& u) // this really belongs elsewhere, perhaps in undo.cpp
675 { // but I'm too lazy to do it at the moment
676  return u.recall_cost();
677 }
678 
679 
680 void advance_unit(const unit& u)
681 {
682  stats& s = get_stats(get_team_save_id(u));
683  s.advanced_to[u.type_id()]++;
684 }
685 
686 void reset_turn_stats(const std::string & save_id)
687 {
688  stats &s = get_stats(save_id);
689  s.turn_damage_inflicted = 0;
690  s.turn_damage_taken = 0;
693  s.turn_by_cth_inflicted.clear();
694  s.turn_by_cth_taken.clear();
695  s.save_id = save_id;
696 }
697 
698 stats calculate_stats(const std::string & save_id)
699 {
700  stats res;
701 
702  DBG_NG << "calculate_stats, side: " << save_id << " master_stats.size: " << master_stats.size() << "\n";
703  // The order of this loop matters since the turn stats are taken from the
704  // last stats merged.
705  for ( std::size_t i = 0; i != master_stats.size(); ++i ) {
706  team_stats_t::const_iterator find_it = master_stats[i].team_stats.find(save_id);
707  if ( find_it != master_stats[i].team_stats.end() )
708  merge_stats(res, find_it->second);
709  }
710 
711  return res;
712 }
713 
714 
715 /**
716  * Returns a list of names and stats for each scenario in the current campaign.
717  * The front of the list is the oldest scenario; the back of the list is the
718  * (most) current scenario.
719  * Only scenarios with stats for the given @a side_id are included, but if no
720  * scenarios are applicable, then a vector containing a single dummy entry will
721  * be returned. (I.e., this never returns an empty vector.)
722  * This list is intended for the statistics dialog and may become invalid if
723  * new stats are recorded.
724  */
725 levels level_stats(const std::string & save_id)
726 {
727  static const stats null_stats;
728  static const std::string null_name("");
729 
730  levels level_list;
731 
732  for ( std::size_t level = 0; level != master_stats.size(); ++level ) {
733  const team_stats_t & team_stats = master_stats[level].team_stats;
734 
735  team_stats_t::const_iterator find_it = team_stats.find(save_id);
736  if ( find_it != team_stats.end() )
737  level_list.emplace_back(&master_stats[level].scenario_name, &find_it->second);
738  }
739 
740  // Make sure we do return something (so other code does not have to deal
741  // with an empty list).
742  if ( level_list.empty() )
743  level_list.emplace_back(&null_name, &null_stats);
744 
745  return level_list;
746 }
747 
748 
750 {
751  config res;
752  res["mid_scenario"] = mid_scenario;
753 
754  for(std::vector<scenario_stats>::const_iterator i = master_stats.begin(); i != master_stats.end(); ++i) {
755  res.add_child("scenario",i->write());
756  }
757 
758  return res;
759 }
760 
762 {
763  out.write_key_val("mid_scenario", mid_scenario);
764 
765  for(std::vector<scenario_stats>::const_iterator i = master_stats.begin(); i != master_stats.end(); ++i) {
766  out.open_child("scenario");
767  i->write(out);
768  out.close_child("scenario");
769  }
770 }
771 
772 void read_stats(const config& cfg)
773 {
774  fresh_stats();
775  mid_scenario = cfg["mid_scenario"].to_bool();
776 
777  for(const config &s : cfg.child_range("scenario")) {
778  master_stats.emplace_back(s);
779  }
780 }
781 
783 {
784  master_stats.clear();
785  mid_scenario = false;
786 }
787 
789 {
790  if(master_stats.empty() == false) {
791  master_stats.pop_back();
792  mid_scenario = false;
793  }
794 }
795 
797 {
798  assert(!master_stats.empty());
799  master_stats.back().team_stats = {};
800  mid_scenario = false;
801 }
802 
804 {
805  int res = 0;
806  for(stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
807  res += i->second;
808  }
809 
810  return res;
811 }
812 
814 {
815  int cost = 0;
816  for (stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
817  const unit_type *t = unit_types.find(i->first);
818  if (!t) {
819  ERR_NG << "Statistics refer to unknown unit type '" << i->first << "'. Discarding." << std::endl;
820  } else {
821  cost += i->second * t->cost();
822  }
823  }
824 
825  return cost;
826 }
827 
829 {
830  return config("hits", hits, "strikes", strikes);
831 }
832 
834  strikes(cfg["strikes"]),
835  hits(cfg["hits"])
836 {}
837 
838 } // end namespace statistics
839 
840 std::ostream& operator<<(std::ostream& outstream, const statistics::stats::hitrate_t& by_cth) {
841  outstream << "[" << by_cth.hits << "/" << by_cth.strikes << "]";
842  return outstream;
843 }
#define DBG_NG
Definition: statistics.cpp:32
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:147
static stats::battle_result_map read_battle_result_map(const config &cfg)
Definition: statistics.cpp:186
long long damage_inflicted
Definition: statistics.hpp:57
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:415
void write(const config &cfg)
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1222
battle_result_map defends_taken
Statistics of enemies&#39; attacks against this side on their turns.
Definition: statistics.hpp:55
scenario_context(const std::string &name)
Definition: statistics.cpp:511
This class represents a single unit of a specific type.
Definition: unit.hpp:120
const std::string & type_id() const
The id of this unit&#39;s type.
Definition: unit.cpp:1792
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
int sum_str_int_map(const stats::str_int_map &m)
Definition: statistics.cpp:803
void un_recall_unit(const unit &u)
Definition: statistics.cpp:660
bool has_attribute(config_key_type key) const
Definition: config.cpp:208
#define a
static stats::hitrate_map read_by_cth_map(const config &cfg)
Definition: statistics.cpp:241
child_itors child_range(config_key_type key)
Definition: config.cpp:357
attribute_map::value_type attribute
Definition: config.hpp:220
long long turn_damage_taken
Definition: statistics.hpp:58
void fresh_stats()
Definition: statistics.cpp:782
int sum_cost_str_int_map(const stats::str_int_map &m)
Definition: statistics.cpp:813
unit_type_data unit_types
Definition: types.cpp:1441
#define d
void remove_attribute(config_key_type key)
Definition: config.cpp:230
void reset_current_scenario()
Reset the stats of the current scenario to the beginning.
Definition: statistics.cpp:796
void reset_turn_stats(const std::string &save_id)
Definition: statistics.cpp:686
int cost() const
How much gold is required to recruit this unit.
Definition: unit.hpp:626
str_int_map advanced_to
Definition: statistics.hpp:36
void recruit_unit(const unit &u)
Definition: statistics.cpp:646
const_attr_itors attribute_range() const
Definition: config.cpp:804
std::vector< std::pair< const std::string *, const stats * > > levels
Stats (and name) for each scenario.
Definition: statistics.hpp:135
A single unit type that the player may recruit.
Definition: types.hpp:44
#define b
long long expected_damage_taken
Definition: statistics.hpp:79
const unit_type & type() const
This unit&#39;s type, accounting for gender and variation.
Definition: unit.hpp:345
long long turn_expected_damage_inflicted
Definition: statistics.hpp:80
battle_result_map attacks_inflicted
Statistics of this side&#39;s attacks on its own turns.
Definition: statistics.hpp:49
static config write_by_cth_map(const stats::hitrate_map &m)
Definition: statistics.cpp:200
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:763
This class stores all the data for a single &#39;side&#39; (in game nomenclature).
Definition: team.hpp:44
team & get_team(int i)
Definition: game_board.hpp:94
void write_key_val(const std::string &key, const T &value)
This template function will work with any type that can be assigned to an attribute_value.
static void merge_str_int_map(stats::str_int_map &a, const stats::str_int_map &b)
Definition: statistics.cpp:250
int cost() const
Definition: types.hpp:166
void close_child(const std::string &key)
config write() const
Definition: statistics.cpp:365
static const int decimal_shift
Definition: statistics.hpp:73
void attack_result(hit_result res, int cth, int damage, int drain)
Definition: statistics.cpp:576
Class for writing a config out to a file in pieces.
std::map< int, battle_sequence_frequency_map > battle_result_map
A type that will map different % chances to hit to different results.
Definition: statistics.hpp:46
int un_recall_unit_cost(const unit &u)
Definition: statistics.cpp:674
static stats::hitrate_map read_by_cth_map_from_battle_result_maps(const statistics::stats::battle_result_map &attacks, const statistics::stats::battle_result_map &defends)
Definition: statistics.cpp:213
void open_child(const std::string &key)
game_board * gameboard
Definition: resources.cpp:20
battle_result_map defends_inflicted
Statistics of this side&#39;s attacks on enemies&#39; turns.
Definition: statistics.hpp:51
static stats & get_stats(const std::string &save_id)
Definition: statistics.cpp:100
levels level_stats(const std::string &save_id)
Returns a list of names and stats for each scenario in the current campaign.
Definition: statistics.cpp:725
str_int_map recalls
Definition: statistics.hpp:36
static config write_battle_result_map(const stats::battle_result_map &m)
Definition: statistics.cpp:164
hitrate_map turn_by_cth_taken
Definition: statistics.hpp:71
void read_stats(const config &cfg)
Definition: statistics.cpp:772
void read(const config &cfg)
Definition: statistics.cpp:452
str_int_map killed
Definition: statistics.hpp:36
battle_result_map attacks_taken
Statistics of enemies&#39; counter attacks on this side&#39;s turns.
Definition: statistics.hpp:53
hitrate_map turn_by_cth_inflicted
Definition: statistics.hpp:71
void advance_unit(const unit &u)
Definition: statistics.cpp:680
static lg::log_domain log_engine("engine")
std::size_t i
Definition: function.cpp:933
str_int_map recruits
Definition: statistics.hpp:36
stats calculate_stats(const std::string &save_id)
Definition: statistics.cpp:698
mock_party p
str_int_map deaths
Definition: statistics.hpp:36
hitrate_map by_cth_taken
Definition: statistics.hpp:70
static map_location::DIRECTION s
std::string name
Definition: sdl_ttf.cpp:70
long long damage_taken
Definition: statistics.hpp:57
static stats::str_int_map read_str_int_map(const config &cfg)
Definition: statistics.cpp:148
long long turn_damage_inflicted
Definition: statistics.hpp:58
static void merge_battle_result_maps(stats::battle_result_map &a, const stats::battle_result_map &b)
Definition: statistics.cpp:257
void recall_unit(const unit &u)
Definition: statistics.cpp:653
config & add_child(config_key_type key)
Definition: config.cpp:471
attack_context(const unit &a, const unit &d, int a_cth, int d_cth)
Definition: statistics.cpp:525
long long expected_damage_inflicted
Definition: statistics.hpp:79
str_int_map battle_sequence_frequency_map
Definition: statistics.hpp:43
double t
Definition: astarsearch.cpp:64
static void merge_stats(stats &a, const stats &b)
Definition: statistics.cpp:272
std::vector< std::string > split(const config_attribute_value &val)
void attack_expected_damage(double attacker_inflict, double defender_inflict)
Definition: statistics.cpp:560
std::map< std::string, int > str_int_map
Definition: statistics.hpp:35
config write_stats()
Definition: statistics.cpp:749
void defend_result(hit_result res, int cth, int damage, int drain)
Definition: statistics.cpp:611
Standard logging facilities (interface).
long long turn_expected_damage_taken
Definition: statistics.hpp:80
std::string save_id
Definition: statistics.hpp:81
int side() const
The side this unit belongs to.
Definition: unit.hpp:333
void un_recruit_unit(const unit &u)
Definition: statistics.cpp:667
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
#define ERR_NG
Definition: statistics.cpp:33
mock_char c
void clear_current_scenario()
Delete the current scenario from the stats.
Definition: statistics.cpp:788
static config write_str_int_map(const stats::str_int_map &m)
Definition: statistics.cpp:110
static map_location::DIRECTION n
static void merge_cth_map(stats::hitrate_map &a, const stats::hitrate_map &b)
Definition: statistics.cpp:264
int recall_cost() const
How much gold it costs to recall this unit, or -1 if the side&#39;s default recall cost is used...
Definition: unit.hpp:633
std::ostream & operator<<(std::ostream &outstream, const statistics::stats::hitrate_t &by_cth)
Definition: statistics.cpp:840
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:407
hitrate_map by_cth_inflicted
Definition: statistics.hpp:70