刀剣乱舞累積経験値チェックコードを書いた

何度めかの刀剣乱舞熱が来ていて、最近もnoteで日記を書けば息をするように刀剣乱舞の話をしている気がする。

さて、散々愚痴みたいなことばっかり書いてきたわけだが、その流れで最近「カンストしたキャラクター(刀剣男士)の累積経験値をまめにチェックしたい」というニーズが高まっている。私の中で。
理由は簡単で、大好きな、気高くて眩しい私の推しである山姥切長義が、いったい毎日どれだけ経験値を稼げているのかをチェックすることでモチベーションを高めたいからだ。

もちろんゲーム上ではカンストしてしまうと「レベルアップに必要な残り経験値」も出ないので、表示上サチってしまっている。
そこでまず第一弾として、ローカルのブラウザー側とサーバ側でのやり取りのデータを見ればどこかに書いてあるだろうと思い至り、調べてみた。

簡単に述べれば、ブラウザーからHARファイルを引っこ抜くだけである。(参考リンク:IBM様のサポートページ

HAR("Http ARchive") ファイルは、Web ブラウザのサイトとのやり取りを記録するための JSON 形式のアーカイブ ファイルです。通常、Web ブラウザ内の Cognos コンポーネントに関するパフォーマンス、コンポーネントエラー、ダッシュボード、およびその他様々なブラウザの問題のトラブルシューティングに役立ちます。
https://www.ibm.com/mysupport/s/?language=ja

ダウンロードしたHARファイルを適当なテキストエディタで開き、各刀剣男士のステータスなどの情報を「sword_id」なる識別IDを使って探せばよいという算段。ただ、これだと当然ながら面倒な点がいくつかある。

  • 探すのが面倒くさい(いちいち⌘Fするのは面倒)

  • 増分は自分で計算しないといけない

    • 前回のデータからも手動で抜き出して記録し、自分で引き算

  • 上記の流れを複数の刀剣男士で行うのはマジで面倒くさい

とにかく面倒くさい。と、いうことで、第二弾としてせめて以下の二点を改善しようと思った。

  • 全刀剣男士の現在のステータスを一括で確認できる

  • 前回の記録からの経験値の増加分も一括で確認できる

本当はローカルで動くアプリをTkinterなどで作ってHARファイルをドラッグ&ドロップとかで一瞬で動かせるようにしたいのだが、分析はquick&dirtyがキモだ。そういうプラスアルファは第三弾に回すことにする。

※なお自分で調べて解析までやってから、こんな神ツールがあることを知った。そりゃあるよね。

実装


環境

正直、Google Colabでも良いのだが、HARファイルをアップロードしたくない気がするという曖昧な事情によりローカルで実行。大したスペックは要らないはず。

  • PC

    • macOS Big Sur バージョン11.5.2 

    • Intel Core i7, メモリ8 GB (べつにこんなに要らない)

  • 言語

    • python(バージョンは3.8。筆者のPC、更新していないからです。でも別になんでも動く)

      • サクッとやったのでjupyter notebook上で実行。スクリプト化はしていない。

コード

# -*- coding: utf8 -*-
import pandas as pd
import sys
import json
import datetime
from datetime import timedelta

# 刀帳番号と刀剣男士名のマッピング用の辞書
dict_katana = {
'1':'',
'2':'',
'3':'三日月宗近',
'4':'三日月宗近極',
'5':'小狐丸',
'6':'小狐丸極',
'7':'石切丸',
'8':'石切丸極',
'9':'岩融',
'10':'岩融極',
'11':'今剣',
'12':'今剣極',
'13':'大典太光世',
'14':'大典太光世極',
'15':'ソハヤノツルキ',
'16':'ソハヤノツルキ極',
'17':'数珠丸恒次',
'18':'数珠丸恒次極',
'19':'にっかり青江',
'20':'にっかり青江極',
'21':'鬼丸国綱',
'22':'鬼丸国綱極',
'23':'鳴狐',
'24':'鳴狐極',
'25':'一期一振',
'26':'一期一振極',
'27':'鯰尾藤四郎',
'28':'鯰尾藤四郎極',
'29':'骨喰藤四郎',
'30':'骨喰藤四郎極',
'31':'平野藤四郎',
'32':'平野藤四郎極',
'33':'厚藤四郎',
'34':'厚藤四郎極',
'35':'後藤藤四郎',
'36':'後藤藤四郎極',
'37':'信濃藤四郎',
'38':'信濃藤四郎極',
'39':'前田藤四郎',
'40':'前田藤四郎極',
'41':'秋田藤四郎',
'42':'秋田藤四郎極',
'43':'博多藤四郎',
'44':'博多藤四郎極',
'45':'乱藤四郎',
'46':'乱藤四郎極',
'47':'五虎退',
'48':'五虎退極',
'49':'薬研藤四郎',
'50':'薬研藤四郎極',
'51':'包丁藤四郎',
'52':'包丁藤四郎極',
'53':'大包平',
'54':'大包平極',
'55':'鶯丸',
'56':'鶯丸極',
'57':'明石国行',
'58':'明石国行極',
'59':'蛍丸',
'60':'蛍丸極',
'61':'愛染国俊',
'62':'愛染国俊極',
'63':'千子村正',
'64':'千子村正極',
'65':'蜻蛉切',
'66':'蜻蛉切極',
'67':'物吉貞宗',
'68':'物吉貞宗極',
'69':'太鼓鐘貞宗',
'70':'太鼓鐘貞宗極',
'71':'亀甲貞宗',
'72':'亀甲貞宗極',
'73':'燭台切光忠',
'74':'燭台切光忠極',
'75':'大般若長光',
'76':'大般若長光極',
'77':'小竜景光',
'78':'小竜景光極',
'79':'江雪左文字',
'80':'江雪左文字極',
'81':'宗三左文字',
'82':'宗三左文字極',
'83':'小夜左文字',
'84':'小夜左文字極',
'85':'加州清光',
'86':'加州清光極',
'87':'大和守安定',
'88':'大和守安定極',
'89':'歌仙兼定',
'90':'歌仙兼定極',
'91':'和泉守兼定',
'92':'和泉守兼定極',
'93':'陸奥守吉行',
'94':'陸奥守吉行極',
'95':'山姥切国広',
'96':'山姥切国広極',
'97':'山伏国広',
'98':'山伏国広極',
'99':'堀川国広',
'100':'堀川国広極',
'101':'蜂須賀虎徹',
'102':'蜂須賀虎徹極',
'103':'浦島虎徹',
'104':'浦島虎徹極',
'105':'長曽祢虎徹',
'106':'長曽祢虎徹極',
'107':'髭切',
'108':'髭切特一',
'109':'髭切特二',
'110':'髭切特三',
'111':'髭切極',
'112':'膝丸',
'113':'膝丸特一',
'114':'膝丸特二',
'115':'膝丸極',
'116':'大倶利伽羅',
'117':'大倶利伽羅極',
'118':'へし切長谷部',
'119':'へし切長谷部極',
'120':'不動行光',
'121':'不動行光極',
'122':'獅子王',
'123':'獅子王極',
'124':'小烏丸',
'125':'小烏丸極',
'126':'抜丸',
'127':'抜丸極',
'128':'同田貫正国',
'129':'同田貫正国極',
'130':'鶴丸国永',
'131':'鶴丸国永極',
'132':'太郎太刀',
'133':'太郎太刀極',
'134':'次郎太刀',
'135':'次郎太刀極',
'136':'日本号',
'137':'日本号極',
'138':'御手杵',
'139':'御手杵極',
'140':'巴形薙刀',
'141':'巴形薙刀極',
'142':'毛利藤四郎',
'143':'毛利藤四郎極',
'144':'篭手切江',
'145':'篭手切江極',
'146':'謙信景光',
'147':'謙信景光極',
'148':'小豆長光',
'149':'小豆長光極',
'150':'日向正宗',
'151':'日向正宗極',
'152':'静形薙刀',
'153':'静形薙刀極',
'154':'南泉一文字',
'155':'南泉一文字極',
'156':'千代金丸',
'157':'千代金丸極',
'158':'山姥切長義',
'159':'山姥切長義極',
'160':'豊前江',
'161':'豊前江極',
'162':'祢々切丸',
'163':'祢々切丸極',
'164':'白山吉光',
'165':'白山吉光極',
'166':'南海太郎朝尊',
'167':'南海太郎朝尊極',
'168':'肥前忠広',
'169':'肥前忠広極',
'170':'北谷菜切',
'171':'北谷菜切極',
'172':'桑名江',
'173':'桑名江極',
'174':'水心子正秀',
'175':'水心子正秀極',
'176':'源清麿',
'177':'源清麿極',
'178':'松井江',
'179':'松井江極',
'180':'山鳥毛',
'181':'山鳥毛極',
'182':'古今伝授の太刀',
'183':'古今伝授の太刀極',
'184':'地蔵行平',
'185':'地蔵行平極',
'186':'治金丸',
'187':'治金丸極',
'188':'日光一文字',
'189':'日光一文字極',
'190':'太閤左文字',
'191':'太閤左文字極',
'192':'五月雨江',
'193':'五月雨江極',
'194':'大千鳥十文字槍',
'195':'大千鳥十文字槍極',
'196':'泛塵',
'197':'泛塵極',
'198':'一文字則宗',
'199':'一文字則宗極',
'200':'村雲江',
'201':'村雲江極',
'202':'姫鶴一文字',
'203':'姫鶴一文字極',
'204':'福島光忠',
'205':'福島光忠極',
'206':'七星剣',
'207':'七星剣極',
'208':'',
'209':'',
'210':'稲葉江',
'211':'稲葉江極',
'212':'笹貫',
'213':'笹貫極'
}

# ファイル整形の関数
def convert_har(file_path, dict_katana = dict_katana):
    # HAR ファイルの読み込みと Python オブジェクト化
    data = json.load(codecs.open(file_path, encoding='utf-8'))

    # 特定の項目を抜き出す処理
    # ここはどう考えてももっと良いやり方があるはず
    data2 =  data["log"]["entries"]
    sword_data = data2[2]["response"]["content"]
    sword_data = sword_data["text"]
    
    # DataFrame化:何段階か
    # ここはどう考えてももっと良いやり方があるはず......
    sw_json = json.loads(sword_data)
    katana = pd.DataFrame(sw_json['sword']).T.reset_index(drop=True)
    
    # 整形
    # 刀剣男士名にしたいので、あらかじめ作った辞書でマッピング
    katana['name'] = katana['sword_id'].map(dict_katana)
    # わかりにくいカラム名を日本語にしただけ
    katana = katana.rename(columns={'name''刀剣男士名''serial_id''個体識別ID''level''レベル''exp''累積経験値',  'recovered_at''更新日''created_at':'入手日'})
    
    #保存
    now = datetime.datetime.now()
    filename = './log_' + now.strftime('%Y%m%d_%H%M%S') + '.csv'
    katana.to_csv(filename) # 今日の日付で保存しておく
    
    # 差分とか計算したいのでオブジェクトも作っておく
    return katana



# 実行
# ダウンロードしたHARファイルの場所を指定すればOK
new = convert_har("/Users/ひみつ/Downloads/20220920_start.har")

これで最低限の整形などができる。ここからはサクッと分析をいくつかやってみた。

まずはデータをチラ見してみる。推しの山姥切長義のデータをみてみよう。

katana.query('sword_id == "158"')[['刀剣男士名','個体識別ID','レベル''累積経験値''更新日''入手日']]
二振りいる。個体識別IDが異なる。

いい感じに育っているようだ。
ここのデータは過去(9/12)のものなので、最新版を読み込んで差分をチェックする。

diff = pd.merge(
    new[['刀剣男士名','個体識別ID','レベル''累積経験値''更新日''入手日']],
    katana[['刀剣男士名','個体識別ID','レベル''累積経験値''更新日''入手日']],
    on = '個体識別ID',
    suffixes=('新''旧')
    )[['刀剣男士名新','個体識別ID','レベル新','レベル旧','累積経験値新','累積経験値旧''更新日新''更新日旧']]

# int型にするだけ 
diff['累積経験値新'] = diff['累積経験値新'].astype(int)

# 前回記録時(正確には、前回記録時点での最新情報日時)から
# 増えた分の経験値、およびその間の日数、レベル上昇分を算出
diff['獲得経験値'] = diff['累積経験値新'].astype(int) - diff['累積経験値旧'].astype(int)
diff['プレイ時間'] = pd.to_datetime(diff['更新日新']) - pd.to_datetime(diff['更新日旧'])
diff['レベル上昇'] = diff['レベル新'].astype(int) - diff['レベル旧'].astype(int)

# カラム名を変えるだけ
diff.rename(columns={'刀剣男士名新''刀剣男士名''累積経験値新''累積経験値''プレイ時間':'計算対象時間''レベル新':'現在のレベル' }, inplace=True)

# 累積経験値の多い順に見てみる
diff[['刀剣男士名','個体識別ID','累積経験値','獲得経験値''現在のレベル''レベル上昇''計算対象時間']].sort_values('累積経験値'ascending=False).head(20)
累積経験値の多い上位20振

圧倒的に「蛍丸極」の経験値が多い。ゲーム開始5日後くらいで顕現してくれてからずーっとわが部隊のエースなので当然である。
では、在籍日数の影響を減らした場合に累積経験値が多いのはだれだろうか。

# 日数で割って一番累積多いのは?

# 今日までの日数計算のためにdatetime型に変換して計算
new['日数'] = datetime.datetime.now() - pd.to_datetime(new['入手日'])

# numpyをここで入れるという失態
import numpy as np

# シンプルに値が小さくて見にくいので常用対数をとった
new['日割り経験値'] = np.log10(new['累積経験値']/new['日数'])
日割りの累積経験値降順20位

なんと意外にも(?)、日割りにしてみると想定外の千代金丸さんだ。これは夏の連隊戦イベント中にドロップし、経験値効率がよく設定されていたので出ずっぱりだったからだろう。七星剣さんは大侵寇終わってから連れ回されているからそれはそうだ。それでいうとやっぱり蛍丸(#1)の出番の多さはすごい。日向と信濃は入手は遅めであるものの、好きなのでめちゃくちゃ使うようになったのが分かる。山姥切長義も日割だと7位。

経過日数と経験値の二軸プロット

ちなみに日割りではなく全体観としてはこちらの図のような感じ。基本的には経験値が低めのところに集まっているが、ちらほらと2,600万超の蛍丸や、1,700万付近の乱、日向、信濃がポツポツ。1,500万は薬研。左上にあるプロットほど、「短期間にレベルが上がった」ということになる。

とにもかくにも当初の目的であった「全刀剣男士の現在のステータスを一括で確認できる」「前回の記録からの経験値の増加分も一括で確認できる」はDONEである。次はやはり、毎回Jupyterを叩かなくても良いように、ローカルで動くアプリなんかにしてみると良いかもしれない。


……なぜ休みの日まで業務みたいなことをしているのだろうか。でもこんな業務なら、仕事も更に楽しいかもしれない。


おわりに

ここまでお読みいただいた奇特な方がおられましたら御礼を。ありがとうございます。なお、コードミスがあればお知らせいただけますと幸いです。また、コードが汚い点についてはご了承ください。

この記事が気に入ったらサポートをしてみませんか?