Pythonで全ポケモン総合耐久指数ランキング作りました

 このサイトに訪れた物好きな方々に敬意を表して、よまいです。
夏休みということでちょっとPythonの勉強でもしてみようと重い腰を上げ、ちょうど総合耐久指数についての記事を読んでいたので、全ポケモンのデータから自動的にこの指数を導くプログラムを書きたいと思った次第です。

総合耐久指数とは?

HP,防御,特防の実数値を T=HBD/(B+D) という式に代入することで求められる、そのポケモンの総合的な耐久を表す値です。
詳細には、この指数を考案したジロウさんの記事で解説されています。

また、ポケモン徹底攻略には、各々のポケモンに対してこの指数を最大化できる耐久調整ツールが存在します。

一応、なぜ T=HBD/(B+D) という式が総合的な耐久を示すのかを、引用を交えながら軽く説明します。
まず、物理攻撃を受けた時のダメージ計算式の定義は、

$$
ダメージ=(0.44×技威力×\frac{攻撃}{防御}+2)×乱数×補正
$$

なので、端数の2を無視して、
Tb $${\simeq}$$ 0.44×技威力×攻撃×乱数×補正という物理火力を受けたときの被ダメージ $${\simeq}$$ Tb/Bと近似できます。
さらに、物理火力 Tb、特殊火力 Tdを受けたとき、ちょうど HPが0になったとすると、H=Tb/B+Td/Dとなり、ここで Tb=Tdと仮定すると、T=HBD/(B+D)が導かれます。

以上の議論から、この Tという値は、「物理攻撃と特殊攻撃を全く同じ比率で受ける場合(Tb=Td)、瀕死になるまでに受けられる火力の量(の半分)」を表していることがわかると思います。

この Tb=Tdという仮定は、
・物理、特殊片方に特化するよりも、両面の耐久を厚くした方が総合耐久指数は高くなる
・不特定の物理・特殊両方の技を受けることが多いダブルバトルにおいては、仮定が成り立ちやすい
・シングルバトルの場合は、交代際に防御側のみが一方的に相手を選んでいるため、防御側の選択権の方が大きい
→物理技を打ってきそうな相手と対面した場合、防御が高いポケモンに交代すればよい
物理攻撃、特殊攻撃を受ける比率が偏ってしまい、仮定が成り立ちにくい
…というような影響を及ぼします。
そのため、交代を繰り返すサイクル構築ではこの仮定が成り立たず、むしろ対面構築に近い構築において、この総合耐久指数という値は意味を持つといえます。

ランキング

前提として、振る努力値は508、性格補正あり、特性は「いかく、ファーコート、もふもふ、ふとうのたて、すなあらし(岩)」、しんかのきせきの補正を考慮しています。

全データは以下のファイルにあります。

プログラム

Python初心者なのでPandasとかも調べつつ、試行錯誤しながら作りました。一番難しかったのは、Tを最大化するような[H,B,D]を探すアルゴリズムをどう数学的に作るか、でした。
初めに考えたものは、(H,B,D)から(H+1,B,D) or (H,B+1,D) or (H,B,D+1)のうちTが最も大きくなる点に移動する、これを努力値を振り切るまでループさせる…みたいなアルゴリズムでした。しかしこのアルゴリズムは11n調整が最適解の場合、実数値が不連続に変化するためうまくいきませんでした。
そこで、次に考えたのは、初めのアルゴリズムで求めた(H,B,D)付近で最もTが大きくなるような点を最適解とする、というものでした。その後、試行錯誤を重ねた結果…
結局全通り計算したほうが早いのでは?という結論に至りました。
耐久に努力値を振りきった場合、取りうる(H,B,D)の値は高々528通りなので、結局これを全ポケモン987匹分計算させるだけで、だいたい15秒もかかりませんでした。最適なアルゴリズムを見つけるよりも、マシンパワーでごり押したほうが良いときもあるという教訓を得ました。僕の苦労は一体何だったのか。
以下に、今回のランキングを生成するプログラムを貼っておきます。Python触り始めて半年ぐらいで、欲しい結果が得られて満足です。

