Add display of per-scenario statistics.

This commit is contained in:
J. Tyne 2012-09-08 19:02:29 +00:00
parent 3eec1b69a7
commit 1f68e0bb48
6 changed files with 202 additions and 42 deletions

View file

@ -13,6 +13,7 @@ Version 1.11.0+svn:
leader, but with new semantics -- only that leader's recruits/recalls will
be presented as options.
* Fog/shroud clearing has been reworked to be more timely and consistent.
* The statistics window can now show per-scenario statistics.
* WML engine:
* The recall, recruit, prerecall, and prerecruit events will now block
undoing unless they contain [allow_undo].

View file

@ -15,6 +15,7 @@ Version 1.11.0+svn:
* The recruit and recall commands are restored when right-clicking on a
leader, but with new semantics -- only that leader's recruits/recalls will
be presented as options.
* The statistics window can now show per-scenario statistics.
* Miscellaneous and bug fixes:
* Fix invalid memory access crash resulting from deleting all saved games

View file

@ -581,6 +581,42 @@ stats calculate_stats(const std::string & save_id)
return res;
}
/**
* Returns a list of names and stats for each scenario in the current campaign.
* The front of the list is the oldest scenario; the back of the list is the
* (most) current scenario.
* Only scenarios with stats for the given @a side_id are included, but if no
* scenarios are applicable, then a vector containing a single dummy entry will
* be returned. (I.e., this never returns an empty vector.)
* This list is intended for the statistics dialog and may become invalid if
* new stats are recorded.
*/
levels level_stats(const std::string & save_id)
{
static const stats null_stats;
static const std::string null_name("");
levels level_list;
for ( size_t level = 0; level != master_stats.size(); ++level ) {
const team_stats_t & team_stats = master_stats[level].team_stats;
team_stats_t::const_iterator find_it = team_stats.find(save_id);
if ( find_it != team_stats.end() )
level_list.push_back(make_pair(&master_stats[level].scenario_name,
&find_it->second));
}
// Make sure we do return something (so other code does not have to deal
// with an empty list).
if ( level_list.empty() )
level_list.push_back(make_pair(&null_name, &null_stats));
return level_list;
}
config write_stats()
{
config res;

View file

@ -111,6 +111,10 @@ namespace statistics
void reset_turn_stats(const std::string & save_id);
stats calculate_stats(const std::string & save_id);
/// Stats (and name) for each scenario. The pointers are never NULL.
typedef std::vector< std::pair<const std::string *, const stats *> > levels;
/// Returns a list of names and stats for each scenario in the current campaign.
levels level_stats(const std::string & save_id);
} // end namespace statistics
#endif

View file

