Pythonで全ポケモン後攻能力指数ランキング作りました

前回投稿した記事の続きです。

前回は(H,B,D)を528通り計算して最も総合耐久指数 T が高くなるようなステータスを求めました。

その続きとして、後攻能力指数のランキングも作成したので投稿しておきます。

後攻能力指数とは?

後攻能力指数は攻撃実数値 A または特攻実数値 C と、前回の記事で扱った総合耐久指数 Tを用いて、 X=mAV(T) という式で表されます。
詳細には、この指数を考案されたジロウさんの記事にて解説されています。

ざっくり説明すると、後攻能力指数とは、後攻前提のポケモンが瀕死になるまでに相手に与える火力の総量です。この値は、総合耐久指数 T に比例して大きくなる行動回数期待値 V(T) を用いて、(正確には比例ではなく単調増加)

$$
後攻能力指数 X=火力補正 m×攻撃実数値 A×行動回数期待値 V(T)
$$

と定義されます。
要するに、相手の攻撃を耐えられた回数だけ、相手を殴ることができるという、それだけです。(Vの式の形は複雑ですが…)

ランキング…の前に

前提として、基本的には前回の総合耐久指数は同じ条件(「いかく、ファーコート、もふもふ、ふとうのたて、すなあらし(岩)」、しんかのきせきの補正を考慮)のもと、今回は素早さを除くH,A,B,C,Dに努力値を508振り、さらに火力を補正する特性、持ち物を考慮しました。
例えば、ローブシンの場合、てつのこぶし型とこんじょう型の二通りに分けて後攻能力指数を算出しました。なぜ今回は持ち物を考慮したのかというと、一部の専用アイテムを持つポケモンは持ち物が固定されるため、他のアイテムによる火力補助を受けられないからです。具体的には、特性こんじょうをはじめ、スナイパーのピントレンズ、ガラガラのふといホネ、未進化ポケモンのしんかのきせき、ザシアンのくちたけん、ピカチュウのでんきだま、などです。(もしかすると漏れがあるかもしれません) 逆にアイテムが固定されていない、アイテムによる火力補助が可能なポケモンには、すべてプレートを持たせたものとしています。

ランキング

考案者さんの別記事にあるランキングとはなぜか100の位から若干数値がずれていますが、7世代の記事のため、一部はフィールド補正やギルガルドの種族値変更などの影響を受けています。それらを除けば順位は一致しているので、おそらく大丈夫でしょう。
また、特性による耐久や火力の補正において、いちいち分けてコードを書くのが面倒だったため四捨五入すべきところを切り捨てたりしています。まあたぶん誤差誤差。
全データは以下のファイルにあります。

プログラムと感想

前回は耐久に振る努力値に対して(H,B,D)を全通りもとめて、総合耐久指数 Tが最大となるような組み合わせを求めましたが、前回がH+B+D=65(努力値による実数値上昇の上限値が一定)だったのに対して、今回はH+B+D+A(or C)=65ですから、H+B+D=65-A(orC)として、Aの値を0~32の間でループさせればいいだけでした。あとは、耐久と火力に性格補正をかけて、後攻能力指数が大きくなる方を採用すれば終わりです。耐久に性格補正を掛ける/掛けない場合でTが528×3通り、Aが33通り、これをCに関しても計算するので、全通りは約10万通りで、あとはこれを987匹分計算するだけですね。
今回も全通り計算したので、1回の出力に20分ほどかかりました。前回は15秒だったのに…組み合わせ爆発は恐ろしい。後攻能力指数の続きの議論として、先攻能力指数や総合能力指数もあるのですが、これらには素早さのパラメーターが追加されるため、全通り計算するとなると桁違いに時間がかかってしまうのではないでしょうか…一応数理最適化の分野で、この問題は目的関数が非線形で、制約条件が H+B+D+A(or C)+S=65、かつそれぞれのパラメーターが0~32の整数値をとる、いわゆる整数非線形計画問題にあたり、どうやらPyomoというモジュールによって解けるらしいです…いかんせん知識がないのでこれ以降はかなり難しくなりそうですが、進捗があればまた記事を書きたいと思います。

