GameStatistics.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*
  2. * GameStatistics.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "GameStatistics.h"
  12. #include "../CPlayerState.h"
  13. #include "../constants/StringConstants.h"
  14. #include "../VCMIDirs.h"
  15. #include "CGameState.h"
  16. #include "TerrainHandler.h"
  17. #include "StartInfo.h"
  18. #include "HighScore.h"
  19. #include "../mapObjects/CGHeroInstance.h"
  20. #include "../mapObjects/CGTownInstance.h"
  21. #include "../mapObjects/CGObjectInstance.h"
  22. #include "../mapObjects/MiscObjects.h"
  23. #include "../mapping/CMap.h"
  24. #include "../entities/building/CBuilding.h"
  25. #include "../serializer/JsonDeserializer.h"
  26. #include "../serializer/JsonUpdater.h"
  27. #include "../entities/ResourceTypeHandler.h"
  28. VCMI_LIB_NAMESPACE_BEGIN
  29. void StatisticDataSet::add(StatisticDataSetEntry entry)
  30. {
  31. data.push_back(entry);
  32. }
  33. StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs, const StatisticDataSet & accumulatedData)
  34. {
  35. StatisticDataSetEntry data;
  36. HighScoreParameter param = HighScore::prepareHighScores(gs, ps->color, false);
  37. HighScoreCalculation scenarioHighScores;
  38. scenarioHighScores.parameters.push_back(param);
  39. scenarioHighScores.isCampaign = false;
  40. data.map = gs->getMap().name.toString();
  41. data.timestamp = std::time(nullptr);
  42. data.day = gs->getDate(Date::DAY);
  43. data.player = ps->color;
  44. data.playerName = gs->getStartInfo()->playerInfos.at(ps->color).name;
  45. data.team = ps->team;
  46. data.isHuman = ps->isHuman();
  47. data.status = ps->status;
  48. data.resources = ps->resources;
  49. data.numberHeroes = ps->getHeroes().size();
  50. data.numberTowns = gs->howManyTowns(ps->color);
  51. data.numberArtifacts = Statistic::getNumberOfArts(ps);
  52. data.numberDwellings = Statistic::getNumberOfDwellings(ps);
  53. data.armyStrength = Statistic::getArmyStrength(ps, true);
  54. data.totalExperience = Statistic::getTotalExperience(ps);
  55. data.income = Statistic::getIncome(gs, ps);
  56. data.mapExploredRatio = Statistic::getMapExploredRatio(gs, ps->color);
  57. data.obeliskVisitedRatio = Statistic::getObeliskVisitedRatio(gs, ps->team);
  58. data.townBuiltRatio = Statistic::getTownBuiltRatio(ps);
  59. data.hasGrail = param.hasGrail;
  60. data.numMines = Statistic::getNumMines(gs, ps);
  61. data.score = scenarioHighScores.calculate().total;
  62. data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0;
  63. data.numBattlesNeutral = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numBattlesNeutral : 0;
  64. data.numBattlesPlayer = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numBattlesPlayer : 0;
  65. data.numWinBattlesNeutral = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numWinBattlesNeutral : 0;
  66. data.numWinBattlesPlayer = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numWinBattlesPlayer : 0;
  67. data.numHeroSurrendered = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numHeroSurrendered : 0;
  68. data.numHeroEscaped = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).numHeroEscaped : 0;
  69. data.spentResourcesForArmy = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources();
  70. data.spentResourcesForBuildings = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources();
  71. data.tradeVolume = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).tradeVolume : TResources();
  72. data.eventCapturedTown = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).lastCapturedTownDay == gs->getDate(Date::DAY) : false;
  73. data.eventDefeatedStrongestHero = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).lastDefeatedStrongestHeroDay == gs->getDate(Date::DAY) : false;
  74. data.movementPointsUsed = accumulatedData.accumulatedValues.count(ps->color) ? accumulatedData.accumulatedValues.at(ps->color).movementPointsUsed : 0;
  75. return data;
  76. }
  77. void StatisticDataSetEntry::serializeJson(JsonSerializeFormat & handler)
  78. {
  79. handler.serializeString("map", map);
  80. handler.serializeInt("timestamp", timestamp);
  81. handler.serializeInt("day", day);
  82. handler.serializeId("player", player, PlayerColor::CANNOT_DETERMINE);
  83. handler.serializeString("playerName", playerName);
  84. handler.serializeInt("team", team);
  85. handler.serializeBool("isHuman", isHuman);
  86. handler.serializeEnum("status", status, {"ingame", "loser", "winner"});
  87. resources.serializeJson(handler, "resources");
  88. handler.serializeInt("numberHeroes", numberHeroes);
  89. handler.serializeInt("numberTowns", numberTowns);
  90. handler.serializeInt("numberArtifacts", numberArtifacts);
  91. handler.serializeInt("numberDwellings", numberDwellings);
  92. handler.serializeInt("armyStrength", armyStrength);
  93. handler.serializeInt("totalExperience", totalExperience);
  94. handler.serializeInt("income", income);
  95. handler.serializeFloat("mapExploredRatio", mapExploredRatio);
  96. handler.serializeFloat("obeliskVisitedRatio", obeliskVisitedRatio);
  97. handler.serializeFloat("townBuiltRatio", townBuiltRatio);
  98. handler.serializeBool("hasGrail", hasGrail);
  99. {
  100. auto zonesData = handler.enterStruct("numMines");
  101. for(auto & idx : LIBRARY->resourceTypeHandler->getAllObjects())
  102. if(idx != GameResID::MITHRIL)
  103. handler.serializeInt(idx.toResource()->getJsonKey(), numMines[idx], 0);
  104. }
  105. handler.serializeInt("score", score);
  106. handler.serializeInt("maxHeroLevel", maxHeroLevel);
  107. handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
  108. handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
  109. handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
  110. handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
  111. handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
  112. handler.serializeInt("numHeroEscaped", numHeroEscaped);
  113. spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
  114. spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
  115. tradeVolume.serializeJson(handler, "tradeVolume");
  116. handler.serializeBool("eventCapturedTown", eventCapturedTown);
  117. handler.serializeBool("eventDefeatedStrongestHero", eventDefeatedStrongestHero);
  118. handler.serializeInt("movementPointsUsed", movementPointsUsed);
  119. }
  120. void StatisticDataSet::PlayerAccumulatedValueStorage::serializeJson(JsonSerializeFormat & handler)
  121. {
  122. handler.serializeInt("numBattlesNeutral", numBattlesNeutral);
  123. handler.serializeInt("numBattlesPlayer", numBattlesPlayer);
  124. handler.serializeInt("numWinBattlesNeutral", numWinBattlesNeutral);
  125. handler.serializeInt("numWinBattlesPlayer", numWinBattlesPlayer);
  126. handler.serializeInt("numHeroSurrendered", numHeroSurrendered);
  127. handler.serializeInt("numHeroEscaped", numHeroEscaped);
  128. spentResourcesForArmy.serializeJson(handler, "spentResourcesForArmy");
  129. spentResourcesForBuildings.serializeJson(handler, "spentResourcesForBuildings");
  130. tradeVolume.serializeJson(handler, "tradeVolume");
  131. handler.serializeInt("movementPointsUsed", movementPointsUsed);
  132. handler.serializeInt("lastCapturedTownDay", lastCapturedTownDay);
  133. handler.serializeInt("lastDefeatedStrongestHeroDay", lastDefeatedStrongestHeroDay);
  134. }
  135. void StatisticDataSet::serializeJson(JsonSerializeFormat & handler)
  136. {
  137. {
  138. auto eventsHandler = handler.enterArray("data");
  139. eventsHandler.syncSize(data, JsonNode::JsonType::DATA_VECTOR);
  140. eventsHandler.serializeStruct(data);
  141. }
  142. {
  143. auto eventsHandler = handler.enterStruct("accumulatedValues");
  144. for(auto & val : accumulatedValues)
  145. eventsHandler->serializeStruct(GameConstants::PLAYER_COLOR_NAMES[val.first], val.second);
  146. }
  147. }
  148. std::string StatisticDataSet::toCsv(std::string sep) const
  149. {
  150. std::stringstream ss;
  151. auto resources = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS};
  152. ss << "Map" << sep;
  153. ss << "Timestamp" << sep;
  154. ss << "Day" << sep;
  155. ss << "Player" << sep;
  156. ss << "PlayerName" << sep;
  157. ss << "Team" << sep;
  158. ss << "IsHuman" << sep;
  159. ss << "Status" << sep;
  160. ss << "NumberHeroes" << sep;
  161. ss << "NumberTowns" << sep;
  162. ss << "NumberArtifacts" << sep;
  163. ss << "NumberDwellings" << sep;
  164. ss << "ArmyStrength" << sep;
  165. ss << "TotalExperience" << sep;
  166. ss << "Income" << sep;
  167. ss << "MapExploredRatio" << sep;
  168. ss << "ObeliskVisitedRatio" << sep;
  169. ss << "TownBuiltRatio" << sep;
  170. ss << "HasGrail" << sep;
  171. ss << "Score" << sep;
  172. ss << "MaxHeroLevel" << sep;
  173. ss << "NumBattlesNeutral" << sep;
  174. ss << "NumBattlesPlayer" << sep;
  175. ss << "NumWinBattlesNeutral" << sep;
  176. ss << "NumWinBattlesPlayer" << sep;
  177. ss << "NumHeroSurrendered" << sep;
  178. ss << "NumHeroEscaped" << sep;
  179. ss << "EventCapturedTown" << sep;
  180. ss << "EventDefeatedStrongestHero" << sep;
  181. ss << "MovementPointsUsed";
  182. for(auto & resource : resources)
  183. ss << sep << resource.toResource()->getJsonKey();
  184. for(auto & resource : resources)
  185. ss << sep << resource.toResource()->getJsonKey() + "Mines";
  186. for(auto & resource : resources)
  187. ss << sep << resource.toResource()->getJsonKey() + "SpentResourcesForArmy";
  188. for(auto & resource : resources)
  189. ss << sep << resource.toResource()->getJsonKey() + "SpentResourcesForBuildings";
  190. for(auto & resource : resources)
  191. ss << sep << resource.toResource()->getJsonKey() + "TradeVolume";
  192. ss << "\r\n";
  193. for(auto & entry : data)
  194. {
  195. ss << entry.map << sep;
  196. ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H:%M:%S") << sep;
  197. ss << entry.day << sep;
  198. ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << sep;
  199. ss << entry.playerName << sep;
  200. ss << entry.team.getNum() << sep;
  201. ss << entry.isHuman << sep;
  202. ss << static_cast<int>(entry.status) << sep;
  203. ss << entry.numberHeroes << sep;
  204. ss << entry.numberTowns << sep;
  205. ss << entry.numberArtifacts << sep;
  206. ss << entry.numberDwellings << sep;
  207. ss << entry.armyStrength << sep;
  208. ss << entry.totalExperience << sep;
  209. ss << entry.income << sep;
  210. ss << entry.mapExploredRatio << sep;
  211. ss << entry.obeliskVisitedRatio << sep;
  212. ss << entry.townBuiltRatio << sep;
  213. ss << entry.hasGrail << sep;
  214. ss << entry.score << sep;
  215. ss << entry.maxHeroLevel << sep;
  216. ss << entry.numBattlesNeutral << sep;
  217. ss << entry.numBattlesPlayer << sep;
  218. ss << entry.numWinBattlesNeutral << sep;
  219. ss << entry.numWinBattlesPlayer << sep;
  220. ss << entry.numHeroSurrendered << sep;
  221. ss << entry.numHeroEscaped << sep;
  222. ss << entry.eventCapturedTown << sep;
  223. ss << entry.eventDefeatedStrongestHero << sep;
  224. ss << entry.movementPointsUsed;
  225. for(auto & resource : resources)
  226. ss << sep << entry.resources[resource];
  227. for(auto & resource : resources)
  228. ss << sep << entry.numMines.at(resource);
  229. for(auto & resource : resources)
  230. ss << sep << entry.spentResourcesForArmy[resource];
  231. for(auto & resource : resources)
  232. ss << sep << entry.spentResourcesForBuildings[resource];
  233. for(auto & resource : resources)
  234. ss << sep << entry.tradeVolume[resource];
  235. ss << "\r\n";
  236. }
  237. return ss.str();
  238. }
  239. std::string StatisticDataSet::writeCsv() const
  240. {
  241. const boost::filesystem::path outPath = VCMIDirs::get().userCachePath() / "statistic";
  242. boost::filesystem::create_directories(outPath);
  243. const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv");
  244. std::ofstream file(filePath.c_str());
  245. std::string csv = toCsv(";");
  246. file << csv;
  247. return filePath.string();
  248. }
  249. //calculates total number of artifacts that belong to given player
  250. int Statistic::getNumberOfArts(const PlayerState * ps)
  251. {
  252. int ret = 0;
  253. for(auto h : ps->getHeroes())
  254. {
  255. ret += h->artifactsInBackpack.size() + h->artifactsWorn.size();
  256. }
  257. return ret;
  258. }
  259. int Statistic::getNumberOfDwellings(const PlayerState * ps)
  260. {
  261. int ret = 0;
  262. for(const auto * obj : ps->getOwnedObjects())
  263. if (!obj->asOwnable()->providedCreatures().empty())
  264. ret += 1;
  265. return ret;
  266. }
  267. // get total strength of player army
  268. si64 Statistic::getArmyStrength(const PlayerState * ps, bool withTownGarrison)
  269. {
  270. si64 str = 0;
  271. for(auto h : ps->getHeroes())
  272. {
  273. if(!h->isGarrisoned() || withTownGarrison) //original h3 behavior
  274. str += h->getArmyStrength();
  275. }
  276. return str;
  277. }
  278. // get total experience of all heroes
  279. si64 Statistic::getTotalExperience(const PlayerState * ps)
  280. {
  281. si64 tmp = 0;
  282. for(auto h : ps->getHeroes())
  283. tmp += h->exp;
  284. return tmp;
  285. }
  286. // get total gold income
  287. int Statistic::getIncome(const CGameState * gs, const PlayerState * ps)
  288. {
  289. int totalIncome = 0;
  290. //Heroes can produce gold as well - skill, specialty or arts
  291. for(const auto & object : ps->getOwnedObjects())
  292. totalIncome += object->asOwnable()->dailyIncome()[EGameResID::GOLD];
  293. return totalIncome;
  294. }
  295. float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player)
  296. {
  297. float visible = 0.0;
  298. float numTiles = 0.0;
  299. for(int layer = 0; layer < gs->getMap().levels(); layer++)
  300. for(int y = 0; y < gs->getMap().height; ++y)
  301. for(int x = 0; x < gs->getMap().width; ++x)
  302. {
  303. TerrainTile tile = gs->getMap().getTile(int3(x, y, layer));
  304. if(tile.blocked() && !tile.visitable())
  305. continue;
  306. if(gs->isVisibleFor(int3(x, y, layer), player))
  307. visible++;
  308. numTiles++;
  309. }
  310. return visible / numTiles;
  311. }
  312. const CGHeroInstance * Statistic::findBestHero(const CGameState * gs, const PlayerColor & color)
  313. {
  314. const auto &h = gs->players.at(color).getHeroes();
  315. if(h.empty())
  316. return nullptr;
  317. //best hero will be that with highest exp
  318. int best = 0;
  319. for(int b=1; b<h.size(); ++b)
  320. {
  321. if(h[b]->exp > h[best]->exp)
  322. {
  323. best = b;
  324. }
  325. }
  326. return h[best];
  327. }
  328. std::vector<std::vector<PlayerColor>> Statistic::getRank(std::vector<std::pair<PlayerColor, si64>> stats)
  329. {
  330. std::sort(stats.begin(), stats.end(), [](const std::pair<PlayerColor, si64> & a, const std::pair<PlayerColor, si64> & b) { return a.second > b.second; });
  331. //put first element
  332. std::vector< std::vector<PlayerColor> > ret;
  333. ret.push_back( { stats[0].first } );
  334. //the rest of elements
  335. for(int g=1; g<stats.size(); ++g)
  336. {
  337. if(stats[g].second == stats[g-1].second)
  338. {
  339. (ret.end()-1)->push_back( stats[g].first );
  340. }
  341. else
  342. {
  343. //create next occupied rank
  344. ret.push_back( { stats[g].first });
  345. }
  346. }
  347. return ret;
  348. }
  349. int Statistic::getObeliskVisited(const CGameState * gs, const TeamID & t)
  350. {
  351. if(gs->getMap().obelisksVisited.count(t))
  352. return gs->getMap().obelisksVisited.at(t);
  353. else
  354. return 0;
  355. }
  356. float Statistic::getObeliskVisitedRatio(const CGameState * gs, const TeamID & t)
  357. {
  358. if(!gs->getMap().obeliskCount)
  359. return 0;
  360. return static_cast<float>(getObeliskVisited(gs, t)) / gs->getMap().obeliskCount;
  361. }
  362. std::map<EGameResID, int> Statistic::getNumMines(const CGameState * gs, const PlayerState * ps)
  363. {
  364. std::map<EGameResID, int> tmp;
  365. for(auto & res : LIBRARY->resourceTypeHandler->getAllObjects())
  366. tmp[res] = 0;
  367. for(const auto * object : ps->getOwnedObjects())
  368. {
  369. //Mines
  370. if(object->ID == Obj::MINE || object->ID == Obj::ABANDONED_MINE)
  371. {
  372. const auto * mine = dynamic_cast<const CGMine *>(object);
  373. assert(mine);
  374. tmp[mine->producedResource]++;
  375. }
  376. }
  377. return tmp;
  378. }
  379. float Statistic::getTownBuiltRatio(const PlayerState * ps)
  380. {
  381. float built = 0.0;
  382. float total = 0.0;
  383. for(const auto & t : ps->getTowns())
  384. {
  385. built += t->getBuildings().size();
  386. for(const auto & b : t->getTown()->buildings)
  387. if(!t->forbiddenBuildings.count(b.first))
  388. total += 1;
  389. }
  390. if(total < 1)
  391. return 0;
  392. return built / total;
  393. }
  394. VCMI_LIB_NAMESPACE_END