如何跟踪玩家的排名?

Posted

技术标签:

【中文标题】如何跟踪玩家的排名?【英文标题】:How to keep track of players' rankings? 【发布时间】:2016-12-21 19:53:29 【问题描述】:

我有一个带有score 属性的Player 类:

class Player(game_engine.Player):

    def __init__(self, id):
        super().__init__(id)
        self.score = 0

随着玩家成功/失败完成目标,该分数会增加/减少。现在我需要告诉玩家他在玩家总数中的排名,比如

print('Your rank is 0 out of 1')

首先我想有一个所有玩家的列表,以及每当玩家发生任何事情时:

    我检查他的分数是增加还是减少 在列表中找到他 移动他,直到他的分数在正确的位置

但这会非常慢。可能有数十万玩家,玩家可以将自己的分数重置为0,这意味着我必须将堆栈中的每个人都移到他之后。即使找到玩家也是 O(n)。

我正在寻找的是一种高性能的解决方案。尽管应该使用常识,但 RAM 的使用并不那么重要。我怎样才能将系统改进得更快?

更新信息:每次玩家离开游戏服务器时,我都会使用 SQLAlchemy 将玩家的数据存储到 mysql 数据库中,并在每次他加入服务器时加载它。这些是通过'player_join''player_leave' 事件处理的:

@Event('player_join')
def load_player(id):
    """Load player into the global players dict."""
    session = Session()
    query = session.query(Player).filter_by(id=id)
    players[id] = query.one_or_none() or Player(id=id)

@Event('player_leave')
def save_player(id):
    """Save player into the database."""
    session = Session()
    session.add(players[id])
    session.commit()

此外,玩家的分数会根据'player_kill' 事件更新:

@Event('player_kill')
def update_score(id, target_id):
    """Update players' scores upon a kill."""
    players[id].score += 2
    players[target_id].score -= 2

【问题讨论】:

您使用哪个数据库? @r-m-n 我正在使用 MySQL 在某些数据库中可以使用 DENSE_RANK 窗口函数来完成,但 MySQL 不支持此函数。你可以试试这样dukesoftware00.blogspot.ru/2012/11/… 我已经使用 redis 排序集处理了这个问题 - 例如,有一些简单的命令可以获取特定键的等级,并且 redis 将比您尝试构建自己的任何东西都快得多,而无需大量投资。 你可以有一个每小时的任务来制作一个排序的分数列表,这将允许你快速确定当前的排名,比如缓存列表上的 bisect.bisect_left。分数列表不需要完整——也许保留每 1000 个分数。您可以去 db 获取实际***玩家的准确排名。 【参考方案1】:

Redis 排序集有助于解决这种确切情况(文档使用排行榜作为示例用法)http://redis.io/topics/data-types-intro#redis-sorted-sets

您关心的关键命令是 ZADD(更新玩家排名)和 ZRANK(获取特定玩家的排名)。这两种操作都是 O(log(N)) 复杂度。

Redis 可以作为玩家排名的缓存。当您的应用程序启动时,从 SQL 数据填充 redis。在mysql中更新玩家分数的时候也会更新redis。

如果您有多个服务器进程/线程并且它们可以同时触发玩家分数更新,那么您还应该考虑 mysql/redis 更新竞争条件,例如:

仅从数据库触发器更新redis;或 序列化玩家分数更新;或 让数据暂时不同步,并在延迟后进行另一次缓存更新;或 让数据暂时不同步,并以固定的时间间隔进行完整的缓存重建

【讨论】:

【参考方案2】:

您遇到的问题是您想要对数据库进行实时更新,这每次都需要一个数据库查询。如果你在内存中维护一个分数列表,并以更合理的频率更新它(比如一小时一次,甚至一分钟一次,如果你的玩家真的关心他们的排名),那么玩家仍然会体验到真实的——时间进度与分数排名,他们无法真正判断更新是否存在短暂的滞后。

通过内存中得分的排序列表,您可以立即获得玩家的排名(我的意思是立即在内存中查找 O(lg n)),代价是缓存的内存,当然还有时间需要时更新缓存。与每次有人想要查看他们的排名时对 100k 条记录进行数据库查询相比,这是一个更好的选择。

详述排序列表,你必须查询db才能得到它,但你可以继续使用它一段时间。也许您存储 last_update,并且仅当此列表“太旧”时才重新查询数据库。因此,您无需一直尝试更新即可快速更新,而只是感觉像实时一样。

为了几乎立即找到某人的排名,您使用 bisect 模块,该模块支持在排序列表中进行二进制搜索。获得分数时会对其进行排序。

from bisect import bisect_left

# suppose scores are 1 through 10
scores = range(1, 11)

# get the insertion index for score 7
# subtract it from len(scores) because bisect expects ascending sort
# but you want a descending rank
print len(scores) - bisect_left(scores, 7)

这表示 7 分是排名 4,这是正确的。

【讨论】:

"With a sorted list of scores in memory, you can instantly get the player's rank (where by instantly, I mean O(lg n) lookup in memory) at the cost of the memory to cache" 这几乎是我主要帖子中的问题(不是数据库问题,我在帖子中几乎没有提到数据库)。如何在保持排序的同时快速更新和查找列表? 我更新了该部分的示例代码。如果缓存不是 100% 最新的数据库信息是一个破坏者,请告诉我,所以我可以删除它。我不确定这一点,这就是为什么我之前只是将其作为评论发布。【参考方案3】:

可以使用 SQLAlchemy 的 sort_by 函数提取此类信息。如果您执行如下查询:

leaderboard = session.query(Player).order_by(Player.score).all()

您将获得按得分排序的玩家列表。请记住,每次执行此操作时,您都会对数据库执行 I/O,这可能会相当慢,而不是保存数据 python 变量。

【讨论】:

以上是关于如何跟踪玩家的排名?的主要内容,如果未能解决你的问题,请参考以下文章

Unity2D 使用协程来跟踪玩家何时转动?

PHP 搜索引擎排名跟踪器 - RankTrackr访问片段

Android 实时谷歌地图位置跟踪

跟踪算法

(转)导弹跟踪算法

FPS 和 UPS 有啥区别?我应该在游戏循环中跟踪 UPS 吗?