【技術解説】実装概要~サボるAIに処理分割で対応~【キャラAIバトルロイヤル】
2025/12/31

キャラAIバトルロイヤルは、シンプルな入力を元に、AIにバトル結果を判定してもらうゲームです。 その実装の概要を記します。 今のAIは「サボる」ので、指示に正確に従ってもらうために処理を分割しているのがポイントです。
具体的には、指示を詳細にしても最終アウトプットが指示通りなら良いだろうと途中を省略してしまうので、 処理を区切って都度指示しています。 AIに大きな仕事をさせようとする方の参考になりましたら幸いです。
ゲーム本体は『キャラAIバトルロイヤル』でプレイ可能ですので、
ぜひ新ジャンル「AI判定ゲーム」を体験してみてください。
プログラミングブログ記事一覧
[PR]
実装全体概要
本ゲームの実装は、大まかに以下の4パートに別れています。
- クライアントアプリケーション
- cgi
- データベース
- バトルサーバー
以下で順次、概要のみになりますが解説します。 なおバトルサーバーでAIを使用している部分は新規チャレンジのため、詳述します。
クライアントアプリケーション
クライアントアプリケーションは皆さんが見ているアプリケーション本体です。 主にHTMLおよびJavaScriptで記述しています。
入力された「タイプ」「説明」をcgiに渡し、cgiからバトル結果を受け取って表示します。
タスクごとに関数を分けて順番に実行するシステムを実装し、演出は yield を使って制御しています。 その際、通常は await で待つ非同期処理を yield で待てるようにするなどの工夫をしています。
cgi
cgiは、クライアントアプリケーションとデータベースの橋渡しを行います。 プログラミング言語にはPythonを使用しています。 データベース処理は、Python内でSQLを記述して実施しています。
クライアントアプリケーションから入力された「タイプ」「説明」をデータベースに保存します。 また本人が参加しているバトルの情報をクライアントアプリケーションに返します。
セッション番号をサーバーログ出力することで、同時に複数の場所からアクセスされてログが入れ子になってもデバッグしやすくするなどの工夫をしています。
データベース
データベースは、キャラクターの情報や、バトル結果を保存します。 データベースには MySQL を使用しています。
これらの情報は、それぞれ大量に登録されても大丈夫な設計にしています。 具体的には INDEX を効率的に用いて JOIN が高負荷にならない工夫はしています。
ただしデータベースのプログラミングとしては特別高度なことはしておらず、 現時点では実績としても不足している状況ですので、 一般的な範囲かと思います。
バトルサーバー
バトルサーバーは、以下の処理を行います。 プログラミング言語にはPythonを使用しています。 データベース処理は、Python内でSQLを記述して実施しています。
- データベースからキャラクター情報を取得
- キャラクター情報をAIに渡してバトル結果を受け取る
- データベースにバトル結果を保存
より具体的には、以下のように細かく分割して処理しています。
- [Program]データベースからキャラクター情報を取得
- [AI]不適切キャラを除外

- [AI]必殺技とスコアを生成

- [Program]序盤戦:スコアに基づき中盤戦進出者を決定

- [AI]中盤戦:決勝グループ進出者を決定

- [AI]決勝グループ戦:決勝戦進出者を決定

- [AI]決勝戦:優勝決定

- [AI]決勝グループ戦バトルイメージ生成

- [Program]ファイルにバトルイメージを保存
- [Program]データベースにバトル結果を保存
具体的なプログラムはAI API プログラミングの記事に分けて詳述しています。
サボるAIに指示を聞いてもらうため分割
バトルサーバーにおける前述の分割は、当初の設計と異なりました。 AIがサボることがわかり、分割した経緯があります。
AIがサボる?
当初は、参加者全員をプロンプトすなわちAIへの指示に入れ、トーナメント表を組み、順次バトルして優勝者を決める方針でした。
しかし当初のプロンプトでは望んだ結果にならず、厳密化・詳細化しても好転しませんでした。
この現象について、AIと色々と議論しました。 その結果、LLM(大規模言語モデル)で構築される今のAIは、効率的に処理するため、指示を省略して解釈することがわかりました。 つまり人間でいえば「サボる」ということです。
具体的には、トーナメント表を組んで順次バトルする指示を詳細化しても、 AIは従わずに適当な対戦カードで優勝を決めるなどです。 最終アウトプットが指示通りなら、途中の処理は省略しても良いと判断してしまうようです。
それぞれのステップでAIモデルを指定

