【?】コマンドの使い方・オプション編[Discord Bot]

本記事では、以下の項目について説明をする。

  • ? コマンドの基本的な使い方
  • ? コマンドのオプション

正直コマンドのオプションを増やしすぎた

Discordから受け取ったメッセージを実行する、SQLクラスのインスタンスを生成

from SQL import SQL
Query=SQL()

? コマンドの基本的な使い方

? コマンドの引数,出力,説明を見る

? コマンドの引数,出力,説明を見るには、!psql コマンド名を使う。

2行目は、引数1,引数2,引数3 ==> 出力1,出力2,出力3の形式で出力される。

コマンド名を省略して、!psqlと入力すると、すべての ? コマンドの説明が表示される。

実行例 (23/8/7現在)

!psql sev

sev
技, -, -  ==> ポケモン, -, -
その技を覚えるポケモンを表示する
----------------------------------------------------------
sevty
技, タイプ, -  ==> ポケモン, -, -
その技を覚える指定したタイプをもつポケモンを表示する
----------------------------------------------------------
sevdt
技, -, -  ==> ポケモン, -, -
その技の技タイプとタイプ不一致で、その攻撃技を覚えるポケモンを表示する
----------------------------------------------------------

? コマンドの実行

Query.exe()の括弧の文をDiscordで送信すると、結果が返ってくる。

基本的な構文は、?コマンド名 引数1 引数2 ...

例えば、?sev ちょうはつを入力すると、ちょうはつを覚えるポケモンが表示される。

Query.exe("?sev ちょうはつ")
name
0 イダイナキバ
1 ヤトウモリ
2 ケロマツ
3 ミジュマル
4 サナギラス
... ...
213 コノヨザル
214 マニューラ
215 ゲンガー
216 スリーパー
217 ベトベトン

218 rows × 1 columns

?speed 90 130のように、引数を複数とるコマンドも存在する。

!psql speed

speed
種族値, 種族値, -  ==> 種族値, ポケモン, -
そのS種族値以上かつ指定したS種族値以下のポケモンを表示する
----------------------------------------------------------
Query.exe("?speed 90 130")
s name
0 130 ミュウツー
1 130 サンダース
2 130 コオリッポ(ナイス)
3 130 ムゲンダイナ
4 130 レイスポス
... ... ...
148 90 コノヨザル
149 90 ニャース(アローラ)
150 90 ニャース
151 90 ディグダ(アローラ)
152 90 ディアルガ

153 rows × 2 columns

?コマンドを合成する

and オプション (ポケモン名で内部結合)

S種族値が90以上130以下 かつ ちょうはつを覚えるポケモンを表示したいとする。

つまり、?speed 90 130?sev ちょうはつを合成したい。

これは、?speed 90 130 and ?sev ちょうはつと入力することで合成できる。

なお、andは省略できるので、?speed 90 130 ?sev ちょうはつと入力しても同じ。

Query.exe("?speed 90 130 ?sev ちょうはつ")
s name
0 130 ミュウツー
1 130 レイスポス
2 126 ファイアロー
3 125 マニューラ
4 123 オンバーン
... ... ...
76 90 ブロロローム
77 90 カラミンゴ
78 90 コノヨザル
79 90 ニャース(アローラ)
80 90 ニャース

81 rows × 2 columns

複数のコマンドを合成することもできる。

Query.exe("?speed 90 130 and ?sev ちょうはつ ?sev ステルスロック")
s name
0 120 アルセウス
1 115 アグノム
2 112 ルガルガン(まひる)
3 110 ルガルガン(たそがれ)
4 101 ランドロス(化身)
5 100 ミュウ
6 98 サザンドラ
7 95 オコリザル
8 92 ワルビアル
9 91 ランドロス(霊獣)
10 90 コノヨザル

※1 コマンド合成の順番について

展開

コマンドは左から順番に実行され、コマンド合成は、左側のポケモン名を基準に合成される。

そのため、?spped?sevの順番を入れ替えて、?sev ちょうはつ and ?speed 90 130と入力すると、

  1. ちょうはつを覚えるポケモンがピックアップされる

  2. ちょうはつを覚えるポケモンのうち、S種族値が90~130のポケモン

という処理がなされるので、結果はS種族値の順番に並ばない。

Query.exe("?sev ちょうはつ and ?speed 90 130")
name s
0 テツノイサハ 104
1 トルネロス(霊獣) 121
2 トルネロス(化身) 111
3 タギング 110
4 イッカネズミ 111
... ... ...
76 バスラオ(赤) 98
77 ファイヤー(ガラル) 90
78 サンダー(ガラル) 100
79 ビリリダマ(ヒスイ) 100
80 ビリリダマ 100

81 rows × 2 columns

この問題は後述のsortオプションで解決できる。

Query.exe("?sev ちょうはつ and ?speed 90 130 sort s d")
name s
0 ミュウツー 130
1 レイスポス 130
2 ファイアロー 126
3 マニューラ 125
4 オンバーン 123
... ... ...
76 ヤルキモノ 90
77 ニャース 90
78 ファイヤー(ガラル) 90
79 コノヨザル 90
80 ブロロローム 90

81 rows × 2 columns

※2 コマンドの合成の詳細について

展開

コマンド合成は、内部的にはandオプションによって実行されている。

また、andオプションはポケモン(列名がnameのもの)以外で結合することができない。

任意の結合を行いたい場合は、後述のinnner,outer,left,rightオプションを使う。

or オプション (ポケモン名で外部結合)

ちょうはつ もしくは アンコールを覚えるポケモンを表示したいとする。

このときは、andではなくorを使う。

Query.exe("?sev ちょうはつ or ?sev アンコール")
name
0 イダイナキバ
1 ヤトウモリ
2 ケロマツ
3 ミジュマル
4 サナギラス
... ...
264 ルリリ
265 キマワリ
266 ヒマナッツ
267 ププリン
268 ピチュー

269 rows × 1 columns

引数を省略する

引数は * を入力することで、省略することができる。

例えば、?heal *と入力すると、回復技と、その回復技を覚えるポケモンの組がすべて表示される。