@ -22,6 +22,12 @@
#include "game_display.hpp"
bool statistics_dialog::use_campaign_ = false;
// These values just need to be larger than the number of rows in the dialog.
static const int BUTTON_SCENE = 101;
static const int BUTTON_TOGGLE = 102;
namespace {
#ifdef LOW_MEM
@ -51,7 +57,8 @@ std::vector<std::string> create_unit_table(const statistics::stats::str_int_map&
void make_damage_line(std::vector<std::string>& items, const std::string& header,
const long long& damage, const long long& expected,
const long long& turn_damage, const long long& turn_expected)
const long long& turn_damage, const long long& turn_expected,
bool show_this_turn)
{
int shift = statistics::stats::decimal_shift;
@ -64,20 +71,31 @@ void make_damage_line(std::vector<std::string>& items, const std::string& header
<< (expected * 10 + shift / 2) / shift * 0.1
<< COLUMN_SEPARATOR
<< ((dsa < 0) ^ (expected < 0) ? "" : "+")
<< (expected == 0 ? 0 : 100 * dsa / expected)
<< '%' << COLUMN_SEPARATOR
<< COLUMN_SEPARATOR
<< turn_damage << " / "
<< (turn_expected * 10 + shift / 2) / shift * 0.1
<< COLUMN_SEPARATOR
<< ((dst < 0) ^ (turn_expected < 0) ? "" : "+")
<< (turn_expected == 0 ? 0 : 100 * dst / turn_expected)
<< '%';
<< (expected == 0 ? 0 : 100 * dsa / expected) << '%';
if ( show_this_turn ) {
str << COLUMN_SEPARATOR
<< COLUMN_SEPARATOR
<< turn_damage << " / "
<< (turn_expected * 10 + shift / 2) / shift * 0.1
<< COLUMN_SEPARATOR
<< ((dst < 0) ^ (turn_expected < 0) ? "" : "+")
<< (turn_expected == 0 ? 0 : 100 * dst / turn_expected) << '%';
}
items.push_back(str.str());
}
} //end anonymous namespace
/**
* Picks out the stats structure that was selected for displaying.
*/
inline const statistics::stats & statistics_dialog::current_stats()
{
return use_campaign_ ? campaign_ : *scenarios_[scenario_index_].second;
}
void statistics_dialog::action(gui::dialog_process_info &dp_info)
{
int sel = get_menu().selection();
@ -96,26 +114,38 @@ void statistics_dialog::action(gui::dialog_process_info &dp_info)
case gui::CLOSE_DIALOG:
break;
case 0:
items_sub = create_unit_table(stats_.recruits, team_num_);
items_sub = create_unit_table(current_stats().recruits, team_num_);
title = _("Recruits");
break;
case 1:
items_sub = create_unit_table(stats_.recalls, team_num_);
items_sub = create_unit_table(current_stats().recalls, team_num_);
title = _("Recalls");
break;
case 2:
items_sub = create_unit_table(stats_.advanced_to, team_num_);
items_sub = create_unit_table(current_stats().advanced_to, team_num_);
title = _("Advancements");
break;
case 3:
items_sub = create_unit_table(stats_.deaths, team_num_);
items_sub = create_unit_table(current_stats().deaths, team_num_);
title = _("Losses");
break;
case 4:
items_sub = create_unit_table(stats_.killed, team_num_);
/** @todo FIXME? Perhaps killed units shouldn't have the same team-color as your own. */
// Give kills a (probably) different team color.
items_sub = create_unit_table(current_stats().killed, team_num_ == 1 ? 2 : 1);
title = _("Kills");
break;
case BUTTON_SCENE:
// Scenario selection.
do_scene_selection();
set_result(gui::CONTINUE_DIALOG);
break;
case BUTTON_TOGGLE:
// Toggle between campaign and scenario stats.
display_stats(!use_campaign_);
set_result(gui::CONTINUE_DIALOG);
break;
default:
break;
}
@ -128,6 +158,7 @@ void statistics_dialog::action(gui::dialog_process_info &dp_info)
}
}
statistics_dialog::statistics_dialog(game_display &disp,
const std::string& title,
const unsigned int team,
@ -135,23 +166,71 @@ statistics_dialog::statistics_dialog(game_display &disp,
const std::string& player) :
dialog(disp, title, "", gui::NULL_DIALOG),
detail_btn_(new gui::standard_dialog_button(disp.video(), _("Details"), 0 , false)),
toggle_btn_(new gui::dialog_button(disp.video(), "", gui::button::TYPE_PRESS, BUTTON_TOGGLE)),
scene_btn_(new gui::dialog_button(disp.video(), _("Select Scenario"), gui::button::TYPE_PRESS, BUTTON_SCENE)),
player_name_(player),
stats_(),
campaign_(statistics::calculate_stats(team_id)),
scenarios_(statistics::level_stats(team_id)),
scenario_index_(scenarios_.size() - 1), // current scenario
team_num_(team),
unit_count_(5,0)
{
if ( scenarios_.size() > 1 ) {
add_button(scene_btn_, gui::dialog::BUTTON_EXTRA_LEFT);
add_button(toggle_btn_, gui::dialog::BUTTON_EXTRA_LEFT);
}
add_button(detail_btn_, gui::dialog::BUTTON_EXTRA);
add_button(new gui::standard_dialog_button(disp.video(), _("Close"), 1, true),
gui::dialog::BUTTON_STANDARD);
gui::dialog::BUTTON_STANDARD);
// Initialize the displayed data.
if ( use_campaign_ || scenarios_.size() == 1 )
display_stats(use_campaign_);
else {
// Starting with the scenario stats, but we need to make sure the
// window is wide enough for the campaign stats.
display_stats(true);
layout();
display_stats(false);
}
}
statistics_dialog::~statistics_dialog()
{
}
/**
* Fills in the text to be displayed in the dialog.
* This also updates the scenario/campaign toggle button.
*
* @param[in] campaign Indicates whether or not the campaign stats are to
* be displayed.
*/
void statistics_dialog::display_stats(bool campaign)
{
// Record which stats we will display.
use_campaign_ = campaign;
const statistics::stats & stats = current_stats();
const bool show_this_turn =
use_campaign_ || scenario_index_ + 1 == scenarios_.size();
stats_ = statistics::calculate_stats(team_id);
int n, cost;
std::vector<std::string> items;
// The heading for the menu items:
{
std::stringstream str;
str << HEADING_PREFIX
<< COLUMN_SEPARATOR
<< font::BOLD_TEXT << (use_campaign_ ? _("Campaign") : _("Scenario"));
items.push_back(str.str());
}
// Prepare the menu items
{
std::stringstream str;
n = statistics::sum_str_int_map(stats_.recruits);
cost = stats_.recruit_cost;
n = statistics::sum_str_int_map(stats.recruits);
cost = stats.recruit_cost;
unit_count_[0] = n;
str << _("Recruits") << COLUMN_SEPARATOR << n
<< COLUMN_SEPARATOR
@ -161,8 +240,8 @@ statistics_dialog::statistics_dialog(game_display &disp,
}
{
std::stringstream str;
n = statistics::sum_str_int_map(stats_.recalls);
cost = stats_.recall_cost;
n = statistics::sum_str_int_map(stats.recalls);
cost = stats.recall_cost;
unit_count_[1] = n;
str << _("Recalls") << COLUMN_SEPARATOR << n
<< COLUMN_SEPARATOR
@ -172,16 +251,16 @@ statistics_dialog::statistics_dialog(game_display &disp,
}
{
std::stringstream str;
n = statistics::sum_str_int_map(stats_.advanced_to);
n = statistics::sum_str_int_map(stats.advanced_to);
unit_count_[2] = n;
str << _("Advancements") << COLUMN_SEPARATOR << n;
items.push_back(str.str());
}
{
std::stringstream str;
n = statistics::sum_str_int_map(stats_.deaths);
n = statistics::sum_str_int_map(stats.deaths);
unit_count_[3] = n;
cost = statistics::sum_cost_str_int_map(stats_.deaths);
cost = statistics::sum_cost_str_int_map(stats.deaths);
str << _("Losses") << COLUMN_SEPARATOR << n
<< COLUMN_SEPARATOR
<< COLUMN_SEPARATOR << IMAGE_PREFIX << "themes/gold-t.png"
@ -190,9 +269,9 @@ statistics_dialog::statistics_dialog(game_display &disp,
}
{
std::stringstream str;
n = statistics::sum_str_int_map(stats_.killed);
n = statistics::sum_str_int_map(stats.killed);
unit_count_[4] = n;
cost = statistics::sum_cost_str_int_map(stats_.killed);
cost = statistics::sum_cost_str_int_map(stats.killed);
str << _("Kills") << COLUMN_SEPARATOR << n
<< COLUMN_SEPARATOR
<< COLUMN_SEPARATOR << IMAGE_PREFIX << "themes/gold-t.png"
@ -203,27 +282,52 @@ statistics_dialog::statistics_dialog(game_display &disp,
{
std::stringstream str;
str << font::BOLD_TEXT << _("Damage")
<< COLUMN_SEPARATOR << _("Overall") << COLUMN_SEPARATOR
<< COLUMN_SEPARATOR
<< COLUMN_SEPARATOR << _("This Turn");
<< COLUMN_SEPARATOR << _("Overall");
if ( show_this_turn ) {
str << COLUMN_SEPARATOR
<< COLUMN_SEPARATOR
<< COLUMN_SEPARATOR << _("This Turn");
}
items.push_back(str.str());
}
make_damage_line(items, _("Inflicted"),
stats_.damage_inflicted,
stats_.expected_damage_inflicted,
stats_.turn_damage_inflicted,
stats_.turn_expected_damage_inflicted);
stats.damage_inflicted, stats.expected_damage_inflicted,
stats.turn_damage_inflicted, stats.turn_expected_damage_inflicted,
show_this_turn);
make_damage_line(items, _("Taken"),
stats_.damage_taken,
stats_.expected_damage_taken,
stats_.turn_damage_taken,
stats_.turn_expected_damage_taken);
stats.damage_taken, stats.expected_damage_taken,
stats.turn_damage_taken, stats.turn_expected_damage_taken,
show_this_turn);
set_menu(items);
set_menu_items(items, true);
toggle_btn_->set_label(use_campaign_ ? _("Scenario") : _("Campaign"));
scene_btn_->enable(!use_campaign_);
}
statistics_dialog::~statistics_dialog()
/**
* Implements the scenario selection popup.
*/
void statistics_dialog::do_scene_selection()
{
// Prepare a list of scenario names.
std::vector<std::string> names;
for ( size_t i = 0; i != scenarios_.size(); ++i )
names.push_back(*scenarios_[i].first);
// Let the player choose a scenario.
SDL_Rect const &loc = scene_btn_->location();
size_t new_scenario = gui::show_dialog(get_display(), NULL, "", "",
gui::MESSAGE, &names, NULL, "", NULL,
-1, NULL, loc.x, loc.y + loc.h);
if ( new_scenario != scenario_index_ && new_scenario < scenarios_.size() )
{
// Switch the displayed data to the selected scenario.
scenario_index_ = new_scenario;
scene_btn_->set_label(*scenarios_[new_scenario].first);
display_stats(false);
}
}

View file

@ -37,11 +37,25 @@ public:
protected:
void action(gui::dialog_process_info &dp_info);
private:
/// Picks out the stats structure that was selected for displaying.
inline const statistics::stats & current_stats();
/// Fills in the text to be displayed in the dialog.
void display_stats(bool campaign);
/// Implements the scenario selection popup.
void do_scene_selection();
gui::dialog_button *detail_btn_;
gui::dialog_button *toggle_btn_;
gui::dialog_button *scene_btn_;
std::string player_name_;
statistics::stats stats_;
const statistics::stats campaign_;
const statistics::levels scenarios_;
size_t scenario_index_;
unsigned int team_num_;
std::vector<int> unit_count_;
// This is static so the setting can be remembered throughout the session.
static bool use_campaign_;
};
#endif