Search code examples
pythonalgorithmpython-3.xrankingrank

How to keep track of players' rankings?


I have a Player class with a score attribute:

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. Even finding the player would be 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. 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. These are handled through 'player_join' and 'player_leave' events:

@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:

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

Solution

  • 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

    • The key commands you care about are ZADD (update player rank) and ZRANK (get rank for specific player). Both operations are O(log(N)) complexity.

    Redis can be used as a cache of player ranking. When your application starts, populate redis from the SQL data. When updating player scores in mysql also update 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:

    • only update redis from a DB trigger; 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