Query.exe("?heal *")
name move
0 ピカチュウ プレゼント
1 ピカチュウ ねがいごと
2 ピカチュウ ドレインキッス
3 ライチュウ ねがいごと
4 ライチュウ ドレインキッス
... ... ...
687 トドロクツキ はねやすめ
688 テツノブジン ドレインパンチ
689 コライドン ドレインパンチ
690 ミライドン パラボラチャージ
691 テツノイサハ ギガドレイン

692 rows × 2 columns

引数が複数ある場合は、省略したい箇所に*をつける。

Query.exe("?speed 140 *")
s name
0 200 レジエレキ
1 150 マルマイン
2 150 マルマイン(ヒスイ)
3 150 バドレックス(こくば)
4 148 ザシアン(王)
5 142 ドラパルト

※3 * の内部的な処理について

展開

? コマンドを登録する際に、SQL文に?を埋め込むことで、コマンドに引数を設定することができる。

*(任意入力)はクエリ内部で以下の変換表に従って変換されている。

変換前(sqlcmd.cmd) 変換後(PostgreSQL)
不等号 >=? >=0
不等号 ?<= 0<=
不等号 <=? <=65535
不等号 ?>= 65535>=
不等号 >? >0
不等号 ?< 0<
不等号 <? <65535
不等号 ?> 65535>
中間一致 '%'||?||'%' '%'
前方一致 '%'||? '%'
後方一致 ?||'%' '%'
等号 =? LIKE '%'
等号否定 !=? LIKE '%'
IN句 ? IN ( '%' IN ('%',
NOT IN句 ? NOT IN ( '%' IN ('%',

上の表の変換前(sqlcmd.cmd)は!psqlcmd コマンド名で確認できるクエリ。

!psqlcmd heal 

heal
SELECT pokedex.name,move.move FROM move,poke2move,pokedex WHERE 
+ pokedex.name LIKE '%' --変換後
- pokedex.name = ? --変換前
AND poke2move.id = pokedex.id AND move.id = poke2move.moveid AND move.text like '%HP%回復する%' ORDER BY pokedex.id 

? コマンドのオプション

前提として、? コマンドは引数に応じて表を1つ返す。

そして、コマンドのオプションは3種類ある。

  • 1つの表を1つの表に変換するもの(sort,where,drop,loc,rename,dup,desc,groupby)
  • 2つの表を1つの表に結合するもの(and,or,inner,outer,left,right,diff)
  • 出力形式を変更するもの(show,out,plot)

1つの表を1つの表に変換するもの

sort オプション (順番を並び替える)

ネクロズマのフォルム違いを、S種族値順に並び替えたいとする。

これは、sort s dを付けることで実現できる。

dは降順に並び替える場合。また、dは省略できるので、sort sと入力しても同じ。

Query.exe("?st ネクロズマ sort s")
name h a b c d s sum
0 ウルトラネクロズマ 97 167 97 167 97 129 754
1 ネクロズマ 97 107 101 127 89 79 600
2 ネクロズマ(日食) 97 157 127 113 109 77 680
3 ネクロズマ(月食) 97 113 109 157 127 77 680

昇順に並び替える場合は、sort h aと入力する。

Query.exe("?st ネクロズマ sort s a")
name h a b c d s sum
0 ネクロズマ(日食) 97 157 127 113 109 77 680
1 ネクロズマ(月食) 97 113 109 157 127 77 680
2 ネクロズマ 97 107 101 127 89 79 600
3 ウルトラネクロズマ 97 167 97 167 97 129 754

上の実行結果では、日食ネクロと月食ネクロの s=77 で同じだが、b,c,dに関しては値が異なっている。

そのため、複数列を基準にソートしたい場合は、sort s,b a,aのように入力する。

すると、S種族値で昇順にソートしたあと、さらにB種族値で昇順にソートした結果が得られる。

Query.exe("?st ネクロズマ sort s,b a,a")
name h a b c d s sum
0 ネクロズマ(月食) 97 113 109 157 127 77 680
1 ネクロズマ(日食) 97 157 127 113 109 77 680
2 ネクロズマ 97 107 101 127 89 79 600
3 ウルトラネクロズマ 97 167 97 167 97 129 754
where オプション (条件を満たす行を抽出する・列を追加する)

複雑な条件を指定する場合は、条件を`(バッククォート)で囲って記述する。

内部的には、pandasのpd.evalメソッドが呼び出されている。脆弱性があるとかないとか

※バッククォートは、JISキーボードならShift+@で入力できます

SQLWHERE句と同じなのでwhereオプションと呼ぶことにします

以下は、物理耐久指数 = H実数値*性格補正込みB実数値 >=50000 のポケモンを表示している。

Query.exe("?st * `(h+107)*(b+52)*1.1>=50000`")
name h a b c d s sum
0 メガヤドラン 95 75 180 130 80 30 590
1 ハガネール 75 85 200 55 65 30 510
2 メガハガネール 75 125 230 55 95 30 610
3 メガボスゴドラ 70 140 230 60 80 50 630
4 レジロック 80 100 200 50 100 50 580
5 クレベース 95 117 184 44 46 28 514
6 クレベース(ヒスイ) 95 127 184 34 36 38 514
7 ジガルデ(パーフェクト) 216 100 121 91 95 85 708
8 メルメタル 135 143 143 80 65 34 600
9 ディンルー 155 110 125 55 80 45 570

また、物理耐久指数を、新しい列 hb として表に加えたい場合は、以下のように記述する。

Query.exe("?std ?st * `hb=(h+107)*(b+52)*1.1`")
name h a b c d s sum hb
0 リザードン 78 84 78 109 85 100 534 26455.0
1 ライチュウ 60 90 55 90 80 110 485 19655.9
2 ライチュウ(アローラ) 60 85 50 95 85 110 485 18737.4
3 プクリン 140 70 45 85 50 45 435 26354.9
4 モルフォン 70 65 60 90 75 90 450 21806.4
... ... ... ... ... ... ... ... ... ...
290 イーユイ 55 80 80 135 120 100 570 23522.4
291 トドロクツキ 105 139 71 55 101 119 590 28683.6
292 テツノブジン 74 130 90 120 60 116 590 28272.2
293 ウネルミナモ 99 83 91 125 83 109 590 32403.8
294 テツノイサハ 90 130 88 70 108 104 590 30338.0

295 rows × 9 columns

文字列に関する条件や、複数条件を指定することも可能。

Query.exe("?st * `name=='ケッキング'  or name in ['カミツルギ','ラムパルド']`")
name h a b c d s sum
0 ケッキング 150 160 100 95 65 100 670
1 ラムパルド 97 165 60 65 50 58 495
2 カミツルギ 59 181 131 59 31 109 570

正規表現なども利用できる。

@df.aでローカル変数dfにアクセスできる

@df.a>=160a>=160と同じ。

Query.exe("?st * `(name.str.match('メガ.*') or name.str.contains('イルカマン')) and @df.a>=160`")
name h a b c d s sum
0 メガミュウツーX 106 190 100 154 100 130 780
1 メガヘラクロス 80 185 115 40 105 75 600
2 メガバンギラス 100 164 150 95 120 71 700
3 メガバシャーモ 80 160 80 130 80 100 630
4 メガジュペッタ 64 165 75 93 83 75 555
5 メガレックウザ 105 180 100 180 100 115 780
6 メガガブリアス 108 170 115 120 95 92 700
7 メガエルレイド 68 165 95 65 115 110 618
8 メガディアンシー 50 160 110 160 110 110 700
9 イルカマン(マイティ) 100 160 97 106 87 100 650

※4 whereオプション内における* の内部的な処理について

展開

whereオプション付きのコマンドを登録する際に、where句内、`(バッククォート)の内側に引数 ? を設定することができる。

whereオプション内で、*(任意入力)はクエリ内部で以下の変換表に従って変換されている。

変換前(sqlcmd.cmd) 変換後(PostgreSQL)
不等号 >=? >=0
不等号 ?<= 0<=
不等号 <=? <=65535
不等号 ?>= 65535>=
不等号 >? >0
不等号 ?< 0<
不等号 <? <65535
不等号 ?> 65535>
等号 =="?" .str.match('.*')
等号否定 !="?" .str.match('.*')
中間一致 .str.contains("?") .str.match('.*')
前方一致 .str.startswith("?") .str.match('.*')
後方一致 .str.endswith("?") .str.match('.*')

!psqlcmd spe

spe
?showS * ?single * drop rank 
+ `0<=a_s<=65535` --変換後
- `?<=a_s<=?` --変換前
sort a_s,s d,d

loc オプション (列を抽出)

?st *で、全ポケモン種族値データを表示できる。

Query.exe("?st *")
name h a b c d s sum
0 フシギダネ 45 49 49 65 65 45 318
1 フシギソウ 60 62 63 80 80 60 405
2 フシギバナ 80 82 83 100 100 80 525
3 メガフシギバナ 80 100 123 122 120 80 625
4 ヒトカゲ 39 52 43 60 50 65 309
... ... ... ... ... ... ... ... ...
1172 テツノブジン 74 130 90 120 60 116 590
1173 コライドン 100 135 115 85 100 135 670
1174 ミライドン 100 85 100 135 115 135 670
1175 ウネルミナモ 99 83 91 125 83 109 590
1176 テツノイサハ 90 130 88 70 108 104 590

1177 rows × 8 columns

上の表から、name,sum の列だけを抽出したい場合は、loc name,sumと入力する。

Query.exe("?st * loc name,sum")
name sum
0 フシギダネ 318
1 フシギソウ 405
2 フシギバナ 525
3 メガフシギバナ 625
4 ヒトカゲ 309
... ... ...
1172 テツノブジン 590
1173 コライドン 670
1174 ミライドン 670
1175 ウネルミナモ 590
1176 テツノイサハ 590

1177 rows × 2 columns

上の表から、name, h, a, b のように連続している列を抽出したい場合は、loc name:bと入力する。

Query.exe("?st * loc name:b")
name h a b
0 フシギダネ 45 49 49
1 フシギソウ 60 62 63
2 フシギバナ 80 82 83
3 メガフシギバナ 80 100 123
4 ヒトカゲ 39 52 43
... ... ... ... ...
1172 テツノブジン 74 130 90
1173 コライドン 100 135 115
1174 ミライドン 100 85 100
1175 ウネルミナモ 99 83 91
1176 テツノイサハ 90 130 88

1177 rows × 4 columns

drop オプション (列を削除する)

種族値データから、c, sum の列を削除するには、drop c,sumと入力する。

Query.exe("?st * drop c,sum")
name h a b d s
0 フシギダネ 45 49 49 65 45
1 フシギソウ 60 62 63 80 60
2 フシギバナ 80 82 83 100 80
3 メガフシギバナ 80 100 123 120 80
4 ヒトカゲ 39 52 43 50 65
... ... ... ... ... ... ...
1172 テツノブジン 74 130 90 60 116
1173 コライドン 100 135 115 100 135
1174 ミライドン 100 85 100 115 135
1175 ウネルミナモ 99 83 91 83 109
1176 テツノイサハ 90 130 88 108 104

1177 rows × 6 columns

rename オプション (列名を変更する)

?st *で、列名を nameからpokemonに、sumから合計種族値 に変更したいとする。

このときは、rename name:pokemon,sum:合計種族値と入力する。

Query.exe("?st * rename name:pokemon,sum:合計種族値")
pokemon h a b c d s 合計種族値
0 フシギダネ 45 49 49 65 65 45 318
1 フシギソウ 60 62 63 80 80 60 405
2 フシギバナ 80 82 83 100 100 80 525
3 メガフシギバナ 80 100 123 122 120 80 625
4 ヒトカゲ 39 52 43 60 50 65 309
... ... ... ... ... ... ... ... ...
1172 テツノブジン 74 130 90 120 60 116 590
1173 コライドン 100 135 115 85 100 135 670
1174 ミライドン 100 85 100 135 115 135 670
1175 ウネルミナモ 99 83 91 125 83 109 590
1176 テツノイサハ 90 130 88 70 108 104 590

1177 rows × 8 columns

dup オプション (重複行を抽出する)

フォルムチェンジがあるポケモンを表示したいとする。

?tb テーブル名でテーブルの全データを表示できる(ただし、表示できるデータには文字制限がある)

このコマンドを使って、?tb pokedex loc no,nameと入力することでpokedexテーブルから図鑑番号と名前を抽出できる。

Query.exe("?tb pokedex loc no,name")
no name
0 1 フシギダネ
1 2 フシギソウ
2 3 フシギバナ
3 3 メガフシギバナ
4 4 ヒトカゲ
... ... ...
1172 1006 テツノブジン
1173 1007 コライドン
1174 1008 ミライドン
1175 1009 ウネルミナモ
1176 1010 テツノイサハ

1177 rows × 2 columns

さらに、dup noと入力することで、図鑑番号が重複している(≒フォルムチェンジがある)ポケモンを抽出できる。

Query.exe("?tb pokedex loc no,name dup no")
no name
0 3 フシギバナ
1 3 メガフシギバナ
2 6 リザードン
3 6 メガリザードンX
4 6 メガリザードンY
... ... ...
301 916 パフュートン♀
302 931 イキリンコ(青・緑)
303 931 イキリンコ(白・黄)
304 964 イルカマン(ナイーブ)
305 964 イルカマン(マイティ)

306 rows × 2 columns

dupの引数は複数とることができ、図鑑番号と合計種族値が重複しているポケモンを抽出することもできる。

Query.exe("?tb pokedex loc no,name,sum dup no,sum")
no name sum
0 6 メガリザードンX 634
1 6 メガリザードンY 634
2 19 コラッタ 253
3 19 コラッタ(アローラ) 253
4 20 ラッタ 413
... ... ... ...
188 905 ラブトロス(霊獣) 580
189 916 パフュートン♂ 489
190 916 パフュートン♀ 489
191 931 イキリンコ(青・緑) 417
192 931 イキリンコ(白・黄) 417

193 rows × 3 columns

unique オプション (重複行の削除)

dupの逆操作。

例えば、unique noと入力すると、図鑑番号が重複する2番目以降の行が削除される。

また、uniquedup同様、複数列を対象にできる。

Query.exe("?tb pokedex loc no,name unique no")
no name
0 1 フシギダネ
1 2 フシギソウ
2 3 フシギバナ
3 4 ヒトカゲ
4 5 リザード
... ... ...
1005 1006 テツノブジン
1006 1007 コライドン
1007 1008 ミライドン
1008 1009 ウネルミナモ
1009 1010 テツノイサハ

1010 rows × 2 columns

desc オプション (データを要約する)

?showS ポケモン名でそのポケモンのS実数値を表示できる。

Query.exe("?showS コオリッポ")
a_s name set1 set2 s
0 400.0 コオリッポ(ナイス) 最速 こうそくいどう 130
1 364.0 コオリッポ(ナイス) 準速 こうそくいどう 130
2 300.0 コオリッポ(ナイス) 最速 +1 130
3 300.0 コオリッポ(ナイス) 無振 こうそくいどう 130
4 273.0 コオリッポ(ナイス) 準速 +1 130
5 224.0 コオリッポ(アイス) 最速 こうそくいどう 50
6 204.0 コオリッポ(アイス) 準速 こうそくいどう 50
7 200.0 コオリッポ(ナイス) 最速 130
8 182.0 コオリッポ(ナイス) 準速 130
9 168.0 コオリッポ(アイス) 最速 +1 50
10 153.0 コオリッポ(アイス) 準速 +1 50
11 150.0 コオリッポ(ナイス) 無振 130
12 140.0 コオリッポ(アイス) 無振 こうそくいどう 50
13 121.0 コオリッポ(ナイス) 最遅 130
14 112.0 コオリッポ(アイス) 最速 50
15 102.0 コオリッポ(アイス) 準速 50
16 70.0 コオリッポ(アイス) 無振 50
17 49.0 コオリッポ(アイス) 最遅 50

このとき、descオプションをつけると、各列のデータの統計量(stat)を確認できる。(->詳細な仕様)

  • count …行数
  • unique…重複していない(一意な)要素の個数
  • top …最頻値
  • freq …最頻値の出現回数
  • mean …平均値
  • min …最小値
  • max …最大値
Query.exe("?showS コオリッポ desc")
stat a_s name set1 set2 s
0 count 18.000000 18 18 18 18.00000
1 unique NaN 2 4 3 NaN
2 top NaN コオリッポ(ナイス) 最速 NaN
3 freq NaN 9 6 8 NaN
4 mean 195.111111 NaN NaN NaN 90.00000
5 std 98.618033 NaN NaN NaN 41.15966
6 min 49.000000 NaN NaN NaN 50.00000
7 25% 125.750000 NaN NaN NaN 50.00000
8 50% 175.000000 NaN NaN NaN 90.00000
9 75% 260.750000 NaN NaN NaN 130.00000
10 max 400.000000 NaN NaN NaN 130.00000
groupby オプション (データを集約する)

コオリッポのS実数値データを、(name,set2) に関して集約を行いたいとする。

こうすることで、set1=[ 最遅, 無振, 準速, 最速 ] のデータを1行にまとめることができる。

内部的にはpandasのgroupbyメソッドが呼び出されている。

Query.exe("?showS コオリッポ groupby name,set2 a_s:max;min,set1:count")
name set2 a_s.max a_s.min set1.count
0 コオリッポ(アイス) 112.0 49.0 4
1 コオリッポ(アイス) +1 168.0 153.0 2
2 コオリッポ(アイス) こうそくいどう 224.0 140.0 3
3 コオリッポ(ナイス) 200.0 121.0 4
4 コオリッポ(ナイス) +1 300.0 273.0 2
5 コオリッポ(ナイス) こうそくいどう 400.0 300.0 3

2つの表を1つの表に結合するもの

and, or オプション (ポケモン名で結合)

こちらはすでに解説したので省略。

内部的にはpandasのmergeメソッド が呼び出されている。

inner, outer, left, right オプション (任意の列で結合)

こちらもpandasのmergeメソッド が呼び出されている。

innerの構文は?cmd1 inner 結合列名 ?cmd2

outer, left, rightも構文は同じ。

例えば、S種族値が140以上のポケモン種族値を表示したいとする。

andオプションの場合は、ポケモン名(name)の列が結合キーとなる。

そのため、sは結合キーとみなされず、接尾辞(suffix)がつく。

Query.exe("?minS 140 ?st *")
s_x name h a b c d s_y sum
0 200 レジエレキ 80 100 50 100 50 200 580
1 150 マルマイン 60 50 70 80 80 150 490
2 150 マルマイン(ヒスイ) 60 50 70 80 80 150 490
3 150 バドレックス(こくば) 100 85 80 165 100 150 680
4 148 ザシアン(王) 92 150 115 80 115 148 700
5 142 ドラパルト 88 120 75 100 75 142 600

innerを使って、(s,name)を結合キーとすると、以下のように出力される。

Query.exe("?minS 140 inner s,name ?st *")
s name h a b c d sum
0 200 レジエレキ 80 100 50 100 50 580
1 150 マルマイン 60 50 70 80 80 490
2 150 マルマイン(ヒスイ) 60 50 70 80 80 490
3 150 バドレックス(こくば) 100 85 80 165 100 680
4 148 ザシアン(王) 92 150 115 80 115 700
5 142 ドラパルト 88 120 75 100 75 600
diff オプション(表の差分を表示する)

アンコールとちょうはつという2つの技に関して

の3種類がある。

単純にorを使うよりも、diffを使えば、わかりやすく表示できる。

diff nameと入力すると、_mergeの列に、left_only, right_only, both が表示されていることがわかる。

Query.exe("?sev アンコール diff name ?sev ちょうはつ")
name _merge
0 パモット left_only
1 ミジュマル both
2 ゴマゾウ left_only
3 フタチマル both
4 バドレックス left_only
... ... ...
264 サンダー(ガラル) right_only
265 マルマイン(ヒスイ) right_only
266 マルマイン right_only
267 ビリリダマ(ヒスイ) right_only
268 ビリリダマ right_only

269 rows × 2 columns

さらに、whereオプションを使えば、3種類のデータを柔軟に取り出せる。

例えば、_merge=='right_only'とすれば、ちょうはつは覚えるが、アンコールは覚えないポケモンが表示される。

Query.exe("?sev アンコール diff name ?sev ちょうはつ `_merge=='right_only'`")
name _merge
0 イダイナキバ right_only
1 ヤトウモリ right_only
2 ケロマツ right_only
3 サナギラス right_only
4 テツノイサハ right_only
... ... ...
162 サンダー(ガラル) right_only
163 マルマイン(ヒスイ) right_only
164 マルマイン right_only
165 ビリリダマ(ヒスイ) right_only
166 ビリリダマ right_only

167 rows × 2 columns

出力を変更するもの

show オプション (発行したSQL文やオプションを表示する)

showを入力すると、コマンドがどういう風に実行されているかがわかる。デバッグ用の機能。

? コマンド名[
コマンドに対応するSQL文]
オプション (オプションの説明や引数など)
...
show
Query.exe("?minS 90 ?sev ちょうはつ show")
?minS[
SELECT pokedex.s,pokedex.name FROM pokedex WHERE pokedex.s>='90' AND pokedex.insv ORDER BY pokedex.s DESC]
and (inner join on name)
?sev[
SELECT pokedex.name,move.move FROM pokedex,poke2move,move WHERE move.move='ちょうはつ' AND poke2move.moveid = move.id AND pokedex.id = poke2move.id]
show
s name
0 150 マルマイン
1 150 バドレックス(こくば)
2 150 マルマイン(ヒスイ)
3 136 テツノツツミ
4 135 ハバタクカミ
... ... ...
84 90 ニャース
85 90 カラミンゴ
86 90 ファイヤー(ガラル)
87 90 ヤルキモノ
88 90 ザングース

89 rows × 2 columns

out オプション (Google Spread Sheetに出力する)

outオプションを使うことで、今までDiscordに出力されていた表をGoogle Spread Sheetで編集・閲覧できるようになる。

Query.exe("?minS 90 ?sev ちょうはつ out")

(Spread SheetのURLが送信される)

plot オプション (グラフをプロットする)

plotオプションでグラフを描画できる。あまり実用性がない

内部的にはpandasのplotメソッドが呼び出されている。

まずteras1 ポケモン名でそのポケモンのシングルでのテラスタル使用率を表示。

Query.exe("?teras1 ハバタクカミ")
name rank terastype raito
0 ハバタクカミ 1 フェアリー 43.6
1 ハバタクカミ 2 みず 29.8
2 ハバタクカミ 3 じめん 9.0
3 ハバタクカミ 4 ノーマル 5.1
4 ハバタクカミ 5 ゴースト 4.9
5 ハバタクカミ 6 ほのお 4.9
6 ハバタクカミ 7 くさ 0.8
7 ハバタクカミ 8 はがね 0.8
8 ハバタクカミ 9 かくとう 0.4
9 ハバタクカミ 10 でんき 0.4

採用率が4%以上のテラスタイプ毎の円グラフはこんな感じ。

Query.exe(
    """
    ?teras1 ハバタクカミ `raito>=4` 
    plot kind:pie,x:terastype,y:raito,title:ハバタクカミのテラスタイプ使用率
    """)
params args
0 kind pie
1 figsize [4, 3]
2 x terastype
3 y raito
4 title ハバタクカミのテラスタイプ使用率
5 startangle 90
6 counterclock False
7 labels [フェアリー, みず, じめん, ノーマル, ゴースト, ほのお]
8 autopct %1.1f%%

棒グラフはこんな感じ。

Query.exe(
    """
    ?single * `rank<=5` ?st * 
    plot kind:bar,stacked:True,x:name,y:h;a;b;c;d;s
    """)
params args
0 kind bar
1 figsize [4, 3]
2 x name
3 y [h, a, b, c, d, s]
4 stacked True

シングルにおけるS実数値の最大値のグラフ。

Query.exe(
    """
    ?single * ?showS * groupby rank,name a_s:min;mean;max `rank<=50`
    plot kind:bar,x:name,y:a_s.max,figsize:16;9
    """)
params args
0 kind bar
1 figsize [16, 9]
2 x name
3 y a_s.max

以上!説明終わり!

データベースの定義について[DiscordBot]

RDBMSにはPostgreSQLを使っています。

テーブル名の後ろの()の中身は、主キーを表しています。

ポケモンのデータ

pokedex(pokedex.id) ポケモンの基本データ

図鑑番号(No.)はリザードンメガリザードンX(No.=6)のように重複することがあるため、

id=No.+連番浮動小数で表現することにする。

例:

No. id name
6 6.1 リザードン
6 6.2 メガリザードンX
6 6.3 メガリザードンY
create table if not exists pokedex(
    id real primary key, --ポケモンID(id=no.form=図鑑番号.フォルム)
    no int not null, --図鑑番号
    name varchar(32) not null unique, --ポケモン名
    eng varchar(32) not null, --ポケモンの英語名
    type1 varchar(8) not null, --タイプ1(=type.eng)
    type2 varchar(8) default 'なし', --タイプ2
    abl1 varchar(32) not null, --通常特性1(=ability.abl)
    abl2 varchar(32) default 'なし', --通常特性2
    abl3 varchar(32) default 'なし', --隠れ特性
    H int not null,
    A int not null,
    B int not null,
    C int not null,
    D int not null,
    S int not null,
    sum int not null, --合計種族値
    icon text, --アイコンのURL
    height real not null, --高さ
    weight real not null, --重さ
    lowkick_power int not null, --けたぐりの威力
        stage int not null, --進化段階 1:未進化 2:中間進化 3:最終進化
    legend boolean not null, --1:伝説・幻 0:それ以外
        insv boolean not null --1:SVに存在する 0:しない
);

poke2chem ポケモンごとの弱点・耐性

※このテーブルはidごとのパーティションテーブル

パーティションテーブル名は、id=4.1のヒトカゲなら、chem4_1

--各ポケモンが攻撃されるときの耐性値の一覧
create table if not exists poke2chem(
    id real, --攻撃されるポケモンのID(=pokedex.id)
    attr varchar(8) not null, --攻撃技のタイプ
    chem real not null --攻撃されるポケモンのタイプ補正値(特性非考慮)
)partition by list(id);

lang 各言語のポケモンに関する単語の一覧

create table if not exists lang(
    class int not null, --0:ポケモン,1:技,2:特性,3:持ち物,4:タイプ,5:性格
    jpn varchar(32) not null, --日本語
    eng varchar(32) not null, --英語
    ger varchar(32) not null, --ドイツ語
    fra varchar(32) not null, --フランス語
    kor varchar(32) not null, --歓呼国語
    cs varchar(32) not null,--簡体字
    ct varchar(32) not null--繁体字
)partition by list(class);

move(move.id) 技の一覧

create table if not exists move(
    id int primary key, --技ID
    move varchar(32) unique, --技名
    attr varchar(8) not null, --技タイプ
    class varchar(2) not null, --技の分類(物理,特殊,変化,不定)
    power int not null, --威力
    accuracy int not null, --命中
    PP int not null, --PP
    direct boolean not null, --接触技かどうか
    protect boolean not null, --守るを貫通するかどうか
    target varchar(8) not null, --技の対象
    text text not null --技の説明
);

poke2move ポケモンごとの覚える技

※poke2moveのidはsvに存在しているポケモンのidのみを参照している

※このテーブルはidごとのパーティションテーブル

パーティションテーブル名は、id=4.1のヒトカゲなら、move4_1

create table if not exists poke2move(
id real, --ポケモンID(=pokedex.id)
moveid int not null --技ID(=move.id)
)partition by list(id);

type(type.eng) タイプの一覧

create table if not exists type(
    eng varchar(8) primary key,--タイプの英語名
    name varchar(8) not null, --タイプの日本語名
    name2 varchar(1) not null --タイプの単漢字名
);

ability(ability.abl)

※ability.idは全然使っていない…

create table if not exists ability(
    id int primary key, --特性ID
    abl varchar(16) not null, --特性名
    text text not null --説明
);

各種補正

cor_nature(cor_nature.nature) 性格補正表

create table if not exists cor_nature(
    nature varchar(8) primary key, --性格名
    a real not null, --A補正値
    b real not null, --以下略
    c real not null,
    d real not null,
    s real not null
);

cor_type_atk(attr=move.attr) タイプ相性表(攻)

--攻撃するときのタイプ補正値の一覧
create table if not exists cor_type_atk(
    attr varchar(8) not null, --攻撃技のタイプ
    eng varchar(8) not null,--タイプの英語名
    cor real not null --攻撃するときの補正値
);

cor_type_def(type=type.eng) タイプ相性表(防)

--攻撃されるときのタイプ補正値の一覧
create table if not exists cor_type_def(
    eng varchar(8) not null, --攻撃されるポケモンのタイプ
    attr varchar(8) not null,--技タイプの英語名
    cor real not null --攻撃されるときの補正値

cor_abl_atk(name=ability.abl) 特性補正表(攻)

--攻撃するときにタイプ補正がかかる特性の一覧
create table if not exists cor_abl_atk(
    abl varchar(16) not null, --攻撃するときにタイプ補正がかかる特性
    attr varchar(8) not null,--技タイプの英語名
    cor real not null --攻撃するときの補正値
);

cor_ability_def(name=ability.name) 特性補正表(防)

--攻撃されるときにタイプ補正がかかる特性の一覧
create table if not exists cor_abl_def(
    abl varchar(16) not null, --攻撃されるときにタイプ補正がかかる特性
    attr varchar(8) not null,--技タイプの英語名
    cor real not null --攻撃されるときの補正値
);

 

その他のデータベース

sqlcmd(name) 「?」から始まるSQLコマンドを管理するテーブル

※discordからも操作できるようにINSERT権限を与える

--「?」から始まるSQLコマンドに関する情報の一覧
create table if not exists sqlcmd(
    id int default 0, --各コマンドの表示優先順位
    name varchar(16) primary key, --コマンド名
    cmd text not null, --コマンドに対応するSQL文
    text text default '未編集', --コマンドの説明
    i1 varchar(8) default '-', --引数1
    i2 varchar(8) default '-', --引数1
    i3 varchar(8) default '-', --引数1
    o1 varchar(8) default '未編集', --出力1
    o2 varchar(8) default '-', --出力2
    o3 varchar(8) default '-' --出力3
);

【!calciv・!calcev】実数値から個体値・努力値を求める [DiscordBot]

!calciv (個体値表示)

!calciv ポケモン名[*] レベル H A B C D S
!calciv ポケモン名[*] レベル 個体値を調べたい場所(HABCDS) 対応する箇所の実数値

実数値から個体値を計算します。

性格の補正については、上昇補正/下降補正がついている場合は実数値の後に+/-を付けてください。

個体値を調べたい場所を入力しなかった場合は、全ての場所の個体値を求めます。

引数はHABCDSの順で全ての数値を入力してください。

個体値を調べたい場所を入力した場合は、その場所の個体値のみを求めます。

使用例

実数値から個体値を計算するプログラム

概要

実数値(観測値)から個体値を計算するには、!stのときに作ったbst2ast_ij関数を最利用して、

個体値が0~31のときの実数値(理論値)を求め、観測値と一致するような個体値の最小値・最大値を返せばいい。

今回も、H,A,B,C,D,Sをj=1,2,3,4,5,6に対応させて、

j番目の観測値をobst_j,個体値がiのときの理論値をast_ijとする。

l2s=lambda s,l:s.join(map(str,l))
cplist=lambda org:[org,org,org,org,org,org]
#種族値をj番目の実数値に変換
def bst2ast_ij(j,bst,lv,iv,ev,ncor):
    f=lambda bst,iv,ev,lv:int(int(bst*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])
    return ast_j

#種族値を実数値リストに変換
def bst2ast(bst,lv,iv,ev,ncor):
    ast_i=[]
    for j in range(6):
        ast_j=bst2ast_ij(j,bst,lv,iv,ev,ncor)
        ast_i.append(ast_j)
    return ast_i

#グラードンの種族値リスト
bst=[100,150,140,100,90,90] 
#デフォルトのLv
lv=50 
 #個体値・努力値・性格補正
b2a_i=[cplist(31),cplist(252),cplist(1.1)]
ast_i=bst2ast(bst,lv,*b2a_i)
print(ast_i)

#出力:[207, 222, 211, 167, 156, 156]
[207, 222, 211, 167, 156, 156]

そのためには、bst2ast_ijの引数である、

種族値努力値・レベル・性格補正、すなわち bst, ev, lv, ncor を決定しなければならない。

  1. bst…与えられた名前をDBを検索して種族値を取得
  2. ev…捕獲直後を想定するため、すべて0を仮定
  3. lv…与えられる。
  4. ncor…与えられる。(実数値の後ろに+,-をつけて表現する。デフォルト=[1,1,1,1,1,1])

入力形式について

!calcivは引数の入力を省略できるので、両方の入力形式に対応する必要がある。

def calciv(name,lv,*args):
    print(name,lv,args)

#省略しない場合
calciv("グラードン",70,"240","236","240+","160","144","117-")

#省略する場合
calciv("グラードン",70,'A','S',"236","117-")
グラードン 70 ('240', '236', '240+', '160', '144', '117-')
グラードン 70 ('A', 'S', '236', '117-')

省略する場合を考えると、次のようなプログラムでどのステータスの個体値を調べたいのかを判定できる。

省略されなかった場合は、すべてFalseになる。

しかし、それだとどこも調べてくれないので、例外的に処理する必要がある。

args=list(('A','S',"236","117-"))

#個体値を調べたいステータスにフラグを立てる
search_st=[st in args for st in ['H','A','B','C','D','S']]
print(search_st)

#Trueの出現回数
print(search_st.count(True))
[False, True, False, False, False, True]
2

argsの "A", "S" の要素はもう必要ないので削除。

del args[:search_st.count(1)]
print(args)
['236', '117-']

次に、args=('236', '117-') について考える。

文字列に、'+', '-' が含まれているかどうかを判定する。

そのために、reモジュールをimportして、特定の文字列があるかどうかを判定する。

ついでに、'+', '-' を除去して、数値型に変換する。

import re

obst=[]
ncor_flag=[]

for arg in args:
    #上昇補正
    if re.search(r'\+',arg):
        arg=arg.strip('+')
        ncor_flag.append(1.1)
    #下降補正
    if re.search(r'\-',arg):
        arg=arg.strip('-')
        ncor_flag.append(0.9)
    #無補正
    else:
        ncor_flag.append(1)
    obst.append(int(arg))

print(obst)
print(ncor_flag)
[236, 117]
[1, 0.9]

これで、bst2ast_ijが使えるようになった。

#arg='117-'をobst_j=117, ncor_j=0.9に分離する関数
def args2ast_j(arg):
    #上昇補正
    if re.search(r'\+',arg):
        ast_j=arg.strip('+')
        ncor_j=1.1
    #下降補正
    elif re.search(r'\-',arg):
        ast_j=arg.strip('-')
        ncor_j=0.9
    #無補正
    else:
        ast_j=arg
        ncor_j=1
    return int(ast_j),ncor_j

def calciv(name,lv,*args):
    args=list(args)
    #個体値を調べたい場所にフラグを立てる
    search_st=[st in args for st in ['H','A','B','C','D','S']]
    del args[:search_st.count(1)]
        
    iter_args=iter(args)
    #観測したステータス
    obst=[]
    ncor=[]
    for j in range(6):
        #フラグがあるか、ひとつもない場合
        if search_st[j] or search_st.count(True)==0:
            arg=next(iter_args)
            obst_j,ncor_j=args2ast_j(arg)
            obst.append(obst_j)
            ncor.append(ncor_j)
        #フラグがない場合
        else:
            obst.append(0)
            ncor.append(1)

    bst=(100, 150, 140, 100, 90, 90)
    lv=lv
    ev=cplist(0)
    i_maxmindict={}
    i_matchdict={}
    #H~Sでループ
    for j in range(6):
        if search_st[j] or search_st.count(True)==0:
            i_matchdict[j]=[]
            #個体値0~31でループ
            for i in range(32):
                iv=cplist(i)
                #実数値が一致したときの個体値
                if obst[j]==bst2ast_ij(j,bst,lv,iv,ev,ncor):
                    i_matchdict[j].append(i)
            if len(i_matchdict[j])==0:
                pass
            elif len(i_matchdict[j])==1:
                i_maxmindict[j]=i_matchdict[j]
            else:
                i_maxmindict[j]=[min(i_matchdict[j]),max(i_matchdict[j])]

    keys=['H','A','B','C','D','S']
    for item in i_maxmindict.items():
        print(f"{keys[item[0]]}:{l2s('~',item[1])}")
#省略しない場合
calciv("グラードン*",69,"240","236","240+","160","144","117-")
B:31
C:25~26
D:22
S:2~4
#省略する場合
calciv("グラードン",70,'A','S',"236","117-")
A:30~31
S:0~1

これでほとんど完成!

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

DBの検索には、!stのときのPostgreSQLから種族値データを取得する関数を再利用する

import psycopg2,os,re
import numpy as np
from dotenv import load_dotenv

DATABASE_URL=os.environ["DATABASE_URL"]
conn=psycopg2.connect(DATABASE_URL)
l2s=lambda s,l:s.join(map(str,l))
#種族値データを取得
def fetch_bst(cur,name):
    #アイコンを生成
    cur.execute(
        f"select icon from pokedex where name='{name}'"
        )
    icon=cur.fetchone()[0]
    #種族値を検索
    cur.execute(
        f"select h,a,b,c,d,s from pokedex where name ='{name}'"
        )
    bst=cur.fetchone()
    return icon,bst

#初めに呼び出される関数
def get_bst(name):
    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}%' order by id
                      """
                    )
                #ヒットしたすべての結果:fnames
                fnames=np.array(cur.fetchall()).flatten()
                for fname in fnames:
                    icon,bst=fetch_bst(cur,fname)
                    print(bst)
            #あいまい検索でない場合
            else:
                icon,bst=fetch_bst(cur,name)
                print(bst)

#'*'なし、普通の検索
get_bst("グラードン")
(100, 150, 140, 100, 90, 90)

↑これでグラードン種族値が取得できた。

!calcev(努力値計算)

!calcev ポケモン名[*] レベル H A B C D S
!calcev ポケモン名[*] レベル 努値を調べたい場所(HABCDS) 対応する箇所の実数値

個体値を31と仮定して、実数値から努力値を計算します。

性格の補正については、上昇補正/下降補正がついている場合は実数値の後に+/-を付けてください。

努力値を調べたい場所を入力しなかった場合は、全ての場所の努力値を求めます。

努力値を調べたい場所を入力した場合は、その場所のみを求めます。

使用例

個体値が31でない場合は、努力値を0と仮定して、個: [個体値] の形式で出力される

実数値から努力値を計算するプログラム

概要

実は、calcivの条件をほんの少し変えるだけで作れる。

個体値 努力値
calciv 不明 0と仮定
calcev 31と仮定 不明
#calcev
name="グラードン"
lv=100
iv=cplist(31)
ast=[341,399,316,200,216,306]
ncor=[1,1,1,0.9,1,1.1]
i_maxmindict={}
i_matchdict={}
#H~Sでループ
for j in range(6):
    i_matchdict[j]=[]
    #努力値0~252でループ(0,4,12,20...)
    for i in range(64):
        ev_i=4*i
        ev=cplist(ev_i)
        #実数値が一致したときの努力値
        if ast[j]==bst2ast_ij(j,bst,lv,iv,ev,ncor):
            i_matchdict[j].append(ev_i)
    if len(i_matchdict[j])==0:
        pass
    elif len(i_matchdict[j])==1:
        i_maxmindict[j]=i_matchdict[j]
    else:
        i_maxmindict[j]=[min(i_matchdict[j]),max(i_matchdict[j])]

keys=['H','A','B','C','D','S']
for item in i_maxmindict.items():
    print(f"{keys[item[0]]}:{l2s('~',item[1])}")
H:0
A:252
B:0
D:0
S:252

個体値が31より低い場合

個体値が31より低い場合は、努力値を0と仮定し、calciv_jを呼び出して、個体値を求めることにする。

例:Lv100 グラードン 陽気 の特攻が200だったとき、とりうる個体値

def calciv_j(ast_j,j,bst,lv,ncor):
    ev=cplist(0)
    #個体値0~31でループ
    i_matchdict_j=[]
    for i in range(32):
        iv=cplist(i)
        #実数値が一致したときの個体値
        if ast_j==bst2ast_ij(j,bst,lv,iv,ev,ncor):
            i_matchdict_j.append(i)
    if len(i_matchdict_j)==0:
        pass
    elif len(i_matchdict_j)==1:
        i_maxmin_j=str(i_matchdict_j[0])
    else:
        i_maxmin_j=f"{min(i_matchdict_j)}~{max(i_matchdict_j)}"
    print(i_maxmin_j)

name="グラードン"
ast_j=200
j=3
bst=[100,150,140,100,90,90]
lv=100
ncor=[1,1,1,0.9,1,1.1]
print("C個体値")
calciv_j(ast_j,j,bst,lv,ncor)
C個体値
18

あとはプログラムをdiscord.pyの形式に修正し、出力形式をembedにすればOK!

【!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

これで完成!