Skocz do zawartości

Rekomendowane odpowiedzi

Opis

 

Prosty system rankingowy oparty na ELO. Nadaje się idealnie do mierzenia siły graczy w pojedynkach jeden na jednego (np. arenki 1v1, choć prawdopodobnie na normalnych serwerach też się sprawdzi)

 

https://pl.wikipedia.org/wiki/Ranking_szachowy

https://metinmediamath.wordpress.com/2013/11/27/how-to-calculate-the-elo-rating-including-example/

 

Jak to działa w skrócie: każdy gracz rozpoczyna z 800 elo. Kiedy jeden gracz zabije drugiego punkty są aktualizowane w zależności od siły ofiary i zabójcy.

Możliwe scenariusze:

1. zbliżony ranking (np. 800 - 800). Zabójca dostanie podobną liczbę punktów do tej, która zostanie zabrana ofierze (np. +12, -12)

2. zwycięstwo osoby z wyższym rankingiem (np. 1200 - 800). Spodziewany rezultat z uwagi na większą siłę zabójcy. Niewielka nagroda (np. +4, -4)

3. zwycięstwo osoby z niższym rankingiem (np. 800 - 1200). Bardzo duży bonus dla zwycięzcy za zabicie osoby ze znacznie wyższym rankingiem (np. +20, -20)

 

Rozkład elo po 3 dniach:

image.png

 

Widać wyraźnie zgodność z rozkładem normalnym - większość graczy jest przeciętnych, natomiast na końcach przedziałów są najlepsi i najgorsi gracze, których jest najmniej.

Dla porównania histogram z rozkładem rankingowym na chess.com:

image.png

 

Komendy

sm_elo - menu glowne

 

Konfiguracja

Na chwilę obecną tylko trzy makrodefinicje:

#define STARTING_ELO 800 // poczatkowy ranking kazdego gracza
#define IGNORE_AFTER_DAYS 30 // po ilu dniach ignorowac gracza w SQL - nie bedzie sie wliczal do rankingu ogolnego
#define K 24 // mnoznik punktow. Im wiekszy, tym bardziej bedzie wahać sie ranking

Zapis na MySQL (nazwa konfiguracji: EloSystem)

 

Kod

Spoiler

 

#include <sourcemod>
#include <multicolors>

#define TAG "{orchid}[EloSystem]{default}"

#define STARTING_ELO 800 // poczatkowy ranking kazdego gracza
#define IGNORE_AFTER_DAYS 30 // po ilu dniach ignorowac gracza w SQL - nie bedzie sie wliczal do rankingu ogolnego
#define K 24 // mnoznik punktow. Im wiekszy, tym bardziej bedzie wahać sie ranking

enum struct Player {
    int elo;
    int kills;
    int deaths;

    void Insert(int elo, int kills, int deaths) {
        this.elo = elo;
        this.kills = kills;
        this.deaths = deaths;
    }
}

Player playerStats[MAXPLAYERS + 1];

Database DB;

public void OnPluginStart() {
    Database.Connect(OnSQLConnect, "EloSystem")

    HookEvent("player_death", SmiercGracza);

    RegConsoleCmd("sm_elo", EloMenu);
}

public Action EloMenu(int client, int args) {
    char buffer[512];

    int pastTimestamp = GetTime() - IGNORE_AFTER_DAYS*86400;

    Format(buffer, sizeof(buffer), "SELECT `SID`,`kills`,`deaths` FROM `Players` WHERE `lastPlayed` > %d ORDER BY `elo` DESC", pastTimestamp);

    DB.Query(LoadRankResult, buffer, GetClientUserId(client), DBPrio_Normal);
}

