【ポケモンSV】自動ダメージ計算サイトの仕組みと高速化について

はじめに

ポケモンSV向けに自動ダメージ計算サイトを作りました。

キャプチャーボードを接続したPCからアクセスすると、ゲーム画面を勝手に解析してダメージ計算してくれます。

pbasv.cloudfree.jp

1年前くらいにPythonで同じようなアプリを作ったのですが、スクリプトや実行ファイルは配布に不向きでメンテナンスも面倒だったため、ウェブに移植したいと考えていました。

ただ、ウェブ開発の経験がなかったこともあり、なかなか着手できず、昨年末に参考書を購入してようやく作り始めました。

Pythonアプリの下地があったため、1ヶ月あればできるだろうと舐めてかかった結果、土日をほぼすべて費やした上で3ヶ月もかかってしまいました...

せっかくなので、アプリの仕組みや工夫した点について記事にしようと思います。

ユーザーや、これからアプリを開発をしたい方にとって参考になれば幸いです。

コンセプト

自動ダメージ計算アプリ

私がポケモン対戦をまともにやったのはXY以来で、SVもかなりのライトユーザーです。

移り変わる環境について行くのは大変で、特にダメージ感覚がまったくありません。

とはいえ、対戦中にダメージ計算を叩くのも面倒なので、画像解析して自動化しようと考えました。

対戦中にアプリを使うこと自体が負担にならないよう、以下の点に注意して設計しました。

  • キャプチャ映像を表示する (ゲーム画面とアプリ画面を往復しなくてよい)
  • キャプチャ映像の上にモノを置かない (背景が動くと目が疲れる)
  • 自分と相手のパーティを常に表示する (対戦中に確認する手間を減らす、死に出し時に確認できる)
  • ダメージ計算と致死率は数字で明確化する。情報量は色濃度と表示桁数で調整
  • 相手の持ち物・テラスといった未知の情報は、数を限定して表示する (情報過多にしない)
  • 種族値などの慣れてくると分かる情報は、カーソルを合わせた時に注釈として表示する (情報過多にしない)

自動ダメージ計算アプリ

ポケモン管理アプリ

ポケモンを一から育成する場合、仮想敵を考えてステータスを調整することが多いです。

そのため、育成情報と仮想敵とのダメージ計算は常にセットで管理したいと考えました。

前述の自動ダメージ計算アプリと併用する前提のため、PCの大画面で効率よく管理できるように設計しました。

  • 育成情報とダメージ計算を同時に表示する
  • 複数のダメージ計算を並列に表示する
  • 耐久調整 (◯◯耐え) の自動化
  • 最低限のダメージ加算機能

ポケモン管理アプリ

使用言語

一部のバックエンドを除き、アプリの大部分は Javascript で実装しました。

したがって、ダメージ計算などの処理はほぼブラウザで処理されています。

標準ライブラリのほかに、以下の外部ライブラリを利用しました。

  • Tesseract.js : 文字認識
  • OpenCV.js : テンプレートマッチング
  • guessLanguage.js : 言語識別

文字認識(OCR)とは、文字通り画像から文字を読み取る機能です。

対戦中の場のポケモンを認識するときなど、多くの場面で利用しています。

残念ながら一度の読み取りに約0.1秒と時間がかかるため、多用するとアプリの使い勝手が悪くなってしまいます。

テンプレートマッチングとは、二つの画像の類似度や類似箇所を判定する機能です。

選出画面での相手パーティの認識や、試合場面の識別などに利用しています。

文字認識と比べて高速ですが、比較対象となるテンプレート画像を持っておく必要があります。

このアプリでは、相手パーティを認識するために全ポケモンの立ち絵をあらかじめ用意しています。

言語識別については、場のポケモンを認識する際に文字認識と組み合わせて使用しています。

ダメージ計算

ダメージ計算には自作したライブラリを使用しています。

図鑑や技の情報はあらかじめ外部ファイルに集約しておき、起動時に読み込んでいます。

計算式はポケモンWikiを参照しました。

ダメージ計算式 - ポケモン対戦考察まとめWiki|最新世代(スカーレット・バイオレット)

また、検算にはポケモンソルジャー様のダメージ計算サイトを使用しました。

ダメージ計算ツールSV byポケソル

ダメージ計算の高速化

ダメージには乱数があり、85%, 86%, ..., 100%の16通りの中からランダムに選ばれます。

ダメージによる致死率を正確に求めるためには、すべてのパターンを網羅しなければいけません。

1発だけなら16通りで済みますが、例えばスケショ5発のダメージを計算すると16の5乗 (約100万) 通りに分岐します。

分岐が増えるに従って計算時間も増加します。

それでも5発程度であれば計算可能ですが、スケショ+神速といった加算計算や、スライドバーでステータスを変えながら繰り返し計算する場合、この計算量はもはや現実的ではありません。

開発途中で確認したところ、6発計算 (16の6乗) に0.5秒かかり、7発計算しようとするとブラウザが停止しました。

これの対策として、実際には16通りの中に重複するダメージが含まれることを利用して、同じダメージをまとめて計算しています。

