游戏中的排行榜Lua设计(简单实现,线段树,跳表)

Leader Board Design In Lua

Introduction

在多人在线游戏中,排行榜是很重要的一个功能。多年游戏经验告诉我,排行榜不仅是对自身游戏角色实力的一种评判,还是一种让用户加大投入时间,甚至充值的驱动力。想一想,如果你离排行榜第一名只差一点点,这不爆肝一晚冲榜首

排行榜很重要,但排行榜却不是那么容易设计的。每个用户的得分都在实时变化,并且一般还得提供不同维度的排名,当用户群体一多,数据更新的操作就多了。如何保持高效的数据更新,便显得尤为重要

Simple LeadBoard With Advance

抽象而言,排行榜的最简形态无非就是每个玩家两个字段,用于区分不同玩家的Id字段以及用于排序的Score字段

  • 根据Id获取玩家得分
  • 根据得分进行排序
  • 将结果输出到排行榜
  • 当玩家得分变化时更新得分并重新排序
  • 新玩家进入时则增加一条记录

以上思路基本可以完成排行榜的各项需求,而用LuaTable实现起来也是相当简单


function LeaderBoard:ctor()
    self.rank = {}
end

function LeaderBoard:addNewPlayer(entid, name, score)
    self.rank[#self.rank + 1] = {["entid"] = entid ["name"] = name, ["score"] = score}
    table.sort(self.rank)
end


function LeaderBoard:updateLeaderboard(entid, newScore)
    for i = 1, #self.rank do
        if self.rank[i]["entid"] = entid then
            self.rank[i]["score"] = newScore
        end
    end
    table.sort(self.rank)

end


function LeaderBoard:queryPlayerRank(entid)
    local score = getPlayerScore(entid)
    for i = 1, #self.rank do
        if self.rank[i]["score"] = score then
            return i
        end
    end
    local name = getPlayerName(entid)
    self:addNewPlayer(entid, name, score)
    return -1
end

上述实现的思路是当每次数据变动时都将Rank进行排序以保持Rank按得分有序,在查询时只需找到对应的得分在第几个即可

但仔细思考发现,要想获得自己的排名,根本没必要保持Rank有序,只需要遍历Rank求出得分比自己高的玩家个数即可,因此,可以就此优化添加和更新接口的实现:

function LeaderBoard:ctor()
    self.rank = {}
end

function LeaderBoard:addNewPlayer(entid, name, score)
    self.rank[entid] = {["name"] = name, ["score"] = score}
end


function LeaderBoard:updateLeaderboard(entid, name, oldScore, newScore)
    self.rank[entid]["score"] = newScore

end


function LeaderBoard:queryPlayerRank(entid)
    local rank = 0
    local myScore = self.rank[entid]["score"]
    for k, v in pairs(self.rank) do
        if v["score"] > myScore then
            rank = rank + 1
        end
    end
    return rank + 1
end

比对两者,可以发现优化后的算法查询排名复杂度不变O(N),但添加和更新都变成了O(1)操作,大大加快了执行效率

Segment Tree

虽然优化后的排名查询效率已经达到O(N),但当玩家数量庞大时每次可能有十万级的查询请求,O(N)的复杂度显然不能满足要求

注意到,一般玩家的得分在一段时间内总是在一定范围内的,不可能有一天增长100倍的情况,于是我们可以把得分划分区间,借助线段树优化查询效率

  • 构建一棵从1到最大值的线段树,最大值可以预估未来几个月玩家得分的上限,比如10万分
  • 线段树的每个节点由Start(区间左值), End(区间右值), Count(得分处于此区间的玩家人数)组成,且每个节点的左孩子节点的区间为当前区间的左半边,右孩子为当前区间的右半边,叶子节点则左值与右值相等,整棵树相当于把原始区间不断二分
  • 当增加一个新得分时,即插入一个节点,沿根节点不断选择得分所处区间的节点直至叶子节点,将这条路径上的每个节点的Count加1
  • 删除同理,即当Count大于0时将Count减一,其余与插入一样
  • 更新操作则为先删除后插入
  • 可以看出每个节点的Count代表着处于此区间的玩家人数,因此每次选择子节点若选择左孩子子节点,则代表当前有着右孩子节点Count的人数分数比你高,根据这条性质,查询排名时从根节点向下遍历到叶子节点,每次要往左走时则加上右孩子节点的Count,求和后即得所有分数比当前分数高的人数,即得排名
local sgmentTree = {}

function sgmentTree:node(min, max)
    return {Start = min, End = max}
end

function sgmentTree:new(min, max)
    if 
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值