public void LoadRankResult(Database db, DBResultSet results, const char[] error, int clientUserId) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not load player stats! Error: %s", error);
        return;
    }

    int client = GetClientOfUserId(clientUserId);
    if (!client)
        return;

    int n = 0;
    int playersCount = results.RowCount;
    while (results.FetchRow()) {
        ++n;

        if (GetSteamAccountID(client) == results.FetchInt(0))
            break;
    }

    float percentile = float(playersCount - n + 1) / float(playersCount) * 100.0;

    Menu menu = new Menu(RankMenu_Handler);

    char buffer[512];
    Format(buffer, sizeof(buffer), "Punkty ELO: %d\nRanking: %d/%d\nKille: %d | Deady: %d\n\nJesteś lepszy niż %.0f procent graczy\n", playerStats[client].elo, n, playersCount, playerStats[client].kills, playerStats[client].deaths, percentile);

    menu.SetTitle(buffer);
    menu.AddItem("top", "Ranking Top 10");
    menu.Display(client, MENU_TIME_FOREVER);
}

public int RankMenu_Handler(Menu menu, MenuAction action, int client, int position) {
  if (action == MenuAction_Select) {
    char item[32];
    GetMenuItem(menu, position, item, sizeof(item));

    if (StrEqual(item, "top")) ShowTop(client);
  }
  else if (action == MenuAction_End)
    delete menu;
}

void ShowTop(int client) {
    char buffer[512];
    Format(buffer, sizeof(buffer), "SELECT `nick`, `elo`,`kills`,`deaths` FROM `Players` ORDER BY `elo` DESC LIMIT 10");
    DB.Query(LoadTopResult, buffer, GetClientUserId(client), DBPrio_Normal);
}

public void LoadTopResult(Database db, DBResultSet results, const char[] error, int clientUserId) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not load top! Error: %s", error);
        return;
    }

    int client = GetClientOfUserId(clientUserId);
    if (!client)
        return;

    Menu menu = new Menu(ShowTop_Handler);
    menu.SetTitle("Top 10 [elo] (kille/deady)")

    char buffer[512];
    char nick[MAX_NAME_LENGTH];

    while (results.FetchRow()) {
        results.FetchString(0, nick, sizeof(nick));
        Format(buffer, sizeof(buffer), "%s [%d] (%d/%d)", nick, results.FetchInt(1), results.FetchInt(2), results.FetchInt(3));
        menu.AddItem("", buffer);
    }

    menu.Display(client, MENU_TIME_FOREVER);
}

public int ShowTop_Handler(Menu menu, MenuAction action, int client, int position) {
  if (action == MenuAction_Select || action == MenuAction_End)
    delete menu;
}

public Action SmiercGracza(Handle event, char[] name, bool dontbroadcast) {
    int client = GetClientOfUserId(GetEventInt(event, "userid"));
    int killer = GetClientOfUserId(GetEventInt(event, "attacker"));

    if (!IsValidClient(client) || !IsValidClient(killer) || client == killer)
        return Plugin_Continue;

    float transformedEloClient = Pow(10.0, float(playerStats[client].elo)/400.0);
    float transformedEloKiller = Pow(10.0, float(playerStats[killer].elo)/400.0);

    float expectedScoreClient = transformedEloClient / (transformedEloClient + transformedEloKiller);
    float expectedScoreKiller = transformedEloKiller / (transformedEloClient + transformedEloKiller);

    int updatedEloClient = playerStats[client].elo + RoundToCeil(K * (0 - expectedScoreClient));
    int updatedEloKiller = playerStats[killer].elo + RoundToCeil(K * (1 - expectedScoreKiller));

    if (updatedEloClient < 0)
        updatedEloClient = 0;

    if (updatedEloKiller < 0)
        updatedEloKiller = 0;

    CPrintToChat(client, "%s  %N(%d): {darkred} PRZEGRANA.{olive} elo: %d{lightred} (%d){default}. {grey}Przewidywana szansa na wygraną: %.1f%%", TAG, killer, playerStats[killer].elo, updatedEloClient, updatedEloClient - playerStats[client].elo, expectedScoreClient*100.0);
    CPrintToChat(killer, "%s  %N(%d): {green} WYGRANA.{olive} elo: %d{lightgreen} (+%d){default}. {grey}Przewidywana szansa na wygraną: %.1f%%", TAG, client, playerStats[client].elo, updatedEloKiller, updatedEloKiller - playerStats[killer].elo, expectedScoreKiller*100.0);

    playerStats[client].elo = updatedEloClient;
    playerStats[client].deaths += 1;

    playerStats[killer].elo = updatedEloKiller;
    playerStats[killer].kills += 1;

    return Plugin_Continue;
}

