【ITニュース解説】I made an agent play Slay the Spire 2 on its own — and what unlocked it was the game saying 'no'
2026年09月10日に「Dev.to」が公開したITニュース「I made an agent play Slay the Spire 2 on its own — and what unlocked it was the game saying 'no'」について初心者にもわかりやすく解説しています。
ITニュース概要
AIがゲーム「Slay the Spire 2」を自律プレイするエージェントを開発した。LLMに頼らず、ゲームからの「no」という拒否フィードバックを次の行動に活かし、安定動作を実現。同期処理のデッドロック回避や、ゲーム更新への対応など、実践的なシステム開発の課題解決法も示した。
ITニュース解説
Slay the Spire 2という人気のゲームを、人間が操作することなくAIエージェントが自動でプレイするという興味深いプロジェクトが公開された。このエージェントは、7ターンで35回もの行動を、一度も失敗せずに戦闘を完了したという。特筆すべきは、この自動プレイを実現したのが、高度な「賢いAI」、具体的には大規模言語モデル(LLM)の力に全面的に頼ったわけではないという点にある。むしろ、ゲームがエージェントの行動を「拒否」することから学び、そのフィードバックが成功の鍵となった。
このプロジェクトでは、まずゲームの内部構造を深く理解することから始まった。Slay the Spire 2はGodotというゲームエンジン上でC#/.NETというプログラミング言語を使って開発されているため、そのプログラムファイルを解析(リバースエンジニアリング)し、内部の仕組みを探ったのだ。すると、開発者がゲームのテストのために作った「AutoSlay」という自動プレイシステムが既に存在することを発見した。このシステムはゲームのマップ移動から戦闘、報酬の選択までを自動で行うもので、プレイヤーには公開されていない。さらに、このシステムがカード選択に使う「ICardSelector」という特定のインターフェース(プログラム間の取り決めのようなもの)も見つけ出した。これはゲームがカードの破棄、報酬、強化、削除など、あらゆるカード操作に利用する共通の窓口だったため、エージェントがゲームに介入する大きなヒントとなった。
多くの人が「AIがゲームをプレイする」と聞くと、LLMが全ての行動を考えていると思いがちだが、このプロジェクトはその常識を覆している。エージェントの戦闘中の意思決定は、LLMではなく、ゲームの内部に組み込まれたPython製の「ブリッジ」と呼ばれるプログラム内で、スコアに基づいたシンプルな「貪欲法」(その時点で最も良いと思われる手を選ぶ方法)で行われた。例えば、「ダメージ/コスト」の効率が良いカード、敵を倒せるカード、防御力の高いカードなどを優先的に選択する。LLM(ここではHermesというモデルが使われた)は、ゲームプレイの主要な判断を下す「脳」ではなく、むしろシステム全体のオペレーター(操作者)やオブザーバー(監視者)としての役割を担っている。つまり、エージェントのオン/オフを切り替えたり、ゲームの状態を読んだり、後述する行動の拒否を見たり、意思決定のポリシー(方針)を調整したりする役割だった。この「どこに意思決定を置くべきか」という見極めが、プロジェクトの重要なエンジニアリング判断となっている。
エージェントとゲームの連携は、専用の「コマンドチャネル」を通じて行われる。ゲーム内には、筆者が開発したMod(ゲームの改造プログラム)が組み込まれており、これがゲームの最新の状態を、PC内部のネットワーク(ループバック、127.0.0.1:5000というアドレス)を通じてPythonブリッジにHTTP POSTという通信方式で送信する。ブリッジはそれを受け取ると、直ちにHTTPレスポンスとして、ゲームに実行してほしいコマンド(例:「このカードをプレイしろ」「ターンを終了しろ」など)を返信する。ゲームのModはそのコマンドを受け取って実行するという、シンプルだが堅牢な同期(一方向ずつ順番に処理が進む)の仕組みが構築された。
このプロジェクトの核心とも言えるのが、「拒否フィードバックパターン」である。通常、エージェントがゲームに対して行動を指示しても、その行動がゲームのルール上無効だった場合、ゲームはただその行動を実行しないだけで、エージェントにはその失敗を直接伝えないことが多い。これではエージェントは同じ失敗を無限に繰り返してしまう可能性がある。この問題を解決するため、筆者はModに「専用のエラーチャネル」を設けた。ゲームがエージェントの行動(例:カードをプレイしようとしたが、エネルギーが足りない、対象がいない、特定の条件を満たしていないなど)を拒否した場合、Modはその失敗の理由や状況を詳細な情報としてまとめて、Pythonブリッジの専用のエラー受け入れ口に送信する。
重要なのは、この拒否情報が単なるログ(記録)として扱われるだけでなく、エージェントの「持続的な状態」として保存される点である。例えば、「NotPlayable」(プレイできない)という理由で拒否されたカードは、ブリッジ内の「unplayable」(プレイ不可)というリストに登録される。そして、エージェントが次にどのカードをプレイするかを決定する際、この「unplayable」リストに登録されたカードは候補から除外される。つまり、エージェントは「ゲームが『できない』と言ったこと」を記憶し、同じ失敗を繰り返さないように行動を修正するのだ。これは再学習やLLMの再プロンプトといった複雑な処理なしに、環境からの直接的なフィードバックによってエージェントが「学習」し、適応する非常に効率的な方法である。
このパターンの有効性は、「GRAND_FINALE」というカードが引き起こしたソフトロック(ゲームのフリーズ)問題で証明された。このカードは、山札が完全に空の時だけプレイできる特殊なカードなのだが、エージェントは条件を満たしていないにも関わらず繰り返しプレイしようとし、ゲームが応答しなくなるという問題が発生した。拒否フィードバックパターンを導入することで、一度「NotPlayable」として拒否されたGRAND_FINALEは「unplayable」リストに追加され、以降そのセッション中はエージェントが再びプレイしようとすることはなくなった。これにより、ゲームのフリーズが回避され、エージェントはスムーズにプレイを続行できるようになったのである。
プロジェクトの開発過程では、いくつかの技術的な課題にも直面した。一つは、「Godotのメインスレッドでのデッドロック」問題である。これは、Modがカード選択のためにブリッジに問い合わせる際に、同期的に(処理が終わるまで待機して)応答を待つような実装をしていたため、ゲームのメイン処理が停止し、そのままフリーズしてしまうというものだった。ゲームの主要な処理はメインスレッドで実行されており、そこで外部からの応答を同期的に待つことは、ゲーム全体の停止を招く危険がある。この問題は、非同期処理(応答を待つ間に別の処理を進めることができる)とタイムアウトを組み合わせることで解決された。具体的には、HTTP通信のタスクとタイムアウト用のタスクを同時に開始し、どちらかが先に完了したら処理を進めるという「Task.WhenAny」のような手法を用いることで、ゲームがフリーズすることなく、仮に応答が遅れても別の処理に進むことが可能になった。
もう一つの課題は、ゲームが「早期アクセス(Early Access)」版であることによる頻繁なアップデートと、それに伴う内部構造の変化だった。ゲームのバージョンアップによって、特定の機能や変数名が変更されたり削除されたりすると、Modがそれらを直接参照しているとエラーが発生し、動作しなくなってしまう。この問題に対しては、「防御的リフレクション」という手法が用いられた。リフレクションとは、プログラムが自分自身の構造や他のプログラムの内部構造を、実行時に動的に調べたり操作したりする機能である。これにより、Modはゲームの内部フィールド名を決め打ちするのではなく、実行時に「候補リスト」を元に適切なフィールドを探索し、その時点での正しい構造を読み取るようにした。これにより、仮に特定のフィールドが消えても、プログラム全体がクラッシュすることなく、変更に強く、バージョンアップにも対応しやすい堅牢なシステムが構築された。
このプロジェクトが示唆するのは、AIエージェントの開発において、単に「より賢いAI」を追求するだけでなく、エージェントと環境(ここではゲーム)との間の「コマンドチャネル」(どのように指示を伝えるか)と「フィードバックチャネル」(どのように環境の応答や拒否を受け取るか)をいかに設計するかが極めて重要であるということだ。ゲームが「できない」という明確なフィードバックを与えることが、エージェントの行動を修正し、安定した動作を可能にする。そして、LLMのような強力なAIは、全てを決定する「脳」としてではなく、システム全体の監視や高レベルな判断、戦略の調整といった、その能力が最も活きる場所に配置することが、優れたエージェントシステムの実現には不可欠だということを教えてくれる。