@@ -67,11 +67,12 @@ void MapStats::processGame(const Game &game, const Players &players)
6767 }
6868 }
6969
70- std::chrono::year_month_day date{game.date ().year (), game.date ().month (), std::chrono::day{1 }};
70+ std::chrono::year_month_day keyDate{game.date ().year (), game.date ().month (), std::chrono::day{1 }};
71+ std::chrono::sys_days gameDate = game.sysDate ();
7172
72- _gameCountsPerMonthAndPlayer[date ][mapName].count ++;
73- _gameCountsPerMonthAndPlayer[date ][mapName].differentPlayers .insert (game.userId (0 ));
74- _gameCountsPerMonthAndPlayer[date ][mapName].differentPlayers .insert (game.userId (1 ));
73+ _gameCountsPerMonthAndPlayer[keyDate ][mapName].count ++;
74+ _gameCountsPerMonthAndPlayer[keyDate ][mapName].differentPlayers .insert (game.userId (0 ));
75+ _gameCountsPerMonthAndPlayer[keyDate ][mapName].differentPlayers .insert (game.userId (1 ));
7576
7677 // Ignore games with duration 0. These are probably manually added game, e.g. tournament games.
7778 if (game.duration () > 0 )
@@ -136,14 +137,19 @@ void MapStats::processGame(const Game &game, const Players &players)
136137 std::vector<int > loserELOs = losersELOs (game);
137138
138139 Upset upset{ game.date (), winnerIds, loserIds, mapName, winnerFactions, loserFactions, winnerELOs, loserELOs, diff };
139- _upsetsMonthly[date ].insert (upset);
140- if (_upsetsMonthly[date ].size () > 20 )
140+ _upsetsMonthly[keyDate ].insert (upset);
141+ if (_upsetsMonthly[keyDate ].size () > 20 )
141142 {
142- _upsetsMonthly[date ].erase (std::prev (_upsetsMonthly[date ].end ()));
143+ _upsetsMonthly[keyDate ].erase (std::prev (_upsetsMonthly[keyDate ].end ()));
143144 }
144145
145- auto yearBoundary = floor<std::chrono::days>(std::chrono::system_clock::now ()) - std::chrono::days (365 );
146- if (date >= yearBoundary)
146+ std::chrono::time_point<std::chrono::system_clock, std::chrono::days> today
147+ = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now ());
148+
149+ std::chrono::time_point<std::chrono::system_clock, std::chrono::days> yearBoundary
150+ = today - std::chrono::days (365 );
151+
152+ if (gameDate >= yearBoundary)
147153 {
148154 _upsetsLast12Month.insert (upset);
149155 }
@@ -152,8 +158,11 @@ void MapStats::processGame(const Game &game, const Players &players)
152158 _upsetsLast12Month.erase (std::prev (_upsetsLast12Month.end ()));
153159 }
154160
155- auto monthBoundary = floor<std::chrono::days>(std::chrono::system_clock::now ()) - std::chrono::days (31 );
156- if (date >= monthBoundary)
161+ std::chrono::time_point<std::chrono::system_clock, std::chrono::days> monthBoundary
162+ = today - std::chrono::days (31 );
163+
164+
165+ if (gameDate >= monthBoundary)
157166 {
158167 _upsetsLast30Days.insert (upset);
159168 }
@@ -195,18 +204,21 @@ void MapStats::processGame(const Game &game, const Players &players)
195204 Probabilities &probsWinners = _teamStats[winnerTeamId];
196205 Probabilities &probsLosers = _teamStats[loserTeamId];
197206
198- _lastTeamELOs[winnerTeamId].first = (winners[0 ].userId < winners[1 ].userId ) ? winners[1 ].elo : winners[0 ].elo ;
199- _lastTeamELOs[winnerTeamId].second = (winners[1 ].userId > winners[0 ].userId ) ? winners[0 ].elo : winners[1 ].elo ;
200- _lastTeamELOs[loserTeamId].first = (losers[0 ].userId < losers[1 ].userId ) ? losers[1 ].elo : losers[0 ].elo ;
201- _lastTeamELOs[loserTeamId].second = (losers[1 ].userId > losers[0 ].userId ) ? losers[0 ].elo : losers[1 ].elo ;
207+ double winnerFirstElo = (winners[0 ].userId < winners[1 ].userId ) ? winners[1 ].elo : winners[0 ].elo ;
208+ double winnerSecondElo = (winners[1 ].userId > winners[0 ].userId ) ? winners[0 ].elo : winners[1 ].elo ;
209+ _lastTeamELOs[winnerTeamId].push_back ({winnerFirstElo, winnerSecondElo});
210+
211+ double loserFirstElo = (losers[0 ].userId < losers[1 ].userId ) ? losers[1 ].elo : losers[0 ].elo ;
212+ double loserSecondElo = (losers[1 ].userId > losers[0 ].userId ) ? losers[0 ].elo : losers[1 ].elo ;
213+ _lastTeamELOs[loserTeamId].push_back ({loserFirstElo, loserSecondElo});
202214
203215 Rating winnerRating (winners[0 ].elo + winners[1 ].elo , winners[0 ].deviation + winners[1 ].deviation , glicko::initialVolatility);
204216 Rating losersRating (losers[0 ].elo + losers[1 ].elo , losers[0 ].deviation + losers[1 ].deviation , glicko::initialVolatility);
205217
206218 double expectedWinRate = winnerRating.e_star (losersRating.toArray (), 0.0 );
207219
208- probsWinners.addGame (expectedWinRate, 1 );
209- probsLosers.addGame (1.0 - expectedWinRate, 0 );
220+ probsWinners.addGame (expectedWinRate, game. sysDate (), 1 );
221+ probsLosers.addGame (1.0 - expectedWinRate, game. sysDate (), 0 );
210222 }
211223
212224 // Process longest games.
@@ -270,7 +282,7 @@ void MapStats::processGame(const Game &game, const Players &players)
270282 return ;
271283 }
272284
273- _mapStats[factionSetup][mapName].addGame (expectedWinRate, (game.winnerIndex () == static_cast <int >(alliedPlayerIndex)));
285+ _mapStats[factionSetup][mapName].addGame (expectedWinRate, game. sysDate (), (game.winnerIndex () == static_cast <int >(alliedPlayerIndex)));
274286 }
275287 else if (factionSetup == factions::AvY)
276288 {
@@ -291,7 +303,7 @@ void MapStats::processGame(const Game &game, const Players &players)
291303 return ;
292304 }
293305
294- _mapStats[factionSetup][mapName].addGame (expectedWinRate, (game.winnerIndex () == static_cast <int >(alliedPlayerIndex)));
306+ _mapStats[factionSetup][mapName].addGame (expectedWinRate, game. sysDate (), (game.winnerIndex () == static_cast <int >(alliedPlayerIndex)));
295307 }
296308 else if (factionSetup == factions::YvS)
297309 {
@@ -312,34 +324,51 @@ void MapStats::processGame(const Game &game, const Players &players)
312324 return ;
313325 }
314326
315- _mapStats[factionSetup][mapName].addGame (expectedWinRate, (game.winnerIndex () == static_cast <int >(yuriPlayerIndex)));
327+ _mapStats[factionSetup][mapName].addGame (expectedWinRate, game. sysDate (), (game.winnerIndex () == static_cast <int >(yuriPlayerIndex)));
316328 }
317- }
329+
330+ } // void MapStats::processGame(const Game &game, const Players &players)
318331
319332/* !
320333 */
321- void MapStats::finalize (const std::filesystem::path &directory, const Players &players)
334+ void MapStats::finalize (const std::filesystem::path &directory, const Players &players, std::chrono::sys_days date )
322335{
323336 Log::info () << " Finalizing map statistics." ;
324337
325338 using json = nlohmann::json;
326339
327340 static std::vector<factions::Setup> factionSetups = { factions::AvS, factions::AvY, factions::YvS };
328341
342+ Log::info () << " Creating team stats." ;
343+
329344 for (std::map<uint64_t , Probabilities>::iterator it = _teamStats.begin (); it != _teamStats.end (); ++it)
330345 {
331346 uint64_t key = it->first ;
332347 Probabilities &value = it->second ;
333348 value.finalize ();
349+ ProbResult probToday = value.result (date);
350+ ProbResult probYesterday = value.result (date - std::chrono::days{3 });
334351 uint32_t player1 = static_cast <uint32_t >(key & 0xFFFFFFFF );
335352 uint32_t player2 = static_cast <uint32_t >(key >> 32 );
336353
337- // Exclude teams where no player has ELO > 1400.
338- if (value.count () > 20 && players[player1].isActive () && players[player2].isActive () && value.wins () > 1 &&
339- value.count () != value.wins () &&
340- (players[player1].elo (factions::Combined) > 1400 || players[player2].elo (factions::Combined) > 1400 ))
354+ auto eloDiff = [](const ProbResult &result) -> double {
355+ return -400.0 * std::log10 ((1.0 / result.normalized ) - 1.0 );
356+ };
357+ // Exclude teams where no player has ELO > 1300.
358+ if (probToday.games >= 20 && players[player1].isActive () && players[player2].isActive () && probToday.wins > 1 &&
359+ probToday.games != probToday.wins &&
360+ (players[player1].elo (factions::Combined) > 1300 || players[player2].elo (factions::Combined) > 1300 ))
361+ {
362+ Log::info () << players[player1].alias () << " + " << players[player2].alias () << " " << probToday.games << " /" << probToday.wins << " " << probToday.actual << " /" << probToday.expected ;
363+ _teams.insert ({key, probToday.games , probToday.wins , players[player1].elo (factions::Combined) + players[player2].elo (factions::Combined), eloDiff (probToday), probToday.lastGame });
364+ }
365+
366+ if (probYesterday.games >= 20 && players[player1].isActive () && players[player2].isActive () && probYesterday.wins > 1 &&
367+ probYesterday.games != probYesterday.wins &&
368+ (players[player1].elo (factions::Combined) > 1300 || players[player2].elo (factions::Combined) > 1300 ))
341369 {
342- _teams.insert ({key, value.count (), value.wins (), players[player1].elo (factions::Combined) + players[player2].elo (factions::Combined), value.eloDifference () });
370+ Log::info () << players[player1].alias () << " + " << players[player2].alias () << " " << probYesterday.games << " /" << probYesterday.wins << " " << probYesterday.actual << " /" << probYesterday.expected ;
371+ _yesterdaysTeams.insert ({key, probYesterday.games , probYesterday.wins , players[player1].elo (factions::Combined) + players[player2].elo (factions::Combined), eloDiff (probYesterday), probYesterday.lastGame });
343372 }
344373 }
345374
@@ -655,6 +684,12 @@ void MapStats::exportLongestGames(const std::filesystem::path &directory, const
655684
656685 for (auto it = _longestGames.begin (); it != _longestGames.end (); ++it)
657686 {
687+ if (it->winners .empty () || it->losers .empty ())
688+ {
689+ Log::error () << " No winners or losers while exporting longest game." ;
690+ continue ;
691+ }
692+
658693 json jLongestGame = json::object ();
659694 jLongestGame[" rank" ] = rank++;
660695 jLongestGame[" date" ] = stringtools::fromDate (it->date );
@@ -673,6 +708,8 @@ void MapStats::exportLongestGames(const std::filesystem::path &directory, const
673708 std::ofstream streamLongest (directory / (gamemodes::shortName (_gameMode) + " _longest_games.json" ));
674709 streamLongest << std::setw (4 ) << jLongestGames << std::endl;
675710 streamLongest.close ();
711+
712+ Log::verbose () << " Exported longest games." ;
676713}
677714
678715/* !
@@ -683,14 +720,18 @@ void MapStats::exportBestTeams(const std::filesystem::path &directory, const Pla
683720
684721 json data = json::object ();
685722
686- data[" description" ] = " Top 20 teams with the highest performance above predicted ELO" ;
723+ data[" description" ] = " Top 40 teams with the highest performance above predicted ELO" ;
687724 data[" columns" ] = json::array ({
688725 { { " index" , 0 }, { " header" , " #" } , { " name" , " rank" } },
689- { { " index" , 1 }, { " header" , " Names" } , { " name" , " names" } },
690- { { " index" , 2 }, { " header" , " Games" } , { " name" , " games" } },
691- { { " index" , 3 }, { " header" , " Team ELO" } , { " name" , " elo_team" } },
692- { { " index" , 4 }, { " header" , " Performance" } , { " name" , " performance" } },
693- { { " index" , 5 }, { " header" , " Difference" } , { " name" , " diff" }, { " info" , " Performance above team ELO." } }
726+ { { " index" , 1 }, { " header" , " ∆ #" } , { " name" , " delta_rank" } },
727+ { { " index" , 2 }, { " header" , " Names" } , { " name" , " names" } },
728+ { { " index" , 3 }, { " header" , " Games" } , { " name" , " games" } },
729+ { { " index" , 4 }, { " header" , " ∆ Games" } , { " name" , " delta_gamesplayed" } },
730+ { { " index" , 5 }, { " header" , " Last game" } , { " name" , " last_game" } },
731+ { { " index" , 6 }, { " header" , " Team ELO" } , { " name" , " elo_team" } },
732+ { { " index" , 7 }, { " header" , " Performance" } , { " name" , " performance" } },
733+ { { " index" , 8 }, { " header" , " Difference" } , { " name" , " diff" }, { " info" , " Performance above team ELO." } },
734+ { { " index" , 9 }, { " header" , " ∆" } , { " name" , " delta_diff" }, { " info" , " Performance above team ELO change since the day before." } }
694735 });
695736
696737 int rank = 1 ;
@@ -701,8 +742,10 @@ void MapStats::exportBestTeams(const std::filesystem::path &directory, const Pla
701742 {
702743 json jTeam = json::object ();
703744 jTeam[" rank" ] = rank++;
704- uint32_t elo1 = static_cast <uint32_t >(std::round (_lastTeamELOs[team.teamId ].first ));
705- uint32_t elo2 = static_cast <uint32_t >(std::round (_lastTeamELOs[team.teamId ].second ));
745+ jTeam[" last_game" ] = stringtools::fromDate (team.lastGame );
746+
747+ uint32_t elo1 = static_cast <uint32_t >(std::round (_lastTeamELOs[team.teamId ][team.games - 1 ].first ));
748+ uint32_t elo2 = static_cast <uint32_t >(std::round (_lastTeamELOs[team.teamId ][team.games - 1 ].second ));
706749
707750 std::string player1 = players[team.player1 ()].alias () + " (" + std::to_string (elo1) + " )" ;
708751 std::string player2 = players[team.player2 ()].alias () + " (" + std::to_string (elo2) + " )" ;
@@ -711,9 +754,31 @@ void MapStats::exportBestTeams(const std::filesystem::path &directory, const Pla
711754 jTeam[" games" ] = std::to_string (team.games );
712755 jTeam[" performance" ] = std::to_string (elo1 + elo2 + static_cast <uint32_t >(std::round (team.eloDifference )));
713756 jTeam[" diff" ] = std::to_string (static_cast <uint32_t >(std::round (team.eloDifference )));
757+
758+ int deltaRank = 0 ;
759+ int deltaGames = 0 ;
760+ int deltaDiff = 0 ;
761+ int currentRank = 0 ;
762+ for (const Team &yesterdayTeam : _yesterdaysTeams)
763+ {
764+ currentRank++;
765+ if (yesterdayTeam.teamId == team.teamId )
766+ {
767+ deltaRank = currentRank - (rank - 1 );
768+ deltaGames = team.games - yesterdayTeam.games ;
769+ deltaDiff = team.eloDifference - yesterdayTeam.eloDifference ;
770+ break ;
771+ }
772+ }
773+
774+
775+ jTeam[" delta_rank" ] = deltaRank;
776+ jTeam[" delta_gamesplayed" ] = deltaGames;
777+ jTeam[" delta_diff" ] = deltaDiff;
778+
714779 jTeams.push_back (jTeam);
715780
716- if (rank == 21 )
781+ if (rank == 31 )
717782 break ;
718783 }
719784
@@ -726,6 +791,8 @@ void MapStats::exportBestTeams(const std::filesystem::path &directory, const Pla
726791 stream << std::setw (4 ) << data << std::endl;
727792 stream.close ();
728793 }
794+
795+ Log::verbose () << " Exported best teams." ;
729796}
730797
731798
@@ -746,8 +813,13 @@ void MapStats::exportUpsets(const std::filesystem::path &directory, const Player
746813 }
747814
748815 exportUpsets (directory, _upsetsLast12Month, gamemodes::shortName (_gameMode) + " _upsets_last12month.json" , " Upsets within the last 12 month" , players);
816+ Log::verbose () << " Exported biggest upsets of the last 12 month." ;
817+
749818 exportUpsets (directory, _upsetsLast30Days, gamemodes::shortName (_gameMode) + " _upsets_last30days.json" , " Upsets within the last 30 days" , players);
819+ Log::verbose () << " Exported biggest upsets of the last 30 days." ;
820+
750821 exportUpsets (directory, _upsetsAllTime, gamemodes::shortName (_gameMode) + " _upsets_alltime.json" , " Biggest upsets of all time" , players);
822+ Log::verbose () << " Exported biggest upsets of all time.." ;
751823}
752824
753825/* !
0 commit comments