// ---*SQL*---

public void OnSQLConnect(Database db, const char[] error, any data) {
    if (db == INVALID_HANDLE) {
        LogError("[Mapy SQL] Database failure: %s", error);
        SetFailState("[Mapy SQL] Databases won't work! Plugin turns off...");
        return;
    }

    DB = db;

    char buffer[512];

    Format(buffer, sizeof(buffer), "CREATE TABLE IF NOT EXISTS `Players`(`SID` INTEGER NOT NULL UNIQUE, `nick` TEXT, `elo` INTEGER NOT NULL, `kills` INTEGER NOT NULL, `deaths` INTEGER NOT NULL, `lastPlayed` INTEGER NOT NULL);");
    DB.Query(CreateResult, buffer, _, DBPrio_High);
}

public void CreateResult(Database db, DBResultSet results, const char[] error, any data) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not create new table! Error: %s", error);
        return;
    }
}

public void OnClientAuthorized(int client, const char[] auth) {
    LoadDataSQL(client);
}

public void OnClientDisconnect(int client) {
    SaveDataSQL(client);
}

void LoadDataSQL(int client) {
    char buffer[512];
    Format(buffer, sizeof(buffer), "SELECT `elo`, `kills`, `deaths` FROM `Players` WHERE `SID`=%d", GetSteamAccountID(client));
    DB.Query(LoadResult, buffer, GetClientUserId(client), DBPrio_High);
}

public void LoadResult(Database db, DBResultSet results, const char[] error, int clientUserId) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not create new table! Error: %s", error);
        return;
    }

    int client = GetClientOfUserId(clientUserId);
    if (!client)
        return;

    char buffer[512];

    if (results.RowCount == 0) {
        playerStats[client].Insert(STARTING_ELO, 0, 0);

        Format(buffer, sizeof(buffer), "INSERT INTO `Players` VALUES(%d, '%N', %d, 0, 0, %d)", GetSteamAccountID(client), client, STARTING_ELO, GetTime());
        DB.Query(InsertResult, buffer, _, DBPrio_High);
    }
    else {
        while (results.FetchRow()) {
            playerStats[client].Insert(results.FetchInt(0), results.FetchInt(1), results.FetchInt(2));
        }
    }
}

public void InsertResult(Database db, DBResultSet results, const char[] error, any data) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not insert new player's data! Error: %s", error);
        return;
    }
}

void SaveDataSQL(int client) {
    char buffer[512];
    Format(buffer, sizeof(buffer), "UPDATE `Players` SET `elo`=%d, `kills`=%d, `deaths`=%d, `lastPlayed`=%d WHERE `SID`=%d", playerStats[client].elo, playerStats[client].kills, playerStats[client].deaths, GetTime(), GetSteamAccountID(client));
    DB.Query(UpdateResult, buffer, _, DBPrio_High);
}

public void UpdateResult(Database db, DBResultSet results, const char[] error, any data) {
    if (db == null || results == null) {
        PrintToServer("error (EloSystem)! %s", error);
        LogError("Could not update player's data! Error: %s", error);
        return;
    }
}

// ---*Helpers*---
public bool IsValidClient(int client) {
  if (client >= 1 && client <= MaxClients && IsClientInGame(client))
    return true;

  return false;
}

 

Sygnatura użytkownika

Użytkowniku! Pamiętaj, że nic tak nie motywuje jak porządna łapka w górę!

Nie mówię tylko o sobie - honoruj każdego, kto na to zasługuje 🙂

YouTube | SteamGitHub | MailboxGO | AchievementsGO | MuteGO

  • Lubię to!
  • Kocham to
Odnośnik do odpowiedzi
Udostępnij na innych stronach

Ostatnio przeglądający ten temat (5 użytkowników)

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Usuń formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić grafiki. Dodaj lub załącz grafiki z adresu URL.

×
×
  • Dodaj nową pozycję...