LeagueRank: A game skill evaluatioin application

Introduction

Nowadays, League of Legends (LOL) is probably the most popular online MOBA game in the world, with over 67 million players every month. LOL is a real-time competing game, which requires skills and strategy. Thus, knowing about your ability and skills is important for people who want to reach a higher level, especially for the professional players. Currently, Riot Games (the producer of LOL) has only provided rank score to estimate the players integrated level. For specific champions' level, there's no official tools to precisely evaluate that. This project is aimed to reveal the players most frequently played champions' level and give players a guidance of how to horn his/her skills.

For background knowledge, there are some terminology that need to be clarified:

  • Summoner: the player
  • Champion: the character summoner uses during the game
  • Tier: the level a summoner is in currently. There are seven tiers currently, from low to high: bronze, silver, gold, platinum, diamond, master and challenger.
  • Division: the sub-level a summoner is in. For example, bronze to diamond have 5 divisions, represented using Roman numbers (I-V), whereas master and challenger have only one division.

Installing the library

Before getting started, we need to prepare some usful libraries. The main package we use to crawl the LOL data is called riotwatcher, which is a Python library built up with the API provided by Riot. To install the library, one common way using pip:

pip install riotwatcher

Although the library is robust and convenience, there are still some functions it does not implement such as getting the most frequently played champion, getting summoner list by tier, etc. To implement those functions, we are going to adopt corresponding Riot APIs, using urllib2 to request from the game server. Installing urllib2 via pip:

pip install urllib2

After you run all the installs, make sure the following commands work for you


In [ ]:
import csv
import json
import os
import ujson
import urllib2

from riotwatcher import RiotWatcher

To use the Riot api, one more important thing to do is to get your own API key. API key can be obtained from here. Note that normal developr API key has a narrow request limit, whereas production API key for commercial use has a looser requirement of request limit. For now, we are just gonna use the normal API key for demonstration.

After getting your own API key, put it in the config dictionary below:


In [ ]:
config = {
    'key': 'API_key',
}

Project Architecture

Data Crawling

The architecture for data crawler is shown as follow:

The process of crawling data could be simplified as follows: 1) Get summoners list from LOL server; 2) For each summoner, get his/her top 3 frequently played champions; 3) Fetch each champion's game stats for 2016 season (latest entire season); 4) Put the fetched data into corresponding csv file for storage.

1. Fetch summoner list

First of all, we need to fetch the summoner information down. Riot has provided with the api to get summoner information by leagues. League is the partial data in a tier. For example, in gold tier, we have summoners in gold I, gold II, gold III, gold IV and gold V. The summoners in gold tier are divided into several leagues, each league contains summoners in all ranges.

The __init__ method of RiotCrawler define the tiers, get_player_by_tier fetches the summoner list in different leagues according to the provided summoner ids.


In [ ]:
class RiotCrawler:
    def __init__(self, key):
        self.key = key
        self.w = RiotWatcher(key)
        self.tiers = {
            'bronze': [],
            'silver': [],
            'gold': [],
            'platinum': [],
            'diamond': [],
            'challenger': [],
            'master': [],
        }

    # def get_player_list(self):
    #     recent_games = self.w.get_recent_games(self.player_id)
    #     player_list = set()
    #     for game in recent_games['games']:
    #         # only pick up ranked games
    #         if 'RANKED' in game['subType']:
    #             fellow_players = game['fellowPlayers']
    #             for fellow_player in fellow_players:
    #                 fellow_player_id = fellow_player['summonerId']
    #                 if fellow_player_id not in player_list:
    #                     player_list.add(fellow_player_id)
    #     return list(player_list)

    def get_player_by_tier(self, summoner_id):
        request_url = 'https://na.api.pvp.net/api/lol/na/v2.5/league/by-summoner/{}?api_key={}'.format(
            summoner_id, self.key
        )
        response = urllib2.urlopen(request_url)
        tier_info = ujson.loads(response.read())

        tier = tier_info[str(summoner_id)][0]['tier'].lower()
        entries = tier_info[str(summoner_id)][0]['entries']

        level = self.tiers[tier]
        for entry in entries:
            level.append(entry['playerOrTeamId'])

