有人的地方就有对比,游戏中自然也少不了排行榜。

当前项目设计目标是,每个服务器玩家数量为百万左右。每个玩家都有战力、经验等属性,战力最大值在50万以内。

现在期望能有战力排行榜,有以下几点需求:

全部角色参与排行,能实时知道某个角色的排名

排行榜显示前100名玩家详情

排名规则是战力越高排名越前,战力相同则比较经验,经验再相同则比较创建时间。

排行榜算法并不少见,这篇文章介绍的就不错。根据上述需求分析,最适合采用文中的算法3,即树形分区设计,具体算法文中有详细介绍。

采用该算法,时间复杂度在O(log(n)),在百万规模下空间消耗也就几十M。但有两个问题待解决:

战力相同时如何确定具体排名

如何获得TOP N

针对问题1,假定游戏设计的战力相对均匀(尽管高战力显然更分散),那么战力相同的玩家数量会在一个较小规模内。依然以战力构建排行树,相同战力为同一个节点。节点可以存在一个有序列表,以经验、创建时间排序。这里有个小技巧,以玩家ID等效于创建时间,就直接记录了相应玩家,同时也保证了唯一性。这在增加删除(排名改变时)尤为有用。

针对问题2,排行树算法决定了最终战力节点都是叶子节点,同时在叶子节点层,战力总是从左向右递增的。在树构建过程中,可以分别使用一个前向和后向节点,将所有叶子节点连成一个双向链表。这样就可以做到既能得到前N名,也可以得到后N名,时间复杂度都是O(N)。

下面show the code,完整代码请参看文末。

public class LeaderboardTree{

class LeaderboardNode{

public int lowerKey = 0;

public int upperKey = 0;

public int number = 0;

public ArrayList extraList = new ArrayList();

public LeaderboardNode left = null;

public LeaderboardNode right = null;

public LeaderboardNode prev = null;

public LeaderboardNode next = null;

}

LeaderboardNode root = null;

LeaderboardNode head = null;

LeaderboardNode tail = null;

public void setup(int lowerKey, int upperKey){

root = setupNode(root, lowerKey, upperKey);

}

public void insert(int score, Extra extra){

insertIntoNode(root, score, extra);

}

public void remove(int score, Extra extra){

removeFromNode(root, score, extra);

}

public void change(int oldKey, int newKey, Extra extra){

remove(oldKey, extra);

insert(newKey, extra);

}

public int getRanking(int score, Extra extra){

return getRankingOfNode(root, score, extra) + 1;

}

public ArrayList getTopN(int n){

ArrayList dataList = new ArrayList();

int count = 0;

LeaderboardNode cursor = tail;

while (cursor != null) {

for (Extra extra : cursor.extraList) {

LeaderboardData data = new LeaderboardData();

data.ranking = ++count;

data.key = cursor.lowerKey;

data.extra = extra;

dataList.add(data);

if (count >= n) {

return dataList;

}

}

cursor = cursor.prev;

}

return dataList;

}

private LeaderboardNode setupNode(LeaderboardNode node, int lowerKey, int upperKey){

if (lowerKey > upperKey) {

return null;

}

node = new LeaderboardNode();

node.lowerKey = lowerKey;

node.upperKey = upperKey;

node.number = 0;

node.extraList.clear();

if (isLeafNode(node)) {

if (head == null) {

head = node;

}

if (tail != null) {

tail.next = node;

node.prev = tail;

}

tail = node;

return node;

}

if (upperKey > lowerKey) {

final int middleKey = getMiddleKey(lowerKey, upperKey);

node.left = setupNode(node.left, lowerKey, middleKey);

node.right = setupNode(node.right, middleKey + 1, upperKey);

}

return node;

}

private void insertIntoNode(LeaderboardNode node, int score, Extra extra){

if (node == null) {

return;

}

if (!isInsideNode(node, score)) {

return;

}

++node.number;

if (isLeafNode(node)) {

node.extraList.add(extra);

node.extraList.sort((Extra left, Extra right) -> left.compareTo(right));

return;

}

final int middleKey = getMiddleKey(node.lowerKey, node.upperKey);

if (score <= middleKey) {

insertIntoNode(node.left, score, extra);

} else {

insertIntoNode(node.right, score, extra);

}

}

private void removeFromNode(LeaderboardNode node, int score, Extra extra){

if (node == null) {

return;

}

if (!isInsideNode(node, score)) {

return;

}

--node.number;

if (isLeafNode(node)) {

node.extraList.remove(extra);

node.extraList.sort((Extra left, Extra right) -> left.compareTo(right));

return;

}

final int middleKey = getMiddleKey(node.lowerKey, node.upperKey);

if (score <= middleKey) {

removeFromNode(node.left, score, extra);

} else {

removeFromNode(node.right, score, extra);

}

}

private int getRankingOfNode(LeaderboardNode node, int score, Extra extra){

int ranking = 0;

if (node == null) {

return ranking;

}

if (score < node.lowerKey) {

ranking += node.number;

return ranking;

}

if (score > node.upperKey) {

ranking += 0;

return ranking;

}

if (isLeafNode(node)) {

ranking += Math.max(node.extraList.indexOf(extra), 0);

return ranking;

}

final int middleKey = getMiddleKey(node.lowerKey, node.upperKey);

if (score <= middleKey) {

ranking += node.right != null ? node.right.number : 0;

ranking += getRankingOfNode(node.left, score, extra);

} else {

ranking += getRankingOfNode(node.right, score, extra);

}

return ranking;

}

private int getMiddleKey(int lowerKey, int upperKey){

final int middleKey = lowerKey + ((upperKey - lowerKey) >> 1);

return middleKey;

}

private boolean isInsideNode(LeaderboardNode node, int score){

return score >= node.lowerKey && score <= node.upperKey;

}

private boolean isLeafNode(LeaderboardNode node){

return node.lowerKey == node.upperKey;

}

}