ダメージの重複は、1発あたりのダメージが小さい連続技において特に顕著になります。

例えば、無振カイリューが無振オーガポンにスケショ1発で与えるダメージは22, 24, 25, 27の4通りしかなく、5発でも24通り(全分岐計算時の4万分の1以下)の計算量で済みます。

実装については、配列ではなく、{"ダメージ値" : 分岐の数} の連想配列 (Pythonでいう辞書型) として扱っています。

技によるダメージを計算した後の致死率計算では、防御側のポケモンのHPの分岐も {"残りHP" : 分岐の数} として管理します。

木の実による回復

前述の方法だけでは、あるHPに至る経緯は保持されないため、オボンの実などの回復実をすでに使ったかどうか判別できません。

そこで、

  • {"100" : 3} → 木の実を保持しているHP100の分岐が3通りある
  • {"100.0" : 3} → 木の実を保持していないHP100の分岐が3通りある

のようにHPを表すキーを少数表示にして区別しています。

キーを整数化すれば同等に扱うことができるため、実装を煩雑化せずに回復実の有無を識別できます。

この方法を用いて、きのみの発動条件を考慮して正確な確定数を計算しています。

アプリの仕組み

データの保存

ユーザーが登録したポケモンや最後に入力した情報は記録され、アプリを再開したときに読み込まれます。

データはブラウザのローカルストレージに保存されているため、アプリにログインしてアカウントで紐づける必要がありません。

一方で、異なるブラウザソフトや端末間では情報が共有されないデメリットや、Cookieを削除するとデータも消えるリスクもあります。

この対策として、ポケモン管理アプリには、登録したポケモンjson形式で出力・入力できるバックアップ機能を実装しています。

キャプチャ映像の取得・描画

キャプボもカメラとして扱われるため、navigator.mediaDevices プロパティを使って映像を取得できます。

html の video 要素で描画すると画面のアスペクト比がなぜか微妙に崩れてしまうため、まわりくどいですが video 要素の映像を canvas 要素に再描画しています。

画面からポケモン読み込み

ありがたいことに、ボックスの右側のステータス表示で情報が完結しているため、これを解析するだけでポケモンを読み込めます。

性格は六角形の頂点にある◯印をテンプレートマッチングで識別し、それ以外は文字認識しています。

ボックス画面のステータス表示

試合場面の判定

自動ダメージ計算アプリの起動中は、キャプチャ映像を一定時間ごとに確認し、現在のゲームの場面を以下の3つに分類します。

  • 見せ合い画面
  • 対戦画面
  • 該当なし

見せ合い画面は、自分のパーティ表示の左下に表示されるモンスターボールの有無で判定しています。

見せ合い画面の判定箇所

対戦画面は、自分のポケモンの「Lv.」という文字の有無で判定しています。

対戦画面の判定箇所

見せ合い画面での処理

見せ合い画面に移行すると、まず試合をリセットします。

次に、相手のパーティを読み込みます。

ポケモンの立ち絵をテンプレート画像と比較して識別しますが、この時、

することで高速化しています。

画面のロードが間に合わず結果がおかしくなる場合があるため、2回連続で同じ結果を得られるまで繰り返し読み取ります。

相手パーティが読み込めたら、自分と相手のポケモンの相性を評価します。

評価値は以下の式で与えています。

自分と相手の評価が相互に影響するため、有効打が多い相手に有利なポケモンがより評価されます。

攻撃技が少ない受けポケモンは評価されにくいですが、そもそも受け性能の評価は単純ではないため諦めました。

見せ合い画面の残り時間で繰り返し評価を行うことで評価値を収束させます。

対戦画面での処理

対戦画面に移行したら、試合時間のタイマーを起動します。

以降の処理は、場のポケモンが変わるたびに繰り返し行います。

まず、場に出ているポケモンの名前を文字認識します。

文字認識の関数は言語を指定して呼び出しますが、ポケモンの名前は日本語とは限りません。

そこで、まず日本語と仮定して読み取ります。

実際の名前がハングルだとうまく読み取れないため、一度目の文字認識で該当するポケモンがいなければ、ハングルを指定して再び読み取ります。

次に、場のポケモンのHPを読み取ります。

自分のポケモンについては、HPバー内に書かれている文字を読み取っています。

相手のポケモンの正確なHPはわからないため、HPバーの有色箇所のピクセル数を数えて割合に換算しています。

場のポケモンとHPをすべて読み終えたらダメージ計算を行います。

被ダメージ計算では公開されている使用率上位の技を考慮しています。

バランスチェック機能

使用率上位のポケモンのうち、今のパーティが苦手とする相手をハイライトできます。

攻・守をクリックすると、すべての相手に対してダメージ計算を行い、与えるダメージ割合の合計値を評価します。

受け性能の評価では、ダメージを技の使用率で重みづけしたものの合計で評価しています。

バランスチェック機能

最後に

今後もバグ修正などは細々と続けていきます。

レギュFを全くプレイできていないので、伝説環境が始まる前にランクマ復帰したいですね。