この記事の概要
任天堂の大人気ゲーム「ポケットモンスター」を最適化問題として定式化し、それをOpenJijで解いてみようという趣旨の記事です。
序章(先行研究)
世界初の商用量子アニーラーを開発したD-Wave。その会社がウェビナーで以下のような講演を行いました。
QUBO, I Choose You! Building an Optimal Pokemon Team with a Quantum Computer
6匹のポケモンを相手ポケモンのタイプ・技タイプに合わせて選出します。どのように選べば相手トレーナーに勝つことができるか、を最適化計算したという内容です。
上の動画では6匹のメンバーの中に「相手ポケモンの攻撃に対する弱点タイプは2匹まで」「相手ポケモンの攻撃に強いタイプは1匹まで」という制約を課しており、現実的なポケモンバトルとは言えません。
そこでこの記事を第一弾として、リアルなポケモンバトルを最適化するための数理モデル・QUBO定式化を考えていきましょう。最後にスクリプトを作成しその数値実験結果を考察する、ということを目標に掲げていきたいと思います。
逃げるんじゃねぇ!サシで勝負だ!
第一弾は簡単のため、自分と相手の手持ちポケモンはそれぞれ1匹ずつと仮定します。またそれらのポケモンは技を4つ持つとします。
定式化
バイナリ変数
自分のポケモンがタイプのとき、そうでないときとなるようなバイナリ変数を考えます。
自分のポケモンが持つ番目の技がタイプのとき、そうでないときとなるようなバイナリ変数を考えます。
さらに固定変数として、相手のポケモンがタイプのとき、そうでないときとなるようなバイナリ変数を用意します。
そしてこちらも固定変数として、相手ポケモンが持つ番目の技がタイプのとき、そうでないときとなるようなバイナリ変数を用意します。
これらのバイナリ変数を用いて、制約と目的関数をQUBO表現することを考えましょう。
制約1: 自分のポケモンのタイプは1つか2つ
ポケモンはそれぞれがタイプを持ちます。しかしそのタイプは1つか多くても2つまで、というのが現行のポケモンのルールです。
これをQUBOで表現すると以下のようになります。
制約2: 技のタイプは1つのみ
ポケモンが持つ技にもタイプがありますが、こちらは現行のポケモンでは1つしか許されていません。
これをQUBOで表現すると以下のようになります。
目的関数: 与えるダメージ-くらうダメージを最大にする
簡単のため、今回は
これを最大にすることを考えましょう。ポケモンのタイプに対する相性表は以下のようになります(行が攻撃技のタイプ、列が防御側ポケモンのタイプです)。
ノーマル | ほのお | みず | でんき | くさ | こおり | かくとう | どく | じめん | ひこう | エスパー | むし | いわ | ゴースト | ドラゴン | あく | はがね | フェアリー | |
ノーマル | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 0.0 | 1.0 | 1.0 | 0.5 | 1.0 |
ほのお | 1.0 | 0.5 | 0.5 | 1.0 | 2.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 0.5 | 1.0 | 0.5 | 1.0 | 2.0 | 1.0 |
みず | 1.0 | 2.0 | 0.5 | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 |
でんき | 1.0 | 1.0 | 2.0 | 0.5 | 0.5 | 1.0 | 1.0 | 1.0 | 0.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 |
くさ | 1.0 | 0.5 | 2.0 | 1.0 | 0.5 | 1.0 | 1.0 | 0.5 | 2.0 | 0.5 | 1.0 | 0.5 | 2.0 | 1.0 | 0.5 | 1.0 | 0.5 | 1.0 |
こおり | 1.0 | 0.5 | 0.5 | 1.0 | 2.0 | 0.5 | 1.0 | 1.0 | 2.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 1.0 |
かくとう | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 1.0 | 0.5 | 0.5 | 0.5 | 2.0 | 0.0 | 1.0 | 2.0 | 2.0 | 0.5 |
どく | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 1.0 | 0.5 | 0.5 | 1.0 | 1.0 | 1.0 | 0.5 | 0.5 | 1.0 | 1.0 | 0.0 | 2.0 |
じめん | 1.0 | 2.0 | 1.0 | 2.0 | 0.5 | 1.0 | 1.0 | 2.0 | 1.0 | 0.0 | 1.0 | 0.5 | 2.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 |
ひこう | 1.0 | 1.0 | 1.0 | 0.5 | 2.0 | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 0.5 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 |
エスパー | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 2.0 | 1.0 | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 0.0 | 0.5 | 1.0 |
むし | 1.0 | 0.5 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 0.5 | 1.0 | 0.5 | 2.0 | 1.0 | 1.0 | 0.5 | 1.0 | 2.0 | 0.5 | 0.5 |
いわ | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 2.0 | 0.5 | 1.0 | 0.5 | 2.0 | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 |
ゴースト | 0.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 1.0 | 1.0 |
ドラゴン | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 0.0 |
あく | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 1.0 | 2.0 | 1.0 | 0.5 | 1.0 | 0.5 |
はがね | 1.0 | 0.5 | 0.5 | 0.5 | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 1.0 | 1.0 | 1.0 | 0.5 | 2.0 |
フェアリー | 1.0 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 0.5 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 2.0 | 2.0 | 0.5 | 1.0 |
この行列をのように書くことにしましょう。すると
と書くことができます。最大化したいものから、目的関数は
のようになります。
全ハミルトニアン
以上の(1), (2), (3)より、求めたい問題のハミルトニアンは、未定乗数を用いて
となります。
Pythonスクリプト
GitHubにリポジトリを作成しました。ぜひクローンして実行してみてください。
Pokemon_optgithub.com
最適化結果
例1: 相手ポケモンとその技が単一タイプ
例として、相手のポケモンが「ほのお」タイプで4つの技も全て「ほのお」タイプという簡単な場合を最適化してみましょう。結果は以下のようになります。
{}
**********
じぶんのポケモン: みず
わざ0: じめん
わざ1: じめん
わざ2: じめん
わざ3: みず
**********
あいてのポケモン: ほのお
わざ0: ほのお
わざ1: ほのお
わざ2: ほのお
わざ3: ほのお
最初の空の辞書は、制約が満たされていることを示しています(制約が破れている場合には何らかの要素を持つ辞書が表示されます)。
相手ポケモンが「ほのお」タイプなので、それに2倍のダメージを与えることができる「じめん」「みず」が選択されていることがわかります。また相手ポケモンの技が全て「ほのお」タイプなので、その技のダメージが0.5倍となる「みず」タイプポケモンが自分のポケモンとして選ばれています。
例2: 相手ポケモンのタイプと技タイプが一致しない場合
もう少し複雑なインスタンスを用意します。今度は「ほのお」タイプのポケモンが、なぜか「みず」「くさ」の技を使えるとした場合、どうなるでしょうか。
{}
**********
じぶんのポケモン: ドラゴン
わざ0: じめん
わざ1: いわ
わざ2: じめん
わざ3: みず
**********
あいてのポケモン: ほのお
わざ0: ほのお
わざ1: くさ
わざ2: くさ
わざ3: みず
相手ポケモンが「ほのお」タイプであることは変わりないので、それに2倍ダメージを与える「じめん」「いわ」「みず」の技が選択されています。そして自分のポケモンのタイプは、相手ポケモンの「ほのお」「みず」「くさ」のダメージが0.5倍となる「ドラゴン」タイプになっています。
相手が上の結果を踏まえて、「ドラゴン」タイプに対する対策をしてくるとどうでしょうか。試しに、相手ポケモンのわざ2を「フェアリー」に変えて実験してみましょう。
{}
**********
じぶんのポケモン: どく
わざ0: みず
わざ1: じめん
わざ2: いわ
わざ3: みず
**********
あいてのポケモン: ほのお
わざ0: ほのお
わざ1: くさ
わざ2: フェアリー
わざ3: みず
今度は、「くさ」「フェアリー」のダメージが0.5倍となる「どく」タイプが自分のポケモンタイプになりました。
考察: これは現実的な1vs1のポケモンバトルとなっているか?
今回の定式化では、ポケモンが使える技タイプに対して制約を施していません。すなわち、この解法では「ほのお」タイプのヒトカゲが「みず」タイプのみずでっぽうを使えるような解も出現します。実際にはポケモンタイプと技タイプに相関が存在します。このように変数に相関がない簡単な最適化問題は貪欲法で解けてしまいます。しかし、現実のポケモンバトルは、貪欲法では最適解を導けないような複雑な問題設定(今後の課題を参照)が多数存在します。よって現実のポケモンバトルの最適解を導くためには
- より詳細な問題の定式化
- それを解くためにOpenJijで採用されているのようなアニーリングアルゴリズム
が必要になるでしょう。
今後の課題
ポケモンバトルは1ターンの間に自分と相手のポケモンが1回ずつ技を使います。このようなターン経過を最適化問題に組み込むことで、どちらのポケモンが勝つかを明確にすることができます。
ダメージ計算にも様々あります。あるポケモンのタイプと技のタイプが同じ属性のときにはダメージが1.5倍になります。またポケモンの攻撃が急所にあたった場合にもダメージが1.5倍です。さらに技によってはバトルフィールドの天候を変え、ポケモンの技の威力を上下させるものがあります。このような効果を入れて計算を行うことで、より現実みのあるダメージ量の最適化が行えるでしょう。
よくある対戦ルールとして、ポケモンバトルは3対3で行われます。相手ポケモンの3匹が持つ技タイプを考慮しての最適化が必要となるでしょう。さらに複数のポケモンを所持している場合には、闘うポケモンの入れ替えを行うことができます。
このようにポケモンには細かなルールがあるため、これらを追加してより現実のポケモンバトルに近いものを最適化できればと考えています。