针对我们的需求,key就是战力,extra包含玩家经验和ID。

采用这种做法,需要在服务器启动时重新构建排行树,先确定排行战力区间,然后依次插入每个玩家战力等数据。运行期间,玩家战力等改变时,先删除旧的排行,再插入新的排行。

该算法在处理千万数据时依然有效,但再大规模性能会不足,占用空间也可观。如果战力分布不均,同战力玩家过多,性能也会大幅退化,可将ArrayList替换为更高效的数据结构,或变通需求。

java 实时排行榜_Java游戏服务器-百万规模实时排行榜实现相关推荐

  1. NodeJS 开发多人实时对战游戏服务器 (一)

    从一个游戏情怀说起 接触的第一款多人对战游戏是帝国时代,依稀记得那时候上学每周最期待的就是冲到电脑课撸一把罗马复兴,高中开始接触<魔兽争霸3>,一款真正让我迷恋十多年的游戏,怀念那时候的& ...

  2. 服务器用java还是c_为什么游戏服务器还是用cpp而不是java?

    用java开发游戏服务器的的确比较少. 之前用c++是因为之前的服务器比较贵,要求一台机器要尽量支撑足够多的玩家.C++因为其高性能,自然是不二之选.java刚推出的时候,主要的解决方案都是偏企业级的 ...

  3. java 监听端口_java游戏服务器检查报告(经验分享)

    java在centos系统运行,经过大量用户使用后,我们使用那些监听手段来判断服务器是否达到我们理想要求呢,判断服务器使用可以继续使用呢? 我们以进程PID9496(监听端口:9624)为对象说明,启 ...

  4. java websocket 实现_JAVA (Tomcat服务器)使用WebSocket实现服务端与HTML前端通信

    在一个项目中要使用WebSocket技术来实现服务器与浏览器实时通信交互,在网上也找了许多资料.为了防止以后忘记具体的使用过程,下面我把自己的使用过程和方法记录下来方便自己以后使用. 项目背景: 基于 ...

  5. java华容道代码_java游戏之华容道

    [实例简介] java游戏华容道,具体见博客http://blog.csdn.net/simon_world [实例截图] [核心代码] MedleyPicture └── MedleyPicture ...

  6. java linux 根目录_java 获取服务器根目录

    [一个基于java的web服务器使用这两个重要的类:java.net.Socket和java.net.ServerSocket,并通过HTTP消息进行通信.在实现Web服务器之前有必要简要说明一下超文 ...

  7. Java在游戏服务器开发中的应用

     Java在游戏服务器开发中的应用 width="22" height="16" src="http://hits.sinajs.cn/A1/we ...

  8. 游戏服务器排行榜的设计

    排行榜是游戏服务器中不可缺少的一部分,几乎所有的游戏都有排行榜.游戏排行榜根据排行榜需要上榜的人数有不同的设计,但是普遍来说一般都是显示前100名或者50名,排行榜排100,1k,1w人数,超过1w名 ...

  9. 游戏服务器Mina框架开发

    游戏服务器Mina框架开发 作者:老九-技术大黍 社交:知乎 公众号:老九学堂(新人有惊喜) 特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权 前言 如果要使用Java语言来开发游戏 ...

最新文章

  1. 赠书 | 新手指南——如何通过HuggingFace Transformer整合表格数据
  2. webapi文档描述-swagger
  3. ubuntu命令行登录
  4. LQR轨迹跟踪算法Python/Matlab算法实现2
  5. AngularJs+bootstrap搭载前台框架——基础页面
  6. netflix-hystrix-简例
  7. 一个帮助你处理延迟,重复,循环操作的jQuery插件 - timing
  8. 手机如何登录企业邮箱,公司电子邮箱登录页面
  9. STM32f401驱动【语音模块】
  10. 202101汇率换算
  11. 尝遍裸辞各种苦,再也不敢任性裸辞了!
  12. 华为OD机试 - 勾股数元组
  13. 正版口腔管理软件免费使用,口腔诊所业绩提升就靠它
  14. 高级查询(mysql)
  15. 《kafka面试100例 -6》如果在/admin/delete_topics/中手动写入一个节点会不会正常删除Topic
  16. 关于幂级数求和是否弃用首项的理解
  17. 字符串左旋和右旋的常见方法
  18. 学习数据分析、数据挖掘、大数据ETL工程师到什么程度可以找工作?
  19. 东方木教你如何用WINDOWS自带的压缩功能去压缩软件
  20. 飞凌嵌入式RZ/G2L处理器核心板及开发板上手评测

热门文章

  1. Chrome 谷歌浏览器,如何安装合适的浏览器驱动?
  2. 【目标检测】基于yolov5的钢筋检测和计数(附代码和数据集)
  3. phpcms付费阅读功能支付宝支付
  4. 用Ant批量运行TestNG测试用例,并配合Reportng实现报告美化
  5. HBASE分布式安装
  6. 浪潮信息释放数据之力 提供安全可靠、绿色高效的智慧医疗数据基础设施
  7. 包裹算子对求解包裹相位差分时的作用
  8. 阅读软件测试论文:Evaluation of a prioritization algorithm for test suite generation
  9. 分享发现一个优秀WP插件 2022最新WordPress登录注册会员功能一体的插件LOGINUSER-CH
  10. 案例:EVE和ENSP对接LLDP协议