import pandas as pd
import math
open("dir\status.csv")
df0=pd.read_csv("dir\status.csv",index_col="No.")
df0=df0.dropna(subset=["H","B","D"])
Lists={n:[[int(x) for x in[h,b,d]],cb,cd] for n,h,b,d,cb,cd in zip(df0["name"], df0["H"],df0["B"],df0["D"],df0["B特性"],df0["D特性"])}
Names=Lists.keys()

#総合耐久指数
def T(h,b,d,c):
    [rb,rd,cb,cd]=c.values()
    if ((math.floor(cb*100))%11==0):
        b=math.floor(b*1.1)
        cb=cb/1.1
    elif ((math.floor(cd*100))%11==0):
        d=math.floor(d*1.1)
        cd=cd/1.1
    b=math.floor(b*cb)
    d=math.floor(d*cd)
    T=h*b*d/(rd*b+rb*d)
    return T

#性格補正
def N_corr(l,c):
    if (math.floor(c["cb"]*100))%11==0:
        l[1]=math.floor(l[1]*1.1)
        str="B補正"
    elif (math.floor(c["cd"]*100))%11==0:
        l[2]=math.floor(l[2]*1.1)
        str="D補正"
    else:
        str="無補正"
    return l,str

def main(l,c,IJK):
    L=[]
    for ijk in IJK:
        L.append([x+y for x,y in zip(l,ijk)])
    TL=[[T(*l,c),*N_corr(l,c)] for l in L]
    TL_sorted=sorted(TL, reverse=True, key=lambda x: x[0])
    m=TL_sorted[0]
    return m

class OPT():
    #初期設定
    def __init__(self):
        self.status=[0,0,0]
        self.correct={"rb":1,"rd":1,"cb":1,"cd":1}
        self.auto=0
        self.IJK=[]
     #最適化     def opt(self):
        l=[x+y for x,y in zip(self.status,[75,20,20])]
        c=self.correct
        IJK=self.IJK
        if self.auto==0:
            opt=[main(l,c,IJK),""]
        #性格補正を自動的に選択
        else:
            #Bに補正
            c["cb"]=c["cb"]*1.1
            m_cb=main(l,c,IJK)
            T_cb=m_cb[0]
            c["cb"]=c["cb"]/1.1
            #Dに補正
            c["cd"]=c["cd"]*1.1
            m_cd=main(l,c,IJK)
            T_cd=m_cd[0]
            c["cd"]=c["cd"]/1.1
            if T_cb>T_cd:
                opt=[*m_cb,""]
            elif T_cb<T_cd:
                opt=[*m_cd,""]
            else:
                opt=[*m_cb,"D補正でもok"]
        return opt

Poke=OPT()
#H,B,Dに配分する努力値による実数値上昇の上限値
e=65
Poke.IJK=[]
for i in range(33):
    for j in range (33):
        k=e-i-j
        if (0<=k)and(k<=32):
            Poke.IJK.append([i,j,k])
        else:
            pass
#物理技/特殊技を受ける割合,性格,持ち物による補正
c={"rb":1,"rd":1,"cb":1,"cd":1}
#性格補正を自動化:Poke.auto=1
Poke.auto=1
#特性補正[いかく,ファーコート,もふもふ,ふくつのたて,すなあらし(岩)]
#進化の輝石補正を考慮する:correct=1
correct=1
d={}
for n in Names:
    Poke.status=Lists[n][0]
    if correct==1:
        Poke.correct["cb"]=c["cb"]*Lists[n][1]
        Poke.correct["cd"]=c["cd"]*Lists[n][2]
        opt=Poke.opt()
        Poke.correct["cb"]=c["cb"]/Lists[n][1]
        Poke.correct["cd"]=c["cd"]/Lists[n][2]
    else:
        Poke.correct=c
        opt=Poke.opt()
    d[n]={"T":round(opt[0],1),"(H,B,D)":opt[1],"補正":opt[2],"備考":opt[3]}
df1=pd.DataFrame(d).T
df1=df1.sort_values(["T"],ascending=False)
print(df1)
df1.to_csv("dir\T_Ranking.csv")

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