游戏实时排行榜C++实现

需求

在网络游戏中,服务器通常会对全服玩家的某些数据进行排名,如等级、战力、活动获取的积分等等。而对于排行榜,需做到以下要求求:

  • 排行榜需要做到实时,如玩家战力提升,立马看到新的排名
  • 只关心前N名玩家的排名,通常 N <= 1000
  • 排名分前后顺序,不可并列,如同一战力,先达到的排名靠前

实现

源码地址

https://github.com/zifengzwz/sundrycpp/tree/main/rank

目录结构

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

sorted_set.hpp

#ifndef SORTED_SET
#define SORTED_SET

#include <memory>
#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)
      : top_(right.top_),
        map_(std::move(right.map_)),
        set_(std::move(right.set_)) {}

  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 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
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值