#         for l in level:
#             print 'summoner id: {}'.format(str(l))

get_tier will return a divisioin dictionary, whose keys are the tier name, and values are the summoner id list in each tier. The results are printed in a human-readable format, categorized by tier.


In [ ]:
def get_tier():
    # challenger: 77759242
    # platinum: 53381
    # gold: 70359816
    # silver: 65213225
    # bronze: 22309680
    # master: 22551130
    # diamond: 34570626
    player_ids = [70359816, 77759242, 53381, 65213225, 22309680, 22551130, 34570626]
    riot_crawler = RiotCrawler(config['key'])
    for player_id in player_ids:
        print 'start crawling id: {}'.format(player_id)
        riot_crawler.get_player_by_tier(player_id)
    return riot_crawler.tiers

tiers = get_tier()
for tier, rank_dict in tiers.iteritems():
    print '--- {} ---'.format(tier)
    for summoner in rank_dict:
        print 'summoner id: {}'.format(summoner)
    print '--- end of {} ---'.format(tier)

2. Fetch most frequently played champions

Since we already had a dictionary of all user ids mapping to all categories of ranks, we can now use those user ids to get the stats data of their most frequently used champions. We will use the raw RESTful APIs of Riot with python here. And here are all the libraries needed in this process.


In [ ]:
import csv
import json
import os
import urllib2

Then we can move on and fetch the data we need. Riot gives us the API to get all champions that a user had used during the season. And the response will be in JSON format. After parsing the JSON response, what we need to do is to get the most frequently used champions which can represent a player's level. So we sort the champions list by the number of games that the player used this champioin (totalSessionsPlayed) in descending order. Notice that the first element in the list will always be the champion with id 0, which represents the stats data of all champions that the player used in the season. So we need to skip that.

After we filter out the top frequently used champions of a user, we need to save the stats data with the player's tier as the training label into a csv file. In this project, each champion has a corresponding csv file which records all the stats data of this hero with the tier of the player as the training data. Since there are hundreds of champions in League of Legend, we will have hundreds of csv files for training and each file uses the id of champions as the file name. If the file is already created, we will append more stats of this champion to the csv file.