import pandas as pd
import math
open("dir\status2.csv")
df0=pd.read_csv("dir\status2.csv",index_col="No.",encoding="cp932")
Lists={n:[[h,b,d],[a,c],[cb,cd],[ca,cc],i,j] for n,h,b,d,cb,cd,a,c,ca,cc,i,j in zip(df0["name"], df0["H"],df0["B"],df0["D"],df0["B特性"],df0["D特性"],df0["A"],df0["C"],df0["A特性"],df0["C特性"],df0["持ち物"],df0["禁止級"])}
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="BD無補正"
    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_s=sorted(TL, reverse=True, key=lambda x: x[0])
    TL_m=TL_s[0]
    return [TL_m[0],TL_m[1]]

class OPT():
    #初期設定
    def __init__(self):
        self.status=[0,0,0]
        self.ck=[0,0]
        self.corr={"rb":1,"rd":1,"cb":1,"cd":1}
        self.atk_corr=1
        self.correct_auto=1
        self.nature=0
        self.IJK={}
        for e in range(33,66):
            IJK_e=[]
            for i in range(33):
                for j in range (33):
                    k=e-i-j
                    if (0<=k)and(k<=32):
                        IJK_e.append([i,j,k])
                    else:
                        pass
            self.IJK[e]=IJK_e
        self.inf=4144
    #最適化
    def opt(self,list,e):
        hbd=[x+y for x,y in zip(list[0],[75,20,20])]
        ac=[x+y for x,y in zip(list[1],[20+65-e,20+65-e])]
        c=self.corr
        atk_corr=1+(self.atk_corr-1)*list[4]
        IJK=self.IJK[e]
        k=self.inf
        #特性・輝石補正
        if self.correct_auto==1:
            [c["cb"],c["cd"]]=[x*y for x,y in zip([c["cb"],c["cd"]],list[2])]
        #性格補正
        if self.nature==0:
            #AorCに補正
            [Tm,m]=main(hbd,c,IJK)
            m=[*m,""]
            ac=[math.floor(x*1.1) for x in ac]
        else:
            #Bに補正
            c["cb"]=c["cb"]*1.1
            [T_cb,m_cb]=main(hbd,c,IJK)
            c["cb"]=c["cb"]/1.1
            #Dに補正
            c["cd"]=c["cd"]*1.1
            [T_cd,m_cd]=main(hbd,c,IJK)
            c["cd"]=c["cd"]/1.1
            [Tm,m]=(lambda x,y:[x,[*m_cb,"D補正でもok"]] if x==y else([x,[*m_cb,""]] if x>y else [y,[*m_cd,""]]))(T_cb,T_cd)
        Tm=round(Tm,1)
        V=2*Tm*math.log(2*Tm/k)-(2*Tm-k)/2
        [Xa,Xc]=[round(x*V*atk_corr,1) for x in [math.floor(y*z) for y,z in zip(ac,list[3])]]
        X=(lambda Xa,Xc: [Xa,ac,"両刀"] if Xa==Xc else([Xa,ac[0],"物理"] if Xa>Xc else [Xc,ac[1],"特殊"]))(Xa,Xc)
        d=[*X,Tm,*m,list[5]]
        if self.correct_auto==1:
            [c["cb"],c["cd"]]=[x/y for x,y in zip([c["cb"],c["cd"]],list[2])]
        return d

Poke=OPT()
#物理技/特殊技を受ける割合,性格,持ち物による補正
Poke.corr={"rb":1,"rd":1,"cb":1,"cd":1}
#持ち物による火力補正
Poke.atk_corr=1.2
#特性補正[いかく,ファーコート,もふもふ,ふくつのたて,すなあらし(岩)]
#進化の輝石補正を考慮する=1
Poke.correct_auto=1
#火力に性格補正=0,耐久に性格補正=1
Poke.nature=0
#火力の下限値
Poke.inf=4144
Dict=[]
for n in Names:
    D=[]
    for Poke.nature in [0,2]:
        d=[[*Poke.opt(Lists[n],e)] for e in range(33,66)]
        D.append(max(d))
    Dict.append(max(D))
df1=pd.DataFrame(Dict)
df1.index=Names
df1.columns=["X","a or c","備考1","T","(H,B,D)","補正","備考2","禁止級"]
df1.to_excel("dir\X_Ranking.xlsx")

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