C++實現遊戲中的實時排行榜
阿新 • • 發佈:2020-12-28
需求
在網路遊戲中,伺服器通常會對全服玩家的某些資料進行排名,如等級、戰力、活動獲取的積分等等。而對於排行榜,需做到以下要求求:
- 排行榜需要做到實時,如玩家戰力提升,立馬看到新的排名
- 只關心前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
- 這裡使用標準庫的set和unordered_map簡單地實現sorted_set(有序集合)
- 參與排名的userid與排名資料為一個整體,作為set的元素和unordered_map的值,userid為unordered_map的鍵
- 利用set的紅黑樹結構進行自排序,unordered_map雜湊特性進行快速索引
- 預設排名為前1000(default_top)
- 通常排名在單執行緒中進行,無需考慮多執行緒安全,禁用拷貝構造和賦值操作
- 添加了begin和end方法,支援從大到小的範圍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