From 5843442f983bd9cda3a05fe19d9a4c03cefb2d99 Mon Sep 17 00:00:00 2001 From: notify Date: Sat, 13 May 2023 14:45:23 +0800 Subject: [PATCH] Win rate (#149) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 胜率(无UI) --- lua/server/room.lua | 30 ++++++++++++++++++ packages/init.sql | 2 +- server/init.sql | 42 +++++++++++++++++++++++-- src/core/util.cpp | 31 ++++++++++++++----- src/core/util.h | 1 + src/network/router.cpp | 4 +-- src/server/room.cpp | 69 +++++++++++++++++++++++++++++++++++++++--- src/server/room.h | 2 ++ src/server/server.cpp | 8 ++--- src/swig/server.i | 2 ++ 10 files changed, 168 insertions(+), 23 deletions(-) diff --git a/lua/server/room.lua b/lua/server/room.lua index cd021851..2f8d9cfc 100644 --- a/lua/server/room.lua +++ b/lua/server/room.lua @@ -2482,6 +2482,18 @@ function Room:useSkill(player, skill, effect_cb) end end +---@param room Room +local function shouldUpdateWinRate(room) + if room.settings.gameMode == "heg_mode" then return false end + if room.settings.gameMode == "aaa_role_mode" and #room.players < 5 then + return false + end + for _, p in ipairs(room.players) do + if p.id < 0 then return false end + end + return true +end + --- 结束一局游戏。 ---@param winner string @ 获胜的身份,空字符串表示平局 function Room:gameOver(winner) @@ -2494,6 +2506,24 @@ function Room:gameOver(winner) end self:doBroadcastNotify("GameOver", winner) + if shouldUpdateWinRate(self) then + for _, p in ipairs(self.players) do + local id = p.id + local general = p.general + local mode = self.settings.gameMode + + if p.id > 0 then + if table.contains(winner:split("+"), p.role) then + self.room:updateWinRate(id, general, mode, 1) + elseif winner == "" then + self.room:updateWinRate(id, general, mode, 3) + else + self.room:updateWinRate(id, general, mode, 2) + end + end + end + end + self.room:gameOver() coroutine.yield("__handleRequest") end diff --git a/packages/init.sql b/packages/init.sql index 5416095a..653d32ce 100644 --- a/packages/init.sql +++ b/packages/init.sql @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -CREATE TABLE packages ( +CREATE TABLE IF NOT EXISTS packages ( name VARCHAR(128), url VARCHAR(255), hash CHAR(40), diff --git a/server/init.sql b/server/init.sql index 500d659f..b201290d 100644 --- a/server/init.sql +++ b/server/init.sql @@ -1,6 +1,8 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ +-- SPDX-License-Identifier: GPL-3.0-or-later -CREATE TABLE userinfo ( +-- 用户基本信息 + +CREATE TABLE IF NOT EXISTS userinfo ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255), password CHAR(64), @@ -10,6 +12,40 @@ CREATE TABLE userinfo ( banned BOOLEAN ); -CREATE TABLE banip ( +CREATE TABLE IF NOT EXISTS banip ( ip VARCHAR(64) ); + +-- 胜率相关 + +CREATE TABLE IF NOT EXISTS winRate ( + id INTEGER, + general VARCHAR(20), + mode VARCHAR(16), + win INTEGER, + lose INTEGER, + draw INTEGER, + PRIMARY KEY (id, general, mode) +); + +CREATE VIEW IF NOT EXISTS playerWinRate AS + SELECT winRate.id, name, mode, + SUM(win) AS 'win', + SUM(lose) AS 'lose', + SUM(draw) AS 'draw', + SUM(win + lose + draw) AS 'total', + ROUND(SUM(win) * 1.0 / (SUM(win + lose + draw) * 1.0) * 100, 2) + AS 'winRate' + FROM winRate, userinfo + WHERE winRate.id = userinfo.id + GROUP BY winRate.id, mode; + +CREATE VIEW IF NOT EXISTS generalWinRate AS + SELECT general, mode, + SUM(win) AS 'win', + SUM(lose) AS 'lose', + SUM(draw) AS 'draw', + SUM(win + lose + draw) AS 'total', + ROUND(SUM(win) * 1.0 / (SUM(win + lose + draw) * 1.0) * 100, 2) + AS 'winRate' + FROM winRate GROUP BY general, mode; diff --git a/src/core/util.cpp b/src/core/util.cpp index 0258d607..66266ba0 100644 --- a/src/core/util.cpp +++ b/src/core/util.cpp @@ -63,14 +63,15 @@ void Dumpstack(lua_State *L) { sqlite3 *OpenDatabase(const QString &filename, const QString &initSql) { sqlite3 *ret; int rc; - if (!QFile::exists(filename)) { - QFile file(initSql); - if (!file.open(QIODevice::ReadOnly)) { - qFatal("cannot open %s. Quit now.", initSql.toUtf8().data()); - qApp->exit(1); - } - QTextStream in(&file); + QFile file(initSql); + if (!file.open(QIODevice::ReadOnly)) { + qFatal("cannot open %s. Quit now.", initSql.toUtf8().data()); + qApp->exit(1); + } + QTextStream in(&file); + + if (!QFile::exists(filename)) { char *err_msg; sqlite3_open(filename.toLatin1().data(), &ret); rc = sqlite3_exec(ret, in.readAll().toLatin1().data(), nullptr, nullptr, @@ -89,10 +90,26 @@ sqlite3 *OpenDatabase(const QString &filename, const QString &initSql) { sqlite3_close(ret); qApp->exit(1); } + + char *err_msg; + rc = sqlite3_exec(ret, in.readAll().toLatin1().data(), nullptr, nullptr, + &err_msg); + + if (rc != SQLITE_OK) { + qCritical() << "sqlite error:" << err_msg; + sqlite3_free(err_msg); + sqlite3_close(ret); + qApp->exit(1); + } } return ret; } +bool CheckSqlString(const QString &str) { + static const QRegularExpression exp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)"); + return (!exp.match(str).hasMatch() && !str.isEmpty()); +} + // callback for handling SELECT expression static int callback(void *jsonDoc, int argc, char **argv, char **cols) { QJsonObject obj; diff --git a/src/core/util.h b/src/core/util.h index 1f74a9a0..5773b328 100644 --- a/src/core/util.h +++ b/src/core/util.h @@ -9,6 +9,7 @@ lua_State *CreateLuaState(); bool DoLuaScript(lua_State *L, const char *script); sqlite3 *OpenDatabase(const QString &filename = "./server/users.db", const QString &initSql = "./server/init.sql"); +bool CheckSqlString(const QString &str); QJsonArray SelectFromDatabase(sqlite3 *db, const QString &sql); // For Lua QString SelectFromDb(sqlite3 *db, const QString &sql); diff --git a/src/network/router.cpp b/src/network/router.cpp index c9fe4c00..df19720c 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -155,8 +155,8 @@ void Router::handlePacket(const QByteArray &rawPacket) { const QString &jsonData) { auto arr = String2Json(jsonData).array(); auto avatar = arr[0].toString(); - static QRegularExpression nameExp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)"); - if (!nameExp.match(avatar).hasMatch()) { + + if (CheckSqlString(avatar)) { auto sql = QString("UPDATE userinfo SET avatar='%1' WHERE id=%2;") .arg(avatar) .arg(sender->getId()); diff --git a/src/server/room.cpp b/src/server/room.cpp index 2ab6a81d..6b7281d8 100644 --- a/src/server/room.cpp +++ b/src/server/room.cpp @@ -302,9 +302,68 @@ void Room::chat(ServerPlayer *sender, const QString &jsonData) { doBroadcastNotify(observers, "Chat", json); } - qInfo("[Chat] %s: %s", sender->getScreenName().toUtf8().constData(), - doc["msg"].toString().toUtf8().constData()); + doc["msg"].toString().toUtf8().constData()); +} + +void Room::updateWinRate(int id, const QString &general, const QString &mode, + int game_result) { + static const QString findWinRate = + QString("SELECT win, lose, draw " + "FROM winRate WHERE id = %1 and general = '%2' and mode = '%3';"); + + static const QString updateRate = + QString("UPDATE winRate " + "SET win = %4, lose = %5, draw = %6 " + "WHERE id = %1 and general = '%2' and mode = '%3';"); + + static const QString insertRate = + QString("INSERT INTO winRate " + "(id, general, mode, win, lose, draw) " + "VALUES (%1, '%2', '%3', %4, %5, %6);"); + + if (!CheckSqlString(general)) + return; + if (!CheckSqlString(mode)) + return; + + int win = 0; + int lose = 0; + int draw = 0; + + switch (game_result) { + case 1: + win++; + break; + case 2: + lose++; + break; + case 3: + draw++; + break; + default: + break; + } + + QJsonArray result = + SelectFromDatabase(server->getDatabase(), + findWinRate.arg(QString::number(id), general, mode)); + + if (result.isEmpty()) { + ExecSQL(server->getDatabase(), + insertRate.arg(QString::number(id), general, mode, + QString::number(win), QString::number(lose), + QString::number(draw))); + } else { + auto obj = result[0].toObject(); + win += obj["win"].toString().toInt(); + lose += obj["lose"].toString().toInt(); + draw += obj["draw"].toString().toInt(); + ExecSQL(server->getDatabase(), + updateRate.arg(QString::number(id), general, mode, + QString::number(win), QString::number(lose), + QString::number(draw))); + } } void Room::gameOver() { @@ -324,7 +383,8 @@ void Room::gameOver() { } QString Room::fetchRequest() { - if (!gameStarted) return ""; + if (!gameStarted) + return ""; request_queue_mutex.lock(); QString ret = ""; if (!request_queue.isEmpty()) { @@ -335,7 +395,8 @@ QString Room::fetchRequest() { } void Room::pushRequest(const QString &req) { - if (!gameStarted) return; + if (!gameStarted) + return; request_queue_mutex.lock(); request_queue.enqueue(req); request_queue_mutex.unlock(); diff --git a/src/server/room.h b/src/server/room.h index f65d5a99..3a28269e 100644 --- a/src/server/room.h +++ b/src/server/room.h @@ -52,6 +52,8 @@ class Room : public QThread { const QString &command, const QString &jsonData); void chat(ServerPlayer *sender, const QString &jsonData); + void updateWinRate(int id, const QString &general, const QString &mode, + int result); void gameOver(); void initLua(); diff --git a/src/server/server.cpp b/src/server/server.cpp index 2e463487..498414b0 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -220,10 +220,6 @@ void Server::processRequest(const QByteArray &msg) { void Server::handleNameAndPassword(ClientSocket *client, const QString &name, const QString &password, const QString &md5_str) { - // First check the name and password - // Matches a string that does not contain special characters - static QRegularExpression nameExp("['\";#]+|(--)|(/\\*)|(\\*/)|(--\\+)"); - auto encryted_pw = QByteArray::fromBase64(password.toLatin1()); unsigned char buf[4096] = {0}; RSA_private_decrypt(RSA_size(rsa), (const unsigned char *)encryted_pw.data(), @@ -263,7 +259,7 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, QJsonArray result; QJsonObject obj; - if (!nameExp.match(name).hasMatch() && !name.isEmpty()) { + if (CheckSqlString(name)) { // Then we check the database, QString sql_find = QString("SELECT * FROM userinfo \ WHERE name='%1';") @@ -311,7 +307,7 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, player->alive = true; client->disconnect(this); broadcast("ServerMessage", - tr("%1 backed").arg(player->getScreenName())); + tr("%1 backed").arg(player->getScreenName())); room->pushRequest(QString("%1,reconnect").arg(id)); return; } else { diff --git a/src/swig/server.i b/src/swig/server.i index 711a0bf7..00ef7659 100644 --- a/src/swig/server.i +++ b/src/swig/server.i @@ -51,6 +51,8 @@ public: const QString &jsonData ); + void updateWinRate(int id, const QString &general, const QString &mode, + int result); void gameOver(); LuaFunction startGame;