【!st】ポケモンの種族値から実数値を算出する[DiscordBot]

初めに

DiscordBotの引継ぎ候補という圧力がサークル内に蔓延している。

Pythonでプログラムを書くのにもだいぶ慣れてきたので、せっかくだし作ることにした。

開発記録・リファレンスとして、記事を投稿していく予定。

!st (種族値/実数値表示)

!st ポケモン名[*] [レベル]

レベルを指定しなかった場合、Lv50の実数値を返します。

レベルを指定した場合、入力したレベルでの実数値を返します。

レベルに-1に指定した場合、そのポケモン種族値を返します。

名前の後ろに半角の * をつけると、名前の部分一致で検索できます。

使用例

種族値を実数値に変換するプログラム

3値(種族値個体値努力値)に関しては、以下の記事を参照。

個体値・種族値・努力値とは?|ポケモン徹底攻略

実数値の表を作成するには?

まずは、変数の定義から説明する。

bst…あるポケモン種族値リスト(Base Status)

ex. ライチュウの場合

bst=[H,A,B,C,D,S]=[60, 90, 55, 90, 80, 110]

Hの実数値は、bst_1=H=100と表現する。

この種族値リストから、以下の表を作成したい。

ast…あるポケモンの実数値(Actually Status)

i\j H A B C D S
最高 167 156 117 156 145 178
167 142 107 142 132 162
無振 135 110 75 110 100 130
最低 120 85 54 85 76 103

この表は、Pythonの辞書の形式で表すと、以下のようになる。

astdict={
'最高': [167, 156, 117, 156, 145, 178], 
' 準': [167, 142, 107, 142, 132, 162],
'無振': [135, 110, 75, 110, 100, 130],
'最低': [120, 85, 54, 85, 76, 103]
}

astdictは4×6の行列になっている。この辞書が作れればok

astdict['最高']=[167, 156, 117, 156, 145, 178]の行を例に挙げて説明する。

これは1行目の実数値リストなので、ast_1と表現する。

Pythonでは、配列aの1番目の要素をa[0]と表現するが、ややこしいのでa_1=a[0]ということにする。一般に、第i行=ast_i)

よって、ast_1=[167, 156, 117, 156, 145, 178]

ast_1のj列の要素は、ast_1jと表現する。

つまり、ast_11=167

ようやく、実数値 ast_ijを計算する準備が整った。

第i行を計算する

種族値と実数値の関係式は以下の通り。

計算式

最大HP

実数値=(種族値×2+個体値+努力値÷4)×レベル÷100+レベル+10

こうげき・ぼうぎょ・とくこう・とくぼう・すばやさ

実数値={(種族値×2+個体値+努力値÷4)×レベル÷100+5}×せいかく補正

(ただし、小数はすべて切り捨て)

この式から、j=1とj≠1のときでast_ijの計算式が異なることがわかる。

第i行のast_iを計算するために必要な変数は、

  • iに依存しないもの

bst_j = 種族値のj列のステータス

(j=1,2,3,4,5,6 に対し、H,A,B,C,D,Sが対応する)

lv=レベル(=デフォルト:50)

  • iに依存するもの

iv_ij = i行j列のステータスの個体値

ev_ij = i行j列のステータスの努力値

ncor_ij = i行j列のステータスの性格補正

iに依存する変数はまとめて、b2a_iと定義する。(bst_j to ast_ij)

b2a_i=[iv_i,ev_i,ncor_i]

そして、lambda式で関数fを次のように定義する

(intで小数から整数に型変換して切り捨てを行っている)

