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