この対応のため、処理をステップに分けて、ひとつひとつ、AIに処理してもらう方式に変更しました。
ステップごとに処理を実施しますので、それぞれAIモデルを指定してAPIを呼び出す形になります。 つまり、各ステップごとにAIモデルを変更できます。
AIモデルとは、例えば ChatGPT であれば、ハイエンドモデルは gpt-5、 前世代のモデルは例えば gpt-4o が使用可能で、その軽量版が gpt-4o-mini などです。 同じ ChatGPT でも、その能力等に応じて、使い分けることができる設計にしました。
ただし本記事公開時点では、画像生成を除き、すべての処理で費用対効果のバランスが良いAIモデル claude-sonnet-4-5 を使用しています。 ここについてはAIモデル比較記事で詳述しています。
画像生成が可能に
本ゲームでは、すべての結果が確定した後に、画像を生成しています。
これは当初の想定にはありませんでしたが、 ステップごとにAIモデルを変更可能な設計にすることで、画像生成に特化したモデルも使用可能になりました。
コストが当初想定の1バトル数円未満から+20円程度上がりましたが、明らかに楽しいゲームになりますので正式に採用しています。 ChatGPT (gpt-image-1) による画像生成 および Gemini (gemini-3-pro-image-preview) による画像生成の記事もご参照ください。 なぜ ChatGPT をボツにして Gemini を採用したのかも解説しています。
序盤戦はプログラムで実施
序盤戦については、AIが生成したスコアに基づき、プログラムで直接判定して処理しています。 これも分割したことでできた効果です。
AIが不適切な回答をした場合の対応
AIは不適切な回答をする、つまり間違えることがあります。
本ゲームのようなバトルシステム内で使用する場合、不適切な回答が致命的な問題になり得ます。 例えば、回答フォーマットが違う、指定キャラクター全員を処理していないなどです。
対応しておかないと、バトルが途中で終了してしまうことになります。 エラーハンドリングが重要です。
チェッカー詳細化で対応
不適切な回答への対応のため、詳細なチェッカーを用意して、やり直す方式にしています。
これについては、AI処理を分割したおかげで、ピンポイントでチェックしてやり直せることになり、結果的には効率的になりました。
例外キャッチ
本ゲームのバトルサーバーは、AIには基本的にJSONフォーマットで回答をしてもらっています。
しかし例えば Anthropic Claude は { と } の対応が合っていない回答をすることが時々あります。
このケースは事前検出に時間を割くよりも、例外を検出した方が簡易的に実装できます。 具体的には以下のようにPythonを記述し、例外発生時にはリトライする方法で対応しています。
try: oResultJson = json.loads( sContent ) except Exception as e: raise LOCustomException(...) # 独自例外※呼び出し元でキャッチしてリトライ
[PR]
まとめ
本記事では、大きく4パートある『キャラAIバトルロイヤル』実装の全体概要と、サボるAIに対して処理分割した実装について解説しました。
AI処理は、分割してそれぞれの処理の目的を明確にすることで、AIのミスはほとんどなくなりました。 画像生成もできるようになりゲームとしてのクオリティーが上がりましたので、怪我の功名です。
それでも各処理のプロンプト(AIへの指示)には工夫が必要ですので、APIの呼び出し方法を含め、順次解説します。
補足
- 記事の校正/添削に生成AIの Anthropic Claude を利用しております。
- 記事内の画像の作成に生成AIの Google Gemini を利用しております。
- 画像内のラスタライズ文字フォントにOpen Font LicenseのNoto Sans Japaneseを使用しております。
- 画像内のラスタライズ文字フォントにOpen Font LicenseのZen Antiqueを使用しております。
- 画像内のラスタライズ文字フォントにOpen Font LicenseのOriginal Surferを使用しております。
- ※各社の登録商標または商標について「®」「™」等の表記はしておりません。
カテゴリー:プログラミング解説,キャラAIバトルロイヤル
[PR]