wesnoth/src/statistics.cpp
2012-05-19 15:57:33 +00:00

656 lines
17 KiB
C++

/* $Id$ */
/*
Copyright (C) 2003 - 2012 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file
* Manage statistics: recruitments, recalls, kills, losses, etc.
*/
#include "global.hpp"
#include "statistics.hpp"
#include "foreach.hpp"
#include "log.hpp"
#include "serialization/binary_or_text.hpp"
#include "unit.hpp"
#include "util.hpp"
static lg::log_domain log_engine("engine");
#define DBG_NG LOG_STREAM(debug, log_engine)
namespace {
// This variable is true whenever the statistics are mid-scenario.
// This means a new scenario shouldn't be added to the master stats record.
bool mid_scenario = false;
typedef statistics::stats stats;
struct scenario_stats
{
explicit scenario_stats(const std::string& name) :
team_stats(),
scenario_name(name)
{}
explicit scenario_stats(const config& cfg);
config write() const;
void write(config_writer &out) const;
std::map<std::string,stats> team_stats;
std::string scenario_name;
};
scenario_stats::scenario_stats(const config& cfg) :
team_stats(),
scenario_name(cfg["scenario"])
{
foreach (const config &team, cfg.child_range("team")) {
team_stats[team["save_id"]] = stats(team);
}
}
config scenario_stats::write() const
{
config res;
res["scenario"] = scenario_name;
for(std::map<std::string,stats>::const_iterator i = team_stats.begin(); i != team_stats.end(); ++i) {
res.add_child("team",i->second.write());
}
return res;
}
void scenario_stats::write(config_writer &out) const
{
out.write_key_val("scenario", scenario_name);
for(std::map<std::string,stats>::const_iterator i = team_stats.begin(); i != team_stats.end(); ++i) {
out.open_child("team");
i->second.write(out);
out.close_child("team");
}
}
std::vector<scenario_stats> master_stats;
} // end anon namespace
static stats &get_stats(const std::string &save_id)
{
if(master_stats.empty()) {
master_stats.push_back(scenario_stats(std::string()));
}
std::map<std::string,stats>& team_stats = master_stats.back().team_stats;
return team_stats[save_id];
}
static config write_str_int_map(const stats::str_int_map& m)
{
config res;
for(stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
char buf[50];
snprintf(buf,sizeof(buf),"%d",i->second);
res[i->first] = buf;
}
return res;
}
static void write_str_int_map(config_writer &out, const stats::str_int_map& m)
{
for(stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
char buf[50];
snprintf(buf,sizeof(buf),"%d",i->second);
out.write_key_val(i->first, buf);
}
}
static stats::str_int_map read_str_int_map(const config& cfg)
{
stats::str_int_map m;
foreach (const config::attribute &i, cfg.attribute_range()) {
m[i.first] = i.second;
}
return m;
}
static config write_battle_result_map(const stats::battle_result_map& m)
{
config res;
for(stats::battle_result_map::const_iterator i = m.begin(); i != m.end(); ++i) {
config& new_cfg = res.add_child("sequence");
new_cfg = write_str_int_map(i->second);
char buf[50];
snprintf(buf,sizeof(buf),"%d",i->first);
new_cfg["_num"] = buf;
}
return res;
}
static void write_battle_result_map(config_writer &out, const stats::battle_result_map& m)
{
for(stats::battle_result_map::const_iterator i = m.begin(); i != m.end(); ++i) {
out.open_child("sequence");
write_str_int_map(out, i->second);
char buf[50];
snprintf(buf,sizeof(buf),"%d",i->first);
out.write_key_val("_num", buf);
out.close_child("sequence");
}
}
static stats::battle_result_map read_battle_result_map(const config& cfg)
{
stats::battle_result_map m;
foreach (const config &i, cfg.child_range("sequence"))
{
config item = i;
int key = item["_num"];
item.remove_attribute("_num");
m[key] = read_str_int_map(item);
}
return m;
}
static void merge_str_int_map(stats::str_int_map& a, const stats::str_int_map& b)
{
for(stats::str_int_map::const_iterator i = b.begin(); i != b.end(); ++i) {
a[i->first] += i->second;
}
}
static void merge_battle_result_maps(stats::battle_result_map& a, const stats::battle_result_map& b)
{
for(stats::battle_result_map::const_iterator i = b.begin(); i != b.end(); ++i) {
merge_str_int_map(a[i->first],i->second);
}
}
static void merge_stats(stats& a, const stats& b)
{
DBG_NG << "Merging statistics\n";
merge_str_int_map(a.recruits,b.recruits);
merge_str_int_map(a.recalls,b.recalls);
merge_str_int_map(a.advanced_to,b.advanced_to);
merge_str_int_map(a.deaths,b.deaths);
merge_str_int_map(a.killed,b.killed);
merge_battle_result_maps(a.attacks,b.attacks);
merge_battle_result_maps(a.defends,b.defends);
a.recruit_cost += b.recruit_cost;
a.recall_cost += b.recall_cost;
a.damage_inflicted += b.damage_inflicted;
a.damage_taken += b.damage_taken;
a.expected_damage_inflicted += b.expected_damage_inflicted;
a.expected_damage_taken += b.expected_damage_taken;
// Only take the last value for this turn
a.turn_damage_inflicted = b.turn_damage_inflicted;
a.turn_damage_taken = b.turn_damage_taken;
a.turn_expected_damage_inflicted = b.turn_expected_damage_inflicted;
a.turn_expected_damage_taken = b.turn_expected_damage_taken;
}
namespace statistics
{
stats::stats() :
recruits(),
recalls(),
advanced_to(),
deaths(),
killed(),
recruit_cost(0),
recall_cost(0),
attacks(),
defends(),
damage_inflicted(0),
damage_taken(0),
turn_damage_inflicted(0),
turn_damage_taken(0),
expected_damage_inflicted(0),
expected_damage_taken(0),
turn_expected_damage_inflicted(0),
turn_expected_damage_taken(0),
save_id()
{}
stats::stats(const config& cfg) :
recruits(),
recalls(),
advanced_to(),
deaths(),
killed(),
recruit_cost(0),
recall_cost(0),
attacks(),
defends(),
damage_inflicted(0),
damage_taken(0),
turn_damage_inflicted(0),
turn_damage_taken(0),
expected_damage_inflicted(0),
expected_damage_taken(0),
turn_expected_damage_inflicted(0),
turn_expected_damage_taken(0),
save_id(std::string())
{
read(cfg);
}
config stats::write() const
{
config res;
res.add_child("recruits",write_str_int_map(recruits));
res.add_child("recalls",write_str_int_map(recalls));
res.add_child("advances",write_str_int_map(advanced_to));
res.add_child("deaths",write_str_int_map(deaths));
res.add_child("killed",write_str_int_map(killed));
res.add_child("attacks",write_battle_result_map(attacks));
res.add_child("defends",write_battle_result_map(defends));
std::ostringstream ss;
ss << recruit_cost;
res["recruit_cost"] = ss.str();
ss.str(std::string());
ss << recall_cost;
res["recall_cost"] = ss.str();
ss.str(std::string());
ss << damage_inflicted;
res["damage_inflicted"] = ss.str();
ss.str(std::string());
ss << damage_taken;
res["damage_taken"] = ss.str();
ss.str(std::string());
ss << expected_damage_inflicted;
res["expected_damage_inflicted"] = ss.str();
ss.str(std::string());
ss << expected_damage_taken;
res["expected_damage_taken"] = ss.str();
ss.str(std::string());
ss << turn_damage_inflicted;
res["turn_damage_inflicted"] = ss.str();
ss.str(std::string());
ss << turn_damage_taken;
res["turn_damage_taken"] = ss.str();
ss.str(std::string());
ss << turn_expected_damage_inflicted;
res["turn_expected_damage_inflicted"] = ss.str();
ss.str(std::string());
ss << turn_expected_damage_taken;
res["turn_expected_damage_taken"] = ss.str();
return res;
}
void stats::write(config_writer &out) const
{
out.open_child("recruits");
write_str_int_map(out, recruits);
out.close_child("recruits");
out.open_child("recalls");
write_str_int_map(out, recalls);
out.close_child("recalls");
out.open_child("advances");
write_str_int_map(out, advanced_to);
out.close_child("advances");
out.open_child("deaths");
write_str_int_map(out, deaths);
out.close_child("deaths");
out.open_child("killed");
write_str_int_map(out, killed);
out.close_child("killed");
out.open_child("attacks");
write_battle_result_map(out, attacks);
out.close_child("attacks");
out.open_child("defends");
write_battle_result_map(out, defends);
out.close_child("defends");
std::ostringstream ss;
ss << recruit_cost;
out.write_key_val("recruit_cost", ss.str());
ss.str(std::string());
ss << recall_cost;
out.write_key_val("recall_cost", ss.str());
ss.str(std::string());
ss << damage_inflicted;
out.write_key_val("damage_inflicted", ss.str());
ss.str(std::string());
ss << damage_taken;
out.write_key_val("damage_taken", ss.str());
ss.str(std::string());
ss << expected_damage_inflicted;
out.write_key_val("expected_damage_inflicted", ss.str());
ss.str(std::string());
ss << expected_damage_taken;
out.write_key_val("expected_damage_taken", ss.str());
ss.str(std::string());
ss << turn_damage_inflicted;
out.write_key_val("turn_damage_inflicted", ss.str());
ss.str(std::string());
ss << turn_damage_taken;
out.write_key_val("turn_damage_taken", ss.str());
ss.str(std::string());
ss << turn_expected_damage_inflicted;
out.write_key_val("turn_expected_damage_inflicted", ss.str());
ss.str(std::string());
ss << turn_expected_damage_taken;
out.write_key_val("turn_expected_damage_taken", ss.str());
out.write_key_val("save_id", save_id);
}
void stats::read(const config& cfg)
{
if (const config &c = cfg.child("recruits")) {
recruits = read_str_int_map(c);
}
if (const config &c = cfg.child("recalls")) {
recalls = read_str_int_map(c);
}
if (const config &c = cfg.child("advances")) {
advanced_to = read_str_int_map(c);
}
if (const config &c = cfg.child("deaths")) {
deaths = read_str_int_map(c);
}
if (const config &c = cfg.child("killed")) {
killed = read_str_int_map(c);
}
if (const config &c = cfg.child("recalls")) {
recalls = read_str_int_map(c);
}
if (const config &c = cfg.child("attacks")) {
attacks = read_battle_result_map(c);
}
if (const config &c = cfg.child("defends")) {
defends = read_battle_result_map(c);
}
recruit_cost = cfg["recruit_cost"].to_double();
recall_cost = cfg["recall_cost"].to_double();
damage_inflicted = cfg["damage_inflicted"].to_double();
damage_taken = cfg["damage_taken"].to_double();
expected_damage_inflicted = cfg["expected_damage_inflicted"].to_double();
expected_damage_taken = cfg["expected_damage_taken"].to_double();
turn_damage_inflicted = cfg["turn_damage_inflicted"].to_double();
turn_damage_taken = cfg["turn_damage_taken"].to_double();
turn_expected_damage_inflicted = cfg["turn_expected_damage_inflicted"].to_double();
turn_expected_damage_taken = cfg["turn_expected_damage_taken"].to_double();
save_id = cfg["save_id"].str();
}
scenario_context::scenario_context(const std::string& name)
{
if(!mid_scenario || master_stats.empty()) {
master_stats.push_back(scenario_stats(name));
}
mid_scenario = true;
}
scenario_context::~scenario_context()
{
mid_scenario = false;
}
attack_context::attack_context(const unit& a,
const unit& d, int a_cth, int d_cth) :
attacker_type(a.type_id()),
defender_type(d.type_id()),
attacker_side(a.side_id()),
defender_side(d.side_id()),
chance_to_hit_defender(a_cth),
chance_to_hit_attacker(d_cth),
attacker_res(),
defender_res()
{
}
attack_context::~attack_context()
{
std::string attacker_key = "s" + attacker_res;
std::string defender_key = "s" + defender_res;
attacker_stats().attacks[chance_to_hit_defender][attacker_key]++;
defender_stats().defends[chance_to_hit_attacker][defender_key]++;
}
stats& attack_context::attacker_stats()
{
return get_stats(attacker_side);
}
stats& attack_context::defender_stats()
{
return get_stats(defender_side);
}
void attack_context::attack_expected_damage(double attacker_inflict_, double defender_inflict_)
{
int attacker_inflict = round_double(attacker_inflict_ * stats::decimal_shift);
int defender_inflict = round_double(defender_inflict_ * stats::decimal_shift);
stats &att_stats = attacker_stats(), &def_stats = defender_stats();
att_stats.expected_damage_inflicted += attacker_inflict;
att_stats.expected_damage_taken += defender_inflict;
def_stats.expected_damage_inflicted += defender_inflict;
def_stats.expected_damage_taken += attacker_inflict;
att_stats.turn_expected_damage_inflicted += attacker_inflict;
att_stats.turn_expected_damage_taken += defender_inflict;
def_stats.turn_expected_damage_inflicted += defender_inflict;
def_stats.turn_expected_damage_taken += attacker_inflict;
}
void attack_context::attack_result(hit_result res, int damage, int drain)
{
attacker_res.push_back(res == MISSES ? '0' : '1');
stats &att_stats = attacker_stats(), &def_stats = defender_stats();
if(res != MISSES) {
// handle drain
att_stats.damage_taken -= drain;
def_stats.damage_inflicted -= drain;
att_stats.turn_damage_taken -= drain;
def_stats.turn_damage_inflicted -= drain;
att_stats.damage_inflicted += damage;
def_stats.damage_taken += damage;
att_stats.turn_damage_inflicted += damage;
def_stats.turn_damage_taken += damage;
}
if(res == KILLS) {
++att_stats.killed[defender_type];
++def_stats.deaths[defender_type];
}
}
void attack_context::defend_result(hit_result res, int damage, int drain)
{
defender_res.push_back(res == MISSES ? '0' : '1');
stats &att_stats = attacker_stats(), &def_stats = defender_stats();
if(res != MISSES) {
//handle drain
def_stats.damage_taken -= drain;
att_stats.damage_inflicted -= drain;
def_stats.turn_damage_taken -= drain;
att_stats.turn_damage_inflicted -= drain;
att_stats.damage_taken += damage;
def_stats.damage_inflicted += damage;
att_stats.turn_damage_taken += damage;
def_stats.turn_damage_inflicted += damage;
}
if(res == KILLS) {
++att_stats.deaths[attacker_type];
++def_stats.killed[attacker_type];
}
}
void recruit_unit(const unit& u)
{
stats& s = get_stats(u.side_id());
s.recruits[u.type_id()]++;
s.recruit_cost += u.cost();
}
void recall_unit(const unit& u)
{
stats& s = get_stats(u.side_id());
s.recalls[u.type_id()]++;
s.recall_cost += u.cost();
}
void un_recall_unit(const unit& u)
{
stats& s = get_stats(u.side_id());
s.recalls[u.type_id()]--;
s.recall_cost -= u.cost();
}
void un_recruit_unit(const unit& u)
{
stats& s = get_stats(u.side_id());
s.recruits[u.type_id()]--;
s.recruit_cost -= u.cost();
}
void advance_unit(const unit& u)
{
stats& s = get_stats(u.side_id());
s.advanced_to[u.type_id()]++;
}
void reset_turn_stats(std::string save_id)
{
stats &s = get_stats(save_id);
s.turn_damage_inflicted = 0;
s.turn_damage_taken = 0;
s.turn_expected_damage_inflicted = 0;
s.turn_expected_damage_taken = 0;
s.save_id = save_id;
}
stats calculate_stats(int category, std::string save_id)
{
DBG_NG << "calculate_stats, category: " << category << " side: " << save_id << " master_stats.size: " << master_stats.size() << "\n";
if(category == 0) {
stats res;
// We are going from last to first to include correct turn stats in result
for(int i = int(master_stats.size()); i > 0 ; --i) {
merge_stats(res,calculate_stats(i,save_id));
}
return res;
} else {
const size_t index = master_stats.size() - size_t(category);
if(index < master_stats.size() && master_stats[index].team_stats.find(save_id) != master_stats[index].team_stats.end()) {
return master_stats[index].team_stats[save_id];
} else {
return stats();
}
}
}
config write_stats()
{
config res;
res["mid_scenario"] = mid_scenario;
for(std::vector<scenario_stats>::const_iterator i = master_stats.begin(); i != master_stats.end(); ++i) {
res.add_child("scenario",i->write());
}
return res;
}
void write_stats(config_writer &out)
{
out.write_key_val("mid_scenario", mid_scenario ? "yes" : "no");
for(std::vector<scenario_stats>::const_iterator i = master_stats.begin(); i != master_stats.end(); ++i) {
out.open_child("scenario");
i->write(out);
out.close_child("scenario");
}
}
void read_stats(const config& cfg)
{
fresh_stats();
mid_scenario = cfg["mid_scenario"].to_bool();
foreach (const config &s, cfg.child_range("scenario")) {
master_stats.push_back(scenario_stats(s));
}
}
void fresh_stats()
{
master_stats.clear();
mid_scenario = false;
}
void clear_current_scenario()
{
if(master_stats.empty() == false) {
master_stats.pop_back();
mid_scenario = false;
}
}
int sum_str_int_map(const stats::str_int_map& m)
{
int res = 0;
for(stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
res += i->second;
}
return res;
}
int sum_cost_str_int_map(const stats::str_int_map &m)
{
int cost = 0;
for (stats::str_int_map::const_iterator i = m.begin(); i != m.end(); ++i) {
cost += i->second * unit_types.find(i->first)->cost();
}
return cost;
}
} // end namespace statistics