简体   繁体   English

如何跟踪球员的排名?

[英]How to keep track of players' rankings?

I have a Player class with a score attribute: 我有一个带有score属性的Player类:

class Player(game_engine.Player):

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

This score increases/decreases as the player succeeds/fails to do objectives. 当玩家成功/未能完成目标时,该分数增加/减少。 Now I need to tell the player his rank out of the total amount of players with something like 现在我需要告诉玩家他的排名超出了玩家的总数

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

First I thought of having a list of all the players, and whenever anything happens to a player: 首先,我想到了所有玩家的列表,以及玩家什么时候发生的事情:

  1. I check if his score increased or decreased 我检查他的分数是增加还是减少
  2. find him in the list 在列表中找到他
  3. move him until his score is in the correct place 移动他,直到他的分数在正确的位置

But this would be extremely slow. 但这将非常缓慢。 There can be hundreds of thousands of players, and a player can reset his own score to 0 which would mean that I'd have to move everyone after him in the stack. 可以有成千上万的玩家,并且玩家可以将他自己的分数重置为0 ,这意味着我必须在堆叠中移动所有人。 Even finding the player would be O(n). 即使找到玩家也是O(n)。

What I'm looking for is a high performance solution. 我正在寻找的是一个高性能的解决方案。 RAM usage isn't quite as important, although common sense should be used. 尽管应该使用常识,但RAM的使用并不那么重要。 How could I improve the system to be a lot faster? 我怎样才能更快地改进系统?

Updated info: I'm storing a player's data into a MySQL database with SQLAlchemy everytime he leaves the gameserver, and I load it everytime he joins the server. 更新信息:我每次离开游戏服务器时都会使用SQLAlchemy将玩家的数据存储到MySQL数据库中,并且每次他加入服务器时都会加载它。 These are handled through 'player_join' and 'player_leave' events: 这些是通过'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()

Also, the player's score is updated upon 'player_kill' event: 此外,玩家的分数会根据'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

Redis sorted sets help with this exact situation (the documentation uses leader boards as the example usage) http://redis.io/topics/data-types-intro#redis-sorted-sets Redis sort sets帮助解决了这个问题(文档使用排行榜作为示例用法) http://redis.io/topics/data-types-intro#redis-sorted-sets

  • The key commands you care about are ZADD (update player rank) and ZRANK (get rank for specific player). 您关心的关键命令是ZADD(更新玩家等级)和ZRANK(获得特定玩家的等级)。 Both operations are O(log(N)) complexity. 两个操作都是O(log(N))复杂度。

Redis can be used as a cache of player ranking. Redis可以用作玩家排名的缓存。 When your application starts, populate redis from the SQL data. 应用程序启动时,从SQL数据中填充redis。 When updating player scores in mysql also update redis. 在mysql中更新玩家分数时也会更新redis。

If you have multiple server processes/threads and they could trigger player score updates concurrently then you should also account for the mysql/redis update race condition, eg: 如果您有多个服务器进程/线程并且它们可以同时触发玩家得分更新,那么您还应该考虑mysql / redis更新竞争条件,例如:

  • only update redis from a DB trigger; 仅从DB触发器更新redis; or 要么
  • serialise player score updates; 序列化球员得分更新; or 要么
  • let data get temporarily out of sync and do another cache update after a delay; 让数据暂时不同步,并在延迟后再次进行缓存更新; or 要么
  • let data get temporarily out of sync and do a full cache rebuild at fixed intervals 让数据暂时不同步,并以固定的时间间隔执行完整的缓存重建

The problem you have is that you want real-time updates against a database, which requires a db query each time. 您遇到的问题是您希望对数据库进行实时更新,每次都需要进行数据库查询。 If you instead maintain a list of scores in memory, and update it at a more reasonable frequency (say once an hour, or even once a minute, if your players are really concerned with their rank), then the players will still experience real-time progress vs a score rank, and they can't really tell if there is a short lag in the updates. 如果您在内存中保留一个分数列表,并以更合理的频率更新(例如每小时一次,甚至每分钟一次,如果您的玩家真的关心他们的等级),那么玩家仍将体验真实的时间进度与分数排名,他们无法确定更新中是否存在短暂滞后。

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, and of course the time to update the cache when you want to. 通过内存中的分数排序列表,您可以立即获得播放器的排名(即时,我的意思是内存中的O(lg n)查找),代价是要缓存的内存,当然还有更新缓存的时间当你想要的时候。 Compared to a db query of 100k records every time someone wants to glance at their rank, this is a much better option. 与每次有人想要浏览其排名的100k记录的db查询相比,这是一个更好的选择。

Elaborating on the sorted list, you must query the db to get it, but you can keep using it for a while. 在排序列表上详细说明,您必须查询数据库以获取它,但您可以继续使用它一段时间。 Maybe you store the last_update, and re-query the db only if this list is "too old". 也许你存储了last_update,并且只有当这个列表“太旧”时才重新查询数据库。 So you update quickly by not trying to update all the time, but rather just enough to feel like real-time. 因此,您不是一直尝试更新而是快速更新,而是让它感觉像是实时的。

In order to find someone's rank nearly instantaneously, you use the bisect module, which supports binary search in a sorted list. 为了几乎立即找到某人的排名,您可以使用bisect模块,该模块支持在排序列表中进行二进制搜索。 The scores are sorted when you get them. 得分时会对分数进行排序。

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)

This says that a 7 score is rank 4, which is correct. 这表示7分是4级,这是正确的。

That kind of information can be pulled using SQLAlchemy's sort_by function. 可以使用SQLAlchemy的sort_by函数来提取这种信息。 If you perform a Query like: 如果您执行以下查询:

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

You will have the list of Players sorted by their score. 您将按照他们的分数排序玩家列表。 Keep in mind that every time you do this you do an I/O with the database which can be rather slow instead of saving the data python variables. 请记住,每次执行此操作时,都要使用数据库执行I / O操作,这可能会相当慢,而不是保存数据python变量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM