본문 바로가기

게임, 트위치

2021 LCK Spring 영혼용 통계

lck의 거의 모든 경기를 챙겨보는데, 바람 용이 등장하는 판이 다른 용에 비해 눈에 띌 정도로 많다. 실제로 리그 초반에도 lck 중계진에 의해서 몇 번 언급될 정도로 독특한 통계이다. 그런데 이 영혼용에 대한 통계는 각종 온라인 사이트들에 기록된 매치기록만 봐서는 쉽게 알 수가 없었으므로 직접 2021년 봄 시즌의 용 통계를 작성한다.

누군가는 나와 같은 시도를 했을 것이 분명하므로 깃헙을 먼저 뒤졌더니, 오래 전에 작성된 매치기록들을 긁어오는 스크립트를 발견했다.

이 스크립트를 사용해서 공식매치 기록들을 긁어올 건데, lpl은 링크형식이 다르기도 하고 실제 리그오브레전드 매치기록으로 연결시켜주는 정보들이 없어보였다. 방법을 아는 누군가가 있다면 알려주면 좋겠다.

위의 스크립트에서는 게임데이터 처리하는 부분이 내가 원하던 용 통계가 아니므로 긁어오는 데까지만 쓰자. 주의할 점 하나는 해당 소스코드에 있는 정규표현식이 https와 http 링크가 섞인 것을 고려하지 않아서(gamepedia 같은 사이트에서 실제로 섞여있다) 그냥 아래의 코드로 바꿔주자 (사실 앞부분 그냥 빼도 된다).

var rx = new RegExp(/https?:\/\/matchhistory.na.leagueoflegends.com\/en\/\#match-details\/([^\&"]+)/g),

최종적으로 스크립트를 실행하면 game-uri-cache.txt에 uri들을 저장한다.

다음으로, https://acs.leagueoflegends.com/v1/stats/game/ESPORTSTMNT01/1690549/timeline?gameHash=12e7e73be0bdaa47 이런식으로 긁어온 링크 정보를 acs.leagueoflegends.com/v1/stats/game에 주면 매치 기록에 대한 전체 타임라인 데이터를 준다.

주의해야할 점은 리그오브레전드 로그인한 상태에서 요청을 날려야하는 것 같다. 따라서 리그오브레전드 로그인 한 뒤에 얻어지는 쿠키들인 id_hintid_token을 기록해서 스크립트를 완성하도록한다 (둘 다 필요한지 까지는 모른다). 매치의 가공되지 않은 타임라인 데이터를 보면 events라고 기록된 이벤트들에 ELITE_MONSTER_KILL 항목에 용이 잡힌 사실도 기록한다. 'monsterType'이 용인 경우들만 먼저 가져오고 'monsterSubType'은 'AIR_DRAGON', 'WATER_DRAGON', 'FIRE_DRAGON', 'EARTH_DRAGON' 으로 분류되므로 세 번째 용의 타입을 통계냈다. 하지만 이 논리로 세면 문제가 하나 있다. 사실 영혼용이 무엇인지 결정되는 타이밍은 두 번째 용이 잡히고 나서 세 번째 용이 결정되고, 지형이 바뀌면서이다. 하지만 이 장면을 포착하려면 진짜 비디오 돌리는 노가다를 해야해서 귀찮아서 안했다. 실제 2용만 잡혀도 영혼 용이 뭔지 알게되지만 내 스크립트에서는 3용 이하로 잡힌 판은 'NONE'으로 기록한다. 실제 LCK 2021 Spring 에서 3용까지 잡히지 않은 판이 두 판 존재하는데 이 논리에 의해서 영혼용이 결정되지 않은 판만 비디오 돌려봐서 채워넣어도 되지만 하기싫다. 완성된 스크립트는 아래와 같다.

import requests
import json
from functools import reduce

# https://lol.gamepedia.com/LCK/2021_Season/Spring_Season

# dragonSouls are determined by the 3rd dragon
dragonSoulCount = 3
dragonTypes = ['AIR_DRAGON', 'WATER_DRAGON', 'FIRE_DRAGON', 'EARTH_DRAGON', 'NONE']
cookies = {
    "id_hint": "id_hint_dummy ",
    "id_token": "id_token_dummy"
}
cacheFileName = "game-uri-cache.txt"

def timelineUriCombinator(uri):
    # input uri format: ESPORTSTMNT01/1690520?gameHash=04199edf6922bed2
    serverAddress = "https://acs.leagueoflegends.com/v1/stats/game/"
    return serverAddress + "/timeline?".join(uri.split("?"))

def getTimelineFromServer(timelineUri):
    print("requesting to " + timelineUri)
    content = requests.get(timelineUri, cookies=cookies).content
    return json.loads(content)

def dragonSoul(timeline):
    frames = timeline['frames']
    events = reduce(lambda x,y: x + y, map(lambda x: x['events'], frames))
    dragonKills = list(filter(lambda x: x['monsterType'] == 'DRAGON' if (x['type'] == 'ELITE_MONSTER_KILL') else False, events))
    if (len(dragonKills) >= dragonSoulCount):
        return dragonKills[dragonSoulCount - 1]['monsterSubType']
    else:
        return 'NONE'

def soulStats(dragonSouls):
    return list(map(lambda dType: {dType: dragonSouls.count(dType)}, dragonTypes))    

with open(cacheFileName) as f:
    uris = json.loads(f.read())['uris']
    print(f"Total games: {len(uris)}")
    dragonSouls = list(map(lambda uri: dragonSoul(getTimelineFromServer(timelineUriCombinator(uri))), uris))
    print(soulStats(dragonSouls))

  LCK LEC LCS
바람용의 영혼 74 16 21
대지용의 영혼 39 25 16
화염용의 영혼 46 20 27
바다용의 영혼 54 27 26
영혼 없음 2 2 0
총 게임 수 215 90 90

 

한국리그에 바람용이 많이 나왔던 것은 팩트다

 

이전 네이버 블로그에 있던 글 하나를 옮기면서 테스트용으로 작성했다. 

반응형