f=lambda bst_j,iv,ev,lv:int(int((int(bst_j*2+iv+ev/4)*lv/100+5

このfを使って、ast_ijは次のように計算できる

ast_j=f(bst[j],iv,ev,lv)+lv+5 (j=1)

ast_j=int((f(bst[j],iv,ev,lv))*ncor[j]) (j≠1)

以上をPythonの関数として記述するとこうなる。

#ある同じ値orgをjに関して繰り返すリストを定義(original copied list)
cplist=lambda org:[org,org,org,org,org,org]

def bst2ast(bst,lv,iv,ev,ncor):
    ast_i=[] #実数値を格納するリスト
    for j in range(6):
        f=lambda st,iv,ev,lv:int(int(st*2+iv+ev/4)*lv/100)+5
        if j==0:
            ast_j=f(bst[j],iv[j],ev[j],lv)+lv+5
        else:
            ast_j=int((f(bst[j],iv[j],ev[j],lv))*ncor[j])
        ast_i.append(ast_j)
    return ast_i

#ライチュウの種族値リスト
bst=[60,90,55,90,80,110] 
#デフォルトのLv
lv=50
#個体値リスト
iv_1=cplist(31)
#努力値リスト
ev_1=cplist(252)
#性格補正リスト
ncor_1=cplist(1.1)

b2a_1=[iv_1,ev_1,ncor_1]

ast_1=bst2ast(bst,lv,*b2a_1)
print(ast_1)
#出力:[167, 156, 117, 156, 145, 178]

これで、Lv50のときの各ステータスの最大値が求められた。

同じように、第i行目に関してast_iを計算するには、上の関数bst2astをiについてループすればよい。

def bst2astdict(bst,lv):
    #lv=-1とした場合、種族値をそのまま返す
    if lv==-1:
        return {"種族値":bst}
    else:
        #個体値・努力値・性格補正の行列
        b2a=[
            [cplist(31),cplist(252),cplist(1.1)],
            [cplist(31),cplist(252),cplist(1)],
            [cplist(31),cplist(0),cplist(1)],
            [cplist(0),cplist(0),cplist(0.9)]
            ]
        #結果を格納するリスト
        astlist=[]
        for i in range(4):
            ast_i=bst2ast(bst,lv,*b2a[i])
            astlist.append(ast_i)
        keys=["最高","  準","無振","最低"]
        astdict=dict(zip(keys,astlist))
        return astdict

bst=[60,90,55,90,80,110] #ライチュウの種族値リスト
lv=50 #デフォルトのLv
astdict=bst2astdict(bst,lv)
print(astdict)
"""出力
{'最高': [167, 156, 117, 156, 145, 178],
'  準': [167, 142, 107, 142, 132, 162],
'無振': [135, 110, 75, 110, 100, 130],
'最低': [120, 85, 54, 85, 76, 103]}
"""

あとはこれを出力すればOK!

#リスト[167, 156, 117, 156, 145, 178]を
#文字列 167-156-117-156-145-178 に変換する関数
l2s=lambda s,l:s.join(map(str,l))
msg=""
for item in astdict.items():
    msg+=f"> {item[0]}: {l2s('-',item[1])}\n"
print(msg)
> 最高: 167-156-117-156-145-178
>   準: 167-142-107-142-132-162
> 無振: 135-110-75-110-100-130
> 最低: 120-85-54-85-76-103

PostgreSQLから種族値データを取得する

あいまい検索かそうでないかで場合分けして結果を出力する。

import psycopg2
import numpy as np
import re
from dotenv import load_dotenv
import os
load_dotenv()
DATABASE_URL=os.environ["DATABASE_URL"]

#list to str
l2s=lambda s,l:s.join(map(str,l))
def st(name,lv=50):
    #出力用関数を定義
    def fetch_bst(name,lv):
        #アイコンを生成
        cur.execute(
            f"select icon from pokedex where name='{name}'"
            )
        icon=cur.fetchone()[0]
        print(icon)
        cur.execute(
            f"select h,a,b,c,d,s from pokedex where name ='{name}'"
            )
        bst=cur.fetchone()
        astdict=bst2astdict(bst,lv)
        msg=""
        for item in astdict.items():
            msg+=f"> {item[0]}: {l2s('-',item[1])}\n"
        print(msg)
    
    conn=psycopg2.connect(DATABASE_URL)
    with conn:
        with conn.cursor() as cur:
            #名前の最後に"*"をつけるとあいまい検索になる
            #あいまい検索している場合
            if re.findall(r'\*',name):
                name=name.strip('\*')
                #DBから名前をあいまい検索
                cur.execute(
                    f"select name from pokedex where name like '%{name}%'"
                    )
                #ヒットしたすべての結果:fnames
                fnames=np.array(cur.fetchall()).flatten()
                for fname in fnames:
                    seek_bst(fname,lv)       
            #あいまい検索でない場合
            else:
                seek_bst(name,lv)

st("ライチュウ*")
> 最高: 167-156-117-156-145-178
>   準: 167-142-107-142-132-162
> 無振: 135-110-75-110-100-130
> 最低: 120-85-54-85-76-103

> 最高: 167-150-112-161-150-178
>   準: 167-137-102-147-137-162
> 無振: 135-105-70-115-105-130
> 最低: 120-81-49-90-81-103

これで完成!