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