TT6: Simplify stat tracking with a std::map

This post is a technical post on how I put together stat tracking for Isotower, for the goals system.

A few days ago, I decided to add goals, or missions, or tasks, or quests to Isotower. I don’t have a name for them in the game, but in the code, they’re goals. Essentially, each goal would track a few stats, like how many people of a certain social class were in the tower, or how many rooms of a certain type were built, or how much money was being made per day, or per week. I was already tracking a lot of these stats, but they were often in their own variables stored in factories, or in the finance code. Writing code to look up this information in an easy way would get complicated if each stat had to have a different function pointer, or if there had to be a giant switch/case statement to know which stat pointed to which factory function. Pointers could have worked, and been fast, but there still would have been a need for a switch/case statement to know what to point to for each part of the goal.

Instead, I created a very simple factory to store a collection of stats, and used a std::map<std::string, double> to store everything. Double because finances and averages would be tracked, as well. This lets me store each stat as a std::string for the goals, and then simply look up the value in the StatTracker data to see if the goal has been reached. The code for the goals is easy to use and understand, I can track stats simply by simply calling StatTracker::INSTANCE().addValue(“new stat to track”); and it all ends up working smoothly. The final code came out to 4 public functions, and a mostly used for debug toString() function. The code is pasted here, because why not.

StatTracker.hpp
#ifndef _STAT_TRACKER_HPP
#define _STAT_TRACKER_HPP

#include <map>
#include <string>

class StatTracker {
private:
 std::map<std::string, double> stats;

 StatTracker();
 StatTracker(StatTracker const&);
 void operator=(StatTracker const&);
public:
 static StatTracker & INSTANCE();

 void addValue(std::string);
 void setValue(std::string, double);
 void changeValue(std::string, double);
 double getValue(std::string);

 std::string toString();
};

#endif
StatTracker.cpp
#include "source/Managers/StatTracker.hpp"

#include <sstream>

void StatTracker::addValue(std::string index)
{
 this->stats.insert(std::pair<std::string, double>(index, 0));
}

void StatTracker::setValue(std::string index, double value)
{
 this->stats[index] = value;
}

void StatTracker::changeValue(std::string index, double value)
{
 this->stats[index] += value;
}

double StatTracker::getValue(std::string index)
{
 return this->stats[index];
}

std::string StatTracker::toString()
{
 std::stringstream temp;

 for (const auto &pair : this->stats)
 {
 temp<<pair.first<<": "<<pair.second<<".\n";
 }

 return temp.str();
}

StatTracker::StatTracker() { }

StatTracker & StatTracker::INSTANCE()
{
 static StatTracker instance;

 return instance;
}

69 lines in total (25+44). Much easier to use and understand.

Sidenote: I use this singleton pattern in almost all the areas of my code. It might not be the “right” way to program things, but it’s easy to use, and at the moment, it does what I need.