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