In [ ]:
class TopChampion:

    FIELD_NAMES = ['totalSessionsPlayed', 'totalSessionsLost', 'totalSessionsWon',
                   'totalChampionKills', 'totalDamageDealt', 'totalDamageTaken',
                   'mostChampionKillsPerSession', 'totalMinionKills', 'totalDoubleKills',
                   'totalTripleKills', 'totalQuadraKills', 'totalPentaKills',
                   'totalUnrealKills', 'totalDeathsPerSession', 'totalGoldEarned',
                   'mostSpellsCast', 'totalTurretsKilled', 'totalPhysicalDamageDealt',
                   'totalMagicDamageDealt', 'totalFirstBlood', 'totalAssists',
                   'maxChampionsKilled', 'maxNumDeaths', 'label']

    def __init__(self, key, player_id, label, n):
        self.label = label
        self.player_id = player_id
        self.key = key
        self.n = n
        self.top_champions = []
        pass

    def get_top_champions(self):
        self.top_champions[:] = []
        data = urllib2.urlopen(
            'https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/' +
            self.player_id +
            '/ranked?season=SEASON2016&api_key=' +
            self.key
        ).read()
        json_data = json.loads(data)
        champions = json_data['champions']
        champion_stats = []
        for champion in champions:
            champion_stat = champion['stats']
            champion_stat['id'] = champion['id']
            champion_stat['label'] = self.label
            champion_stats.append(champion_stat)
            pass
        self.top_champions = sorted(champion_stats,
                                    key=lambda x: x['totalSessionsPlayed'],
                                    reverse=True)[1:self.n + 1]
        return self.top_champions
        pass

    def save_top_champions(self):
        for champion in self.top_champions:
            file_name = '../data/{}.csv'.format(champion['id'])
            if os.path.isfile(file_name):
                with open(file_name, 'a') as csvfile:
                    writer = csv.DictWriter(csvfile, fieldnames=self.FIELD_NAMES)
                    writer.writerow(
                        {
                            'totalSessionsPlayed': champion['totalSessionsPlayed'],
                            'totalSessionsLost': champion['totalSessionsLost'],
                            'totalSessionsWon': champion['totalSessionsWon'],
                            'totalChampionKills': champion['totalChampionKills'],
                            'totalDamageDealt': champion['totalDamageDealt'],
                            'totalDamageTaken': champion['totalDamageTaken'],
                            'mostChampionKillsPerSession': champion['mostChampionKillsPerSession'],
                            'totalMinionKills': champion['totalMinionKills'],
                            'totalDoubleKills': champion['totalDoubleKills'],
                            'totalTripleKills': champion['totalTripleKills'],
                            'totalQuadraKills': champion['totalQuadraKills'],
                            'totalPentaKills': champion['totalPentaKills'],
                            'totalUnrealKills': champion['totalUnrealKills'],
                            'totalDeathsPerSession': champion['totalDeathsPerSession'],
                            'totalGoldEarned': champion['totalGoldEarned'],
                            'mostSpellsCast': champion['mostSpellsCast'],
                            'totalTurretsKilled': champion['totalTurretsKilled'],
                            'totalPhysicalDamageDealt': champion['totalPhysicalDamageDealt'],
                            'totalMagicDamageDealt': champion['totalMagicDamageDealt'],
                            'totalFirstBlood': champion['totalFirstBlood'],
                            'totalAssists': champion['totalAssists'],
                            'maxChampionsKilled': champion['maxChampionsKilled'],
                            'maxNumDeaths': champion['maxNumDeaths'],
                            'label': champion['label']
                        }
                    )
                    pass
                pass
            else:
                with open(file_name, 'w') as csvfile:
                    writer = csv.DictWriter(csvfile, fieldnames=self.FIELD_NAMES)
                    writer.writeheader()
                    writer.writerow(
                        {
                            'totalSessionsPlayed': champion['totalSessionsPlayed'],
                            'totalSessionsLost': champion['totalSessionsLost'],
                            'totalSessionsWon': champion['totalSessionsWon'],
                            'totalChampionKills': champion['totalChampionKills'],
                            'totalDamageDealt': champion['totalDamageDealt'],
                            'totalDamageTaken': champion['totalDamageTaken'],
                            'mostChampionKillsPerSession': champion['mostChampionKillsPerSession'],
                            'totalMinionKills': champion['totalMinionKills'],
                            'totalDoubleKills': champion['totalDoubleKills'],
                            'totalTripleKills': champion['totalTripleKills'],
                            'totalQuadraKills': champion['totalQuadraKills'],
                            'totalPentaKills': champion['totalPentaKills'],
                            'totalUnrealKills': champion['totalUnrealKills'],
                            'totalDeathsPerSession': champion['totalDeathsPerSession'],
                            'totalGoldEarned': champion['totalGoldEarned'],
                            'mostSpellsCast': champion['mostSpellsCast'],
                            'totalTurretsKilled': champion['totalTurretsKilled'],
                            'totalPhysicalDamageDealt': champion['totalPhysicalDamageDealt'],
                            'totalMagicDamageDealt': champion['totalMagicDamageDealt'],
                            'totalFirstBlood': champion['totalFirstBlood'],
                            'totalAssists': champion['totalAssists'],
                            'maxChampionsKilled': champion['maxChampionsKilled'],
                            'maxNumDeaths': champion['maxNumDeaths'],
                            'label': champion['label']
                        }
                    )
                    pass
                pass
            pass
        pass
    pass

With the above class, now we can start crawling the stats data of all champions saving them to csv files by the following code. Notice that this process is pretty slow since we added the sleep methods in our code. Riot APIs have a limitation on the API calls rate. You cannot send more than 500 requests per 10 minutes. So everytime we send a request here, we sleep for 1 second to prevent error responses.


In [ ]:
def main():
    import time
    tiers = get_tier()
    for tier, rank_dict in tiers.iteritems():
        print 'starting tier: {}'.format(tier)
        for summoner_id in rank_dict:
            print 'tier: {}, summoner id: {}'.format(tier, summoner_id)
            top_champion = TopChampion(config['key'], summoner_id, tier, 3)
            top_champion.get_top_champions()
            top_champion.save_top_champions()
            time.sleep(1)
        print 'end tier: {}'.format(tier)