1. 程式人生 > 其它 >C++實現遊戲中的實時排行榜

C++實現遊戲中的實時排行榜

技術標籤:C/C++c++遊戲開發後端

需求

在網路遊戲中,伺服器通常會對全服玩家的某些資料進行排名,如等級、戰力、活動獲取的積分等等。而對於排行榜,需做到以下要求求:

  • 排行榜需要做到實時,如玩家戰力提升,立馬看到新的排名
  • 只關心前N名玩家的排名,通常 N <= 1000
  • 排名分前後順序,不可並列,如同一戰力,先達到的排名靠前

實現

目錄結構如下:

.
├── CMakeLists.txt
├── main.cpp
└── sorted_set.hpp

sorted_set.hpp

#ifndef SORTED_SET
#define SORTED_SET

#include <set>
#include <unordered_map> template <class Key, class T> class sorted_set { public: using key_type = Key; using value_type = T; using size_type = std::size_t; // default: care of top 1000 static constexpr size_type default_top = 1000; struct key_value_type { key_type first; value_type second;
key_value_type(const key_type& k, const value_type& v) : first(k), second(v) {} }; using key_value_ptr = std::shared_ptr<key_value_type>; struct key_value_less { constexpr bool operator()(const key_value_ptr& left, const key_value_ptr&
right) const { if (left->second == right->second) { return left->first < right->first; } return left->second < right->second; } }; sorted_set() : top_(default_top) {} explicit sorted_set(size_type top) : top_(top == 0 ? default_top : top) {} sorted_set(const sorted_set&) = delete; sorted_set& operator=(const sorted_set&) = delete; ~sorted_set() = default; sorted_set(sorted_set&& right) noexcept { top_ = right.top_; map_ = std::move(right.map_); set_ = std::move(right.set_); return *this; } sorted_set& operator=(sorted_set&& right) noexcept { top_ = right.top_; map_ = std::move(right.map_); set_ = std::move(right.set_); return *this; } // ranged-for: large->small auto begin() const { return set_.rbegin(); } auto end() const { return set_.rend(); } auto find(key_type k) { return map_.find(k); } auto map_end() { return map_.end(); } auto set_end() { return set_.end(); } void insert(const key_type& k, const value_type& v) { auto add = [&] { key_value_ptr ptr = std::make_shared<key_value_type>(k, v); map_.emplace(k, ptr); set_.insert(ptr); }; auto it = map_.find(k); if (it == map_.end()) { if (set_.size() < top_) { add(); } else if (set_.size() == top_) { auto val = v; if (val < (*set_.begin())->second) return; map_.erase((*set_.begin())->first); set_.erase(set_.begin()); add(); } } else { set_.erase(it->second); map_.erase(it); add(); } } void erase(const key_type& k) { auto it = map_.find(k); if (it == map_.end()) return; set_.erase(it->second); map_.erase(it); } void clear() { set_.clear(); map_.clear(); } size_type size() { return set_.size(); } size_type rank(const key_type& k) { auto it = map_.find(k); if (it == map_.end() || set_.empty()) return 0; size_type rank = 0; for (auto n = set_.rbegin(); n != set_.rend(); ++n) { ++rank; if ((*n)->first == k) return rank; } return 0; } private: size_type top_; std::unordered_map<key_type, key_value_ptr> map_; std::set<key_value_ptr, key_value_less> set_; }; #endif
  • 這裡使用標準庫的setunordered_map簡單地實現sorted_set(有序集合)
  • 參與排名的userid與排名資料為一個整體,作為set的元素和unordered_map的值,userid為unordered_map的鍵
  • 利用set的紅黑樹結構進行自排序,unordered_map雜湊特性進行快速索引
  • 預設排名為前1000(default_top)
  • 通常排名在單執行緒中進行,無需考慮多執行緒安全,禁用拷貝構造和賦值操作
  • 添加了beginend方法,支援從大到小的範圍for迴圈

main.cpp

#include <chrono>
#include <cstdint>
#include <iostream>
#include <random>

#include "sorted_set.hpp"

struct rank_item {
  int score{0};
  int64_t time{0};

  bool operator<(const rank_item& r) {
    if (score == r.score) {
      return time > r.time;
    }
    return score < r.score;
  }

  bool operator==(const rank_item& r) {
    return score == r.score && time == r.time;
  }
};

// [left, right]
int rand_num(int left, int right) {
  static thread_local std::default_random_engine gen(std::random_device{}());
  std::uniform_int_distribution<decltype(right)> dis(left, right);
  return dis(gen);
}

int64_t get_time_milli() {
  auto now = std::chrono::system_clock::now().time_since_epoch();
  return std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
}

int main(int argc, char* argv[]) {
  sorted_set<int, rank_item> rank_info;
  for (int i = 1; i <= 10; ++i) {
    rank_info.insert(i, rank_item{rand_num(50, 100), get_time_milli()});
  }
  int rank = 1;
  for (auto& n : rank_info) {
    std::cout << "rank: " << rank << ", userid: " << n->first
              << ", score: " << n->second.score << std::endl;
    ++rank;
  }
  return 0;
}
  • 對於排名資料rank_item,需過載<和==運算子
  • 隨機生成50-100之間的數值作為得分,對userid為1-10的玩家進行排名

CMakeLists.txt

cmake_minimum_required(VERSION 3.19)
project(rank LANGUAGES CXX)
SET(CMAKE_CXX_STANDARD 17)
add_executable(rank main.cpp sorted_set.hpp)
  • 由於使用了一些新特性,需要cmake新增對C++17標準的支援

編譯執行

# mkdir build
# cd build
# cmake ..
# cmake --build .
# ./rank
rank: 1, userid: 4, score: 100
rank: 2, userid: 8, score: 97
rank: 3, userid: 5, score: 94
rank: 4, userid: 9, score: 91
rank: 5, userid: 2, score: 89
rank: 6, userid: 3, score: 75
rank: 7, userid: 6, score: 70
rank: 8, userid: 7, score: 68
rank: 9, userid: 10, score: 64
rank: 10, userid: 1, score: 55