Skip to content

Commit 6be210a

Browse files
committed
Write ratings to database.
1 parent ef8d969 commit 6be210a

File tree

6 files changed

+154
-14
lines changed

6 files changed

+154
-14
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
cmake_minimum_required(VERSION 3.14)
3-
project(CnCNet_ELO VERSION 1.0.2 LANGUAGES CXX)
3+
project(CnCNet_ELO VERSION 1.0.3 LANGUAGES CXX)
44

55
set(CMAKE_CXX_STANDARD 23)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)

databaseconnection.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,3 +842,107 @@ uint32_t DatabaseConnection::getBaseAccount(const std::set<uint32_t>& userIds, c
842842
Log::fatal() << "No base account found among user ids " << userIds << ".";
843843
throw std::runtime_error("No base account. Cannot continue;");
844844
}
845+
846+
/*!
847+
*/
848+
void DatabaseConnection::writePlayerRatings(
849+
gamemodes::GameMode gameMode,
850+
const Players &players,
851+
std::map<uint32_t, uint32_t> activeRanks,
852+
std::map<uint32_t, uint32_t> allTimeRanks)
853+
{
854+
try
855+
{
856+
// Check if expected columns exist.
857+
std::set<std::string> requiredColumns = {
858+
"user_id", "ladder_id", "rating", "elo_rank",
859+
"alltime_rank", "rated_games", "active", "created_at", "updated_at"
860+
};
861+
862+
std::unique_ptr<sql::PreparedStatement> checkStmt(
863+
_connection->prepareStatement(R"SQL(
864+
SELECT COLUMN_NAME
865+
FROM information_schema.columns
866+
WHERE table_schema = DATABASE() AND table_name = 'user_ratings'
867+
)SQL")
868+
);
869+
870+
std::unique_ptr<sql::ResultSet> columnResult(checkStmt->executeQuery());
871+
std::set<std::string> actualColumns;
872+
873+
while (columnResult->next())
874+
{
875+
actualColumns.insert(columnResult->getString("COLUMN_NAME"));
876+
}
877+
878+
for (const std::string &col : requiredColumns)
879+
{
880+
if (!actualColumns.contains(col))
881+
{
882+
Log::warning() << "Unable to write player ratings due to missing column '" << col << "' in 'user_ratings'.";
883+
return;
884+
}
885+
}
886+
887+
// Clear table.
888+
std::unique_ptr<sql::PreparedStatement> truncateStmt(
889+
_connection->prepareStatement("TRUNCATE TABLE user_ratings")
890+
);
891+
truncateStmt->execute();
892+
Log::info() << "Table 'user_ratings' truncated.";
893+
894+
// Get ladder id.
895+
uint32_t ladderId = 0;
896+
std::unique_ptr<sql::PreparedStatement> ladderStmt(
897+
_connection->prepareStatement("SELECT id FROM ladders WHERE abbreviation = ? LIMIT 1")
898+
);
899+
ladderStmt->setString(1, _ladder);
900+
std::unique_ptr<sql::ResultSet> ladderResult(ladderStmt->executeQuery());
901+
902+
if (ladderResult->next())
903+
{
904+
ladderId = ladderResult->getUInt("id");
905+
}
906+
else
907+
{
908+
Log::fatal() << "Ladder '" << _ladder << "' not found in table 'ladders'.";
909+
return;
910+
}
911+
912+
// Prepare statement to save user data.
913+
std::unique_ptr<sql::PreparedStatement> insertStmt(
914+
_connection->prepareStatement(R"SQL(
915+
INSERT INTO user_ratings
916+
(user_id, ladder_id, rating, elo_rank, alltime_rank, rated_games, active, created_at, updated_at)
917+
VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
918+
)SQL")
919+
);
920+
921+
for (uint32_t userId : players.userIds())
922+
{
923+
const Player &player = players[userId];
924+
uint32_t gameCount = player.gameCount();
925+
double elo = (gameMode == gamemodes::Blitz2v2) ? player.elo(factions::Combined) : player.maxRating(!player.isActive());
926+
if (elo < 0.0)
927+
{
928+
// Try to get any rating, even if the player was never active.
929+
elo = player.elo(factions::Combined);
930+
}
931+
insertStmt->setUInt(1, userId);
932+
insertStmt->setUInt(2, ladderId);
933+
insertStmt->setInt(3, static_cast<int>(std::round(elo)));
934+
insertStmt->setUInt(4, activeRanks.contains(userId) ? activeRanks[userId] : 0);
935+
insertStmt->setUInt(5, allTimeRanks.contains(userId) ? allTimeRanks[userId] : 0);
936+
insertStmt->setUInt(6, gameCount);
937+
insertStmt->setBoolean(7, player.isActive());
938+
939+
insertStmt->execute();
940+
}
941+
942+
Log::info() << "Player ratings written to 'user_ratings'.";
943+
}
944+
catch (sql::SQLException &e)
945+
{
946+
Log::fatal() << "Error while writing user ratings: " << e.what();
947+
}
948+
}

databaseconnection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ class DatabaseConnection
7272
//! one of the invalid user ids.
7373
uint32_t getBaseAccount(const std::set<uint32_t>& userIds, const std::set<uint32_t> &invalidUserIds);
7474

75+
//! Write data to table user_ratings;
76+
void writePlayerRatings(gamemodes::GameMode gameMode, const Players &players, std::map<uint32_t, uint32_t> activeRanks, std::map<uint32_t, uint32_t> allTimeRanks);
77+
7578
private:
7679
//! Is the connection ready?
7780
bool _ready;

main.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -339,14 +339,14 @@ int main(int argc, char* argv[])
339339

340340
Log::info(lastProcessedGame != nullptr) << "Last game processed: " << *lastProcessedGame;
341341

