前回、ブログで環境構築をしました。
できます。いまの ~/pokemon-ai/agents/v1 と v2 をそのまま使って、記事と同じ「カード画像付きリプレイ」方式を作りましょう。
記事の流れは、cg.game で対戦 → visualize_data() でリプレイJSONを生成 → visualizer.html から HEROZ のVisualizerへ渡す、という流れです。
1. フォルダを作る
まずMacのターミナルで実行してください。
cd ~/pokemon-ai
mkdir -p decks simulations replays
cp -R agents/v1 decks/v1
cp -R agents/v2 decks/v2
cp -R agents/v1/cg simulations/cg
こうなればOKです。
pokemon-ai/
├── agents/
│ ├── v1/
│ └── v2/
│
├── decks/
│ ├── v1/
│ │ ├── main.py
│ │ ├── deck.csv
│ │ └── cg/
│ └── v2/
│ ├── main.py
│ ├── deck.csv
│ └── cg/
│
├── simulations/
│ └── cg/
│
└── replays/
記事でも、decks/<名前>/main.py + deck.csv と、simulations/cg を置く構成になっています。
確認します。
find decks simulations -maxdepth 2 -type f | sort
2. run_battle.py を作る
VS Codeで
~/pokemon-ai/simulations/run_battle.py
を新規作成してください。
以下を貼り付けます。
import importlib.util
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from cg.game import (
battle_start,
battle_finish,
battle_select,
visualize_data,
)
ROOT = Path(__file__).resolve().parents[1]
DECKS_DIR = ROOT / "decks"
REPLAYS_DIR = ROOT / "replays"
def load_deck(name):
path = DECKS_DIR / name / "deck.csv"
deck = []
with open(path, "r", encoding="utf-8-sig") as f:
for line in f:
value = line.strip().split(",")[0]
if value.isdigit():
deck.append(int(value))
if len(deck) != 60:
raise ValueError(
f"{name}: deck.csv が60枚ではありません "
f"({len(deck)}枚)"
)
return deck
def load_agent(name):
agent_dir = DECKS_DIR / name
main_path = agent_dir / "main.py"
# main.py から cg をimportできるようにする
if str(agent_dir) not in sys.path:
sys.path.insert(0, str(agent_dir))
spec = importlib.util.spec_from_file_location(
f"{name}_main",
main_path,
)
module = importlib.util.module_from_spec(spec)
old_cwd = os.getcwd()
try:
# main.py が deck.csv を相対パスで読むため
os.chdir(agent_dir)
spec.loader.exec_module(module)
finally:
os.chdir(old_cwd)
return module.agent
def main():
player1_name = "v1"
player2_name = "v2"
deck1 = load_deck(player1_name)
deck2 = load_deck(player2_name)
agent1 = load_agent(player1_name)
agent2 = load_agent(player2_name)
print(f"Player 1 : {player1_name}")
print(f"Player 2 : {player2_name}")
print("Battle start...")
obs, _ = battle_start(deck1, deck2)
try:
while True:
result = obs["current"]["result"]
if result >= 0:
break
player = obs["current"]["yourIndex"]
if player == 0:
action = agent1(obs)
else:
action = agent2(obs)
# battle_selectへ不要なので削除
obs.pop("search_begin_input", None)
obs = battle_select(action)
print()
print("Battle finished.")
result = obs["current"]["result"]
if result == 0:
print("Winner: v1")
elif result == 1:
print("Winner: v2")
else:
print("Draw")
# リプレイデータ取得
replay = json.loads(visualize_data())
REPLAYS_DIR.mkdir(exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
replay_path = (
REPLAYS_DIR /
f"{timestamp}_{player1_name}_vs_{player2_name}.json"
)
with open(
replay_path,
"w",
encoding="utf-8",
) as f:
json.dump(
replay,
f,
ensure_ascii=False,
)
print()
print(f"Replay saved:")
print(replay_path)
finally:
battle_finish()
if __name__ == "__main__":
main()
ここで重要なのが、
visualize_data()
です。これがカード画像付きVisualizerが再生するためのリプレイ情報を生成します。記事でもこのデータをJSONにしてブラウザから再生しています。
3. まずV1 vs V2を実行する
これまでと同じDockerを使います。
cd ~/pokemon-ai
実行:
docker run --rm \
--platform linux/amd64 \
-v "$PWD":/work \
-w /work/simulations \
gcr.io/kaggle-images/python \
python run_battle.py
成功すれば例えば、
Player 1 : v1
Player 2 : v2
Battle start...
Battle finished.
Winner: v2
Replay saved:
/work/replays/20260809_105500_v1_vs_v2.json
となります。
Mac側にも、
~/pokemon-ai/replays/
の中にJSONができます。
確認:
ls -lh ~/pokemon-ai/replays
4. visualizer.html を作る
次に、
~/pokemon-ai/simulations/visualizer.html
をVS Codeで作ります。
以下を貼り付けてください。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<title>PTCG AI Replay</title>
</head>
<body>
<h2>Pokémon TCG AI Battle Replay</h2>
<p>リプレイJSONを選択してください。</p>
<input
type="file"
id="replayFile"
accept=".json"
>
<script>
const fileInput =
document.getElementById("replayFile");
fileInput.addEventListener("change", function () {
const file = fileInput.files[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = function () {
const data = JSON.parse(reader.result);
const hidden = document.createElement("input");
hidden.type = "hidden";
hidden.name = "json";
/*
Kaggle形式とローカル形式の両方に対応
*/
if (data.steps) {
hidden.value =
JSON.stringify(
data.steps[0][0].visualize
);
} else {
hidden.value =
JSON.stringify(data);
}
const form =
document.createElement("form");
form.method = "POST";
form.action =
"https://ptcgvis.heroz.jp/Visualizer/Replay/0";
form.target = "_blank";
form.appendChild(hidden);
document.body.appendChild(form);
form.submit();
};
reader.readAsText(file);
});
</script>
</body>
</html>
このHTMLは、選択したJSONを読み込んでPTCG VisualizerへPOSTし、新しいタブでリプレイを開く役割です。記事でも同じ仕組みを使っています。
一点だけ注意すると、この操作では対戦リプレイJSONが ptcgvis.heroz.jp に送信されます。 自作AIの盤面履歴を外部サービスへ送信したくない場合は使わないでください。
5. Visualizerを開く
Macなら簡単です。
open ~/pokemon-ai/simulations/visualizer.html
ブラウザに、
Pokémon TCG AI Battle Replay
リプレイJSONを選択してください。
[ファイルを選択]
と表示されます。
6. JSONを選ぶ
「ファイルを選択」を押して、
pokemon-ai
└ replays
└ 20260809_105500_v1_vs_v2.json
を選びます。
すると新しいタブが開きます。
(ブラウザの設定によってはポップアップがブロックされるので、許可してあげる必要があります)
ここで記事に掲載されているような、実際のカード画像を使った対戦リプレイが表示されるはずです。記事でも visualizer.html → vis.json 選択 → 別タブでリプレイ、という手順になっています。
実際の対戦動画



コメント