はじめに
Slay the Spire 2を遊びながら、ふと思いました。
「これ、AIに手札や敵の情報を渡したら、どんな判断をするんだろう?」
ローグライクカードゲームは、毎ターン状況が変わります。
手札、エナジー、敵の行動、HP、デッキ、レリック、ポーション、イベント選択肢など、判断材料が非常に多いです。
そこで今回は、Slay the Spire 2のプレイ状況をAIに渡して、次の行動を相談できるアプリケーションを作ってみました。
ゲーム状態の取得には、Slay the Spire 2向けのModである STS2MCP を利用しています。
STS2MCP は、ゲーム内の状態をローカルAPI経由で取得したり、外部ツールからゲームに対して操作を送ったりできるModです。
さらにMCPサーバーとしても利用できるため、Claude DesktopやClaude CodeのようなMCP対応クライアントからゲーム状態を扱うこともできます。
今回作ったアプリでは、STS2MCPを「ゲーム状態を取得するための基盤」として使い、その上に以下のような仕組みを実装しました。
- 現在のゲーム状態を取得する
- 戦闘・イベント・カード選択・休憩所など、場面ごとにプロンプトを切り替える
- AIが「次に何をすべきか」を配信向けにわかりやすく返す
- OBSのテキスト表示や読み上げと連携する
- 必要に応じてホットキーで再問い合わせできる
この記事では、STS2MCPを使ってゲーム状態を取得し、それをLLMに渡して「AI軍師」として配信に組み込むまでの流れをまとめます。
作ったもの
作ったのは、Slay the Spire 2のプレイをサポートする「AI軍師」アプリです。
プレイヤーがゲームを進めると、アプリ側で現在の状況を取得し、AIに問い合わせます。
AIはその状況をもとに、以下のような助言を返します。
- このターンに使うべきカード
- 攻撃と防御の優先度
- イベント選択肢のおすすめ
- 取得すべきカード
- 休憩所で回復するか、強化するか
- 今後のデッキ方針
ゲーム配信で使うことも想定しているため、回答は長すぎず、視聴者にもわかりやすい形に整えています。
例としては、以下のようなイメージです。
【軍師】
作戦: まずは被ダメージを抑えつつ、次ターン以降の火力につなげる。
対象: 防御カードを優先し、余ったエナジーで攻撃。
理由: 敵の攻撃が重いため、ここでHPを削られすぎると後半が厳しくなる。
STS2MCPについて
今回のアプリでは、Slay the Spire 2のゲーム状態を取得するために STS2MCP を利用しています。
STS2MCPは、Slay the Spire 2のゲーム内状態を外部ツールから扱えるようにするModです。
主に以下のような情報を取得できます。
- 現在の状態
- 戦闘中のプレイヤー情報
- 手札
- 敵の情報
- イベント情報
- 選択肢
- レリック
- ポーション
- デッキ情報
また、外部ツールから操作を送ることもできるため、単なる状態取得だけでなく、AIエージェントによる自動プレイにも応用できる構成になっています。
今回のアプリでは、いきなり完全自動プレイを目指すのではなく、まずは「AIが判断し、人間が操作する」形にしました。
つまり、STS2MCPを使ってゲーム状態を取得し、その状態をAIに渡して、AIから助言をもらう構成です。
Slay the Spire 2
↓
STS2MCP
↓
ローカルAPI
↓
Pythonアプリ
↓
LLM
↓
AIの助言
↓
OBS表示 / 読み上げ
個人的には、ここがかなり面白いポイントでした。
STS2MCPによって、ゲームの状態を「人間が画面を見て判断する情報」から「プログラムが扱える構造化データ」に変換できます。
そのため、LLMに対しても、
「今こういう状況です。次に何をすべきですか?」
とかなり具体的に聞けるようになります。
全体構成
ざっくりした構成は以下です。
Slay the Spire 2
↓
STS2MCP
↓
ゲーム状態の取得
↓
Pythonアプリ
↓
state_typeごとに処理を分岐
↓
場面ごとのプロンプト生成
↓
LLM API
↓
AIの助言
↓
OBS表示 / 読み上げ / ホットキー操作
アプリ側では、現在の状態を以下のような種類に分類しています。
- 戦闘中
- イベント中
- カード選択
- 休憩所
- 報酬選択
- その他の状態
状態によってAIに聞くべき内容が変わるため、単一のプロンプトですべて処理するのではなく、場面ごとにプロンプトを分ける方針にしました。
コード解説1: STS2MCPからゲーム状態を取得する
まずは、STS2MCPが公開しているローカルAPIから現在のゲーム状態を取得します。
実際のエンドポイント名は利用しているバージョンや構成によって変わる可能性がありますが、イメージとしては以下のような処理です。
import requests
STS2MCP_BASE_URL = "http://127.0.0.1:8080" def fetch_game_state() -> dict:
response = requests.get(
f"{STS2MCP_BASE_URL}/state",
timeout=5,
)
response.raise_for_status()
return response.json()
この処理で取得したJSONを、以降のAI判断の入力として使います。
ポイントは、画面キャプチャやOCRではなく、ゲーム状態を構造化データとして取得できるところです。
画面を画像としてAIに見せる方法もありますが、カード名、敵のHP、選択肢、イベント情報などをテキスト・JSONとして扱える方が、プロンプトに組み込みやすくなります。
コード解説2: state_typeで処理を分ける
STS2MCPから取得したゲーム状態には、現在の状態を表す情報が含まれます。
この状態をもとに、どのプロンプトを使うかを切り替えます。
def build_prompt(game_state: dict) -> str:
state_type = game_state.get("state_type")
if state_type == "combat":
return build_combat_prompt(game_state)
if state_type == "event":
return build_event_prompt(game_state)
if state_type == "card_reward":
return build_card_reward_prompt(game_state)
if state_type == "rest":
return build_rest_prompt(game_state)
return build_general_prompt(game_state)
戦闘中であれば「どのカードをどの順番で使うか」が重要になります。
イベント中であれば「どの選択肢を選ぶべきか」が重要になります。
カード報酬であれば「今のデッキ方針に合うカードはどれか」が重要になります。
単一のプロンプトですべてを処理するよりも、状況ごとにAIへ渡す観点を絞った方が、回答が安定しやすくなりました。
コード解説3: 戦闘用プロンプトを生成する
戦闘中は、手札・エナジー・敵の行動予定・HPなどをもとに判断してもらいます。
def build_combat_prompt(game_state: dict) -> str:
player = game_state.get("player", {})
hand = game_state.get("hand", [])
enemies = game_state.get("enemies", [])
relics = game_state.get("relics", [])
potions = game_state.get("potions", [])
return f"""
あなたはSlay the Spire 2の攻略を支援するAI軍師です。
現在の戦闘状況をもとに、次に取るべき行動を判断してください。
# 判断方針
- まず生存を優先してください
- 敵の行動予定を考慮してください
- カードの使用順を明確にしてください
- 不明な情報は推測しすぎないでください
- 配信で読み上げるため、短くわかりやすく答えてください
# プレイヤー情報
HP: {player.get("hp")}
ブロック: {player.get("block")}
エナジー: {player.get("energy")}
# 手札
{format_cards(hand)}
# 敵情報
{format_enemies(enemies)}
# レリック
{format_relics(relics)}
# ポーション {format_potions(potions)}
# 出力形式
作戦:
カード使用順:
理由:
"""
AIに「自由に考えて」と投げるのではなく、判断方針と出力形式を指定しているところがポイントです。
特に配信で使う場合、AIの回答が長すぎるとテンポが悪くなります。
そのため、以下のように出力形式を固定しています。
作戦:
カード使用順:
理由:
これにより、OBS表示もしやすく、読み上げにも使いやすくなりました。
コード解説4: イベント用プロンプトを生成する
イベント中は、戦闘とは判断基準が変わります。
必要なのはカードの使用順ではなく、選択肢ごとのリスクとリターンです。
def build_event_prompt(game_state: dict) -> str:
event = game_state.get("event", {})
player = game_state.get("player", {})
deck = game_state.get("deck", [])
return f"""
あなたはSlay the Spire 2の攻略を支援するAI軍師です。
現在のイベント内容を読み、どの選択肢を選ぶべきか判断してください。
# 判断方針
- 現在HPとデッキ状況を考慮してください
- リスクとリターンを比較してください
- 長期的に強くなれる選択肢を評価してください
- 危険な選択肢を選ぶ場合は理由を説明してください
- 最終的なおすすめを1つに絞ってください
# プレイヤー情報
HP: {player.get("hp")}
# 現在のデッキ概要
{format_deck_summary(deck)}
# イベント名
{event.get("event_name")}
# イベント本文
{event.get("body")}
# 選択肢
{format_event_options(event.get("options", []))}
# 出力形式
おすすめ:
理由:
注意点:
"""
イベントでは、選択肢名だけを渡すとAIが判断しづらいことがありました。
例えば、選択肢のタイトルだけでは、それがレリック獲得なのか、HPを失うのか、カードを追加するのかがわかりません。
そのため、選択肢ごとの説明や効果も含めて渡すようにしました。
def format_event_options(options: list[dict]) -> str:
lines = []
for option in options:
locked = "ロック中" if option.get("is_locked") else "選択可能"
lines.append(
f"""
[{option.get("index")}] {option.get("title")}
状態: {locked}
説明: {option.get("description")}
"""
)
return "\n".join(lines)
STS2MCPから取得できる構造化されたイベント情報があることで、選択肢の比較をAIに任せやすくなりました。
STS2MCPを使って感じたこと
STS2MCPを使うことで、ゲーム画面を人間が目視して判断するだけでなく、ゲーム状態を外部アプリから扱えるようになります。
これはかなり大きいです。
例えば、通常であればAIにゲーム状況を伝えるには、画面キャプチャを使ったり、手動で状況を書いたりする必要があります。
しかし、STS2MCPを使うと、手札、敵、イベント、選択肢などを構造化されたデータとして取得できます。
そのため、LLMに対してもかなり具体的に状況を渡せます。
今の手札はこれです。
敵はこの行動を予定しています。
プレイヤーのHPはこのくらいです。
選択肢はこの3つです。
この状況で一番良い行動を判断してください。
この形にできると、AIの回答もかなり安定します。
一方で、STS2MCPが取得してくれる情報をそのまま投げれば完璧、というわけではありませんでした。
AIに渡す前に、
- どの情報が判断に必要か
- どの情報は省略してよいか
- 今が戦闘なのかイベントなのか
- 最終的にどの形式で回答してほしいか
をアプリ側で整理する必要があります。
つまり、STS2MCPは「ゲーム状態を取得する層」であり、今回作ったアプリは「AIに判断させ、配信で使える形に変換する層」という位置づけです。
この役割分担にすると、かなり扱いやすくなりました。
OBS・読み上げとの連携
AIの回答は、OBS上に表示できるようにテキストファイルへ出力しています。
OBS側では、そのテキストファイルを読み込むことで、配信画面上にAIの助言を表示できます。
また、読み上げ用にVOICEVOXなどの音声合成と組み合わせることで、AIが実際に喋っているような演出も可能にしました。
ただし、読み上げ用の文章には少し整形が必要でした。
例えば、画面表示用には以下のような見出しがあると見やすいです。
作戦:
対象:
理由:
一方で、読み上げ時にはそのままだと不自然になることがあります。
そのため、読み上げ前には以下のような変換を入れています。
text = text.replace("作戦:", "作戦。")
text = text.replace("対象:", "対象。")
text = text.replace("理由:", "理由。")
細かい部分ですが、配信で使う場合はこうした調整がかなり効きます。
ハマったこと
AIの回答がすぐ上書きされる
OBSのテキストが更新されないように見える問題がありました。
調べてみると、ホットキーで問い合わせた回答自体は生成されているものの、その直後に次の自動処理が走り、表示内容が上書きされていました。
そのため、手動問い合わせ中は自動問い合わせを一時的に止める、または手動問い合わせ結果を優先表示する、といった制御が必要になりました。
イベント選択肢の解釈
イベントでは、選択肢の効果をAIが正しく理解できるかが重要でした。
選択肢名だけを渡すと、AIが効果を推測してしまうことがあります。
そのため、以下のような情報をなるべくセットで渡すようにしました。
- 選択肢名
- 説明
- 効果
- 選択可能かどうか
- 獲得できるカードやレリック
- HP変動
- ロック状態
AIに判断してもらう場合でも、入力データの構造化はかなり重要だと感じました。
AIに全部任せると回答がブレる
LLMは柔軟に回答してくれますが、ゲーム攻略では一貫性も重要です。
そのため、以下のような制約を入れた方が安定しました。
- まず現在の生存を優先する
- リスクが高い選択肢は理由を明記する
- カード選択では現在のデッキ方針に合うかを見る
- 不確実な情報は断定しない
- 最終的な推奨行動を1つに絞る
AIに自由に考えさせる部分と、アプリ側でルール化する部分のバランスが大事でした。
作ってみて感じたこと
ゲームとAIの相性はかなり良いと感じました。
特にSlay the Spireのようなカードゲームは、状況判断の連続です。
AIに現在の状態を渡すことで、「なぜそのカードを使うのか」「なぜその選択肢を選ぶのか」を言語化してくれます。
これは攻略支援としても面白いですが、配信コンテンツとしてもかなり相性が良いです。
プレイヤーがAIに相談しながら進めることで、視聴者も「AIはそう考えるのか」「いや、それは違うだろ」とツッコミながら見られます。
単なる自動プレイではなく、人間とAIが一緒に悩む形にできるのが面白いポイントでした。
今後やりたいこと
今後は、以下のような改善を考えています。
- 過去の判断履歴をAIに渡す
- デッキ方針を長期記憶として持たせる
- ボスやエリートに向けた中長期戦略を考えさせる
- AIの判断と実際の結果を比較する
- 視聴者コメントをAIの判断材料に入れる
- AIの人格をより配信向けに調整する
- 回答の優先度や緊急度を出せるようにする
- STS2MCPの操作APIも活用して、将来的には一部操作の自動化も試す
特に、単発の判断だけでなく「このランでは何を目指しているのか」をAIに持たせると、より軍師らしくなりそうです。
また、今回は「AIが判断して、人間が操作する」形にしましたが、STS2MCPは外部ツールから操作を送ることもできるため、将来的にはAIエージェントによる半自動プレイや検証にも広げられそうです。
まとめ
Slay the Spire 2のゲーム状態をAIに渡し、攻略方針を提案してもらうアプリを作りました。
実装してみると、単にLLM APIを呼ぶだけではなく、以下の点が重要でした。
- ゲーム状態をAIが理解しやすい形に整理する
- 場面ごとにプロンプトを切り替える
- 配信で使いやすい短い回答に整える
- OBSや読み上げと連携する
- 自動問い合わせと手動問い合わせの競合を制御する
ゲームAIというと自動操作のイメージがありますが、今回のように「一緒に考えるAI」として組み込むだけでも、かなり面白い体験になります。
今後も、ゲーム配信とAIを組み合わせた実験を続けていきたいです!
最後に
実際の動きを確認したい方は、Youtubeに動画投稿しておりますので、気になる方はこちらをご覧ください!