342-
if (!options.dryRun)
343-
{
344-
players.exportActivePlayers(options.outputDirectory, options.gameMode);
345-
players.exportBestOfAllTime(options.outputDirectory, options.gameMode);
346-
players.exportMostDaysActive(options.outputDirectory, options.gameMode);
347-
players.exportAlphabeticalOrder(options.outputDirectory, options.gameMode);
348-
players.exportNewPlayers(options.outputDirectory, options.gameMode);
349-
}
342+
if (options.dryRun)
343+
return 0;
344+
345+
std::map<uint32_t, uint32_t> activeRanks = players.exportActivePlayers(options.outputDirectory, options.gameMode);
346+
std::map<uint32_t, uint32_t> allTimeRanks = players.exportBestOfAllTime(options.outputDirectory, options.gameMode);
347+
players.exportMostDaysActive(options.outputDirectory, options.gameMode);
348+
players.exportAlphabeticalOrder(options.outputDirectory, options.gameMode);
349+
players.exportNewPlayers(options.outputDirectory, options.gameMode);
350350

351351
for (auto it = ignoredMaps.begin(); it != ignoredMaps.end(); ++it)
352352
{
@@ -374,6 +374,12 @@ int main(int argc, char* argv[])
374374
stats.exportMapsPlayed(options.outputDirectory);
375375
players.exportPlayerDetails(options.outputDirectory, {}, games, options.ladderAbbreviation);
376376
}
377+
Log::info() << "Exported map stats.";
378+
379+
// Player ratings.
380+
Log::info() << "Updating table `user_ratings`.";
381+
connection.writePlayerRatings(options.gameMode, players, activeRanks, allTimeRanks);
377382

383+
Log::info() << "All done.";
378384
return 0;
379385
}

players.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ bool Players::exists(const std::string &playerName)
4040
return _nickToUserId.contains(playerName);
4141
}
4242

43+
/*!
44+
*/
45+
std::vector<uint32_t> Players::userIds() const
46+
{
47+
std::vector<uint32_t> userIds;
48+
userIds.reserve(_players.size());
49+
50+
for (const auto& [userId, player] : _players)
51+
{
52+
userIds.push_back(userId);
53+
}
54+
55+
return userIds;
56+
}
57+
4358
/*!
4459
*/
4560
Player& Players::operator[](uint32_t index)
@@ -208,10 +223,11 @@ uint32_t Players::activePlayerCount() const
208223

209224
/*!
210225
*/
211-
void Players::exportActivePlayers(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const
226+
std::map<uint32_t, uint32_t> Players::exportActivePlayers(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const
212227
{
213228
Log::info() << "Exporting list of active players.";
214229

230+
std::map<uint32_t, uint32_t> rankByUserId;
215231
using json = nlohmann::json;
216232
json data = json::object();
217233

@@ -353,24 +369,28 @@ void Players::exportActivePlayers(const std::filesystem::path &directory, gamemo
353369
}
354370

355371
players.push_back(jsonPlayer);
372+
rankByUserId[player->userId()] = i + 1;
356373
}
357374

358375
data["data"] = players;
359376

360377
std::ofstream stream(directory / (gamemodes::shortName(gameMode) + "_active_players.json"));
361378
stream << std::setw(4) << data << std::endl;
362379
stream.close();
380+
381+
return rankByUserId;
363382
}
364383

365384
/*!
366385
*/
367-
void Players::exportBestOfAllTime(
386+
std::map<uint32_t, uint32_t> Players::exportBestOfAllTime(
368387
const std::filesystem::path &directory,
369388
gamemodes::GameMode gameMode) const
370389
{
371390
Log::info() << "Exporting best players of all time.";
372391

373392
std::vector<const Player*> filteredAndSortedPlayers;
393+
std::map<uint32_t, uint32_t> rankByUserId;
374394

375395
for (auto it = _players.cbegin(); it != _players.cend(); ++it)
376396
{
@@ -434,12 +454,15 @@ void Players::exportBestOfAllTime(
434454
jsonPlayer["status"] = player->isActive() ? "ACTIVE" : "INACTIVE";
435455

436456
players.push_back(jsonPlayer);
457+
rankByUserId[player->userId()] = i + 1;
437458
}
438459
data["data"] = players;
439460

440461
std::ofstream stream(directory / (gamemodes::shortName(gameMode) + "_bestofalltime.json"));
441462
stream << std::setw(4) << data << std::endl;
442463
stream.close();
464+
465+
return rankByUserId;
443466
}
444467

445468
/*!

players.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class Players
3434
//! Returns 0 if no player is found.
3535
uint32_t userId(const std::string &nick, const std::string &ladderName) const;
3636

37+
//! Get all user ids.
38+
std::vector<uint32_t> userIds() const;
39+
3740
//! Get the index of a player based on his alias.
3841
//! Returns 0 if no player is found.
3942
uint32_t userIdFromAlias(const std::string &alias) const;
@@ -68,10 +71,11 @@ class Players
6871
// Exporting methods for actual rankings.
6972
public:
7073
//! Export list of active players, sorted by rating. The is the actual ELO list.
71-
void exportActivePlayers(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const;
74+
//! Returns rank for each user id.
75+
std::map<uint32_t, uint32_t> exportActivePlayers(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const;
7276

73-
//! Make list of best players of all time.
74-
void exportBestOfAllTime(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const;
77+
//! Make list of best players of all time. Returns rank for each user id.
78+
std::map<uint32_t, uint32_t> exportBestOfAllTime(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const;
7579

7680
//! Most active players aka most loyal players.
7781
void exportMostDaysActive(const std::filesystem::path &directory, gamemodes::GameMode gameMode) const;

0 commit comments

Comments
 (0)