macbookでもポケモンカードAIバトルチャレンジの環境つくれるか?
はい、MacBookでも問題なくポケモンカード AIバトルチャレンジの開発環境は作れます。
特にApple Silicon(M1/M2/M3/M4)搭載Macでも参加している人は多く、Dockerを使えばほぼWindows/Linuxと同じように開発できます。ただし、Apple Siliconでは一部Dockerイメージがx86向けのためエミュレーションが必要になるケースがあり、多少遅くなることがあります。
必要なもの
Macなら以下だけでOKです。
- macOS
- Python 3.11以上
- Git
- VS Code
- Docker Desktop
- Kaggleアカウント
Macでのメリット
- Unix系なのでPython開発が非常にやりやすい
- Gitが使いやすい
- Dockerとの相性が良い
- ターミナル環境が最初から整っている
AI開発者はMacを使っている人もかなり多いです。
注意点
Apple Silicon(Mシリーズ)なら
- Docker Desktopを起動してから作業する
- Dockerイメージのダウンロードに20GB以上必要
- メモリは最低16GBあると快適(8GBでも動くが重くなりやすい)
という点だけ注意が必要
全体の流れ
- Homebrewをインストール
- Gitをインストール
- Docker Desktopをインストール
- VS Codeをインストール
- Kaggleを設定
- サンプルAIをダウンロード
- ローカル対戦
Homebrewをインストール
ターミナルを開きます。
インストール済みか確認
brew --version
バージョンが表示されればOKです。
表示されなければ
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
バージョン情報が出ないため、パスを追加
brew --version
zsh: command not found: brew
echo >> ~/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv zsh)"
バージョン確認
brew --version
Homebrew 6.0.10
Gitをインストール
brew install git
確認
git --version
git version 2.50.1 (Apple Git-155)
Docker Desktopをインストール
次はDocker Desktopをインストールします。
① Docker Desktopをダウンロード
公式サイトからMac版をダウンロード。
Docker Desktop(公式)
https://www.docker.com/products/docker-desktop/
※ Apple Silicon(M1/M2/M3/M4)なら Apple Chip 版を選びます。
※ Intel Macなら Intel Chip 版です。

② インストール
ダウンロードした .dmg を開き、
Docker.app を Applications フォルダへドラッグします。

③ 起動
Applicationsから Docker を起動します。
初回は
- 利用規約への同意
- パスワード入力


Skip


④ ターミナルで確認
バージョンの確認
docker --version
Docker version 29.6.1, build 8900f1d
docker run hello-world
を実行すると、初回はイメージをダウンロードしたあと、
Hello from Docker!
と表示されればDockerが正常に動作しています。
VS Codeをインストール
インストールします。
拡張機能は後から追加できます。
ターミナルを開いて実行します。
brew install --cask visual-studio-code
インストールが終わったら起動します。
open -a "Visual Studio Code"
またはSpotlight(⌘ + Space)で「Visual Studio Code」と入力して起動できます。

Kaggleアカウント作成
Kaggleへ登録します。
「Pokemon TCG AI Battle」へ参加します。
右上からサインインします。

ここではGoogleアカウントを使うので「Sign in with Google」を選択

次は「ポケモンカードAIバトルチャレンジ」のコードを取得します
まず、VS Codeからターミナルでも作業できるようになっているか確認しましょう。
ターミナルで次を実行。
$ code --version
1.130.0
1b6a188127eeaf9194f945eb6eb89a657e93c54c
arm64
$
表示されればOKです。
Kaggle APIを設定
KaggleのAPIキー(kaggle.json)が必要になります。
APIキーをダウンロード
Kaggleにログインし、
Profile(右上のアイコン)→ Settings → API → Create New Token



Generate New Tokenをクリック

トークン情報が出るので、これは誰にも教えないようにしましょう
% mkdir -p ~/.kaggle && echo KGAT_abcdefghijklmna26ce1dfcf1f6c5bc > ~/.kaggle/access_token && chmod 600 ~/.kaggle/access_token
Docker.appを起動させておきます。
Finder → アプリケーションDocker.app をダブルクリックして起動
docker run –rm \
-v ~/.kaggle:/root/.kaggle \
gcr.io/kaggle-images/python \
kaggle competitions list
実行するとダウンロードにしばらく時間がかかります。
AIデータの取得
コンペ名は
pokemon-tcg-ai-battle-challenge-strategy
ですね。
次はコンペデータを取得します。
先に参加表明をしておきます。「Join Hackathon」をクリック

同意をします。

作業をフォルダを作成
mkdir -p ~/pokemon-ai
cd ~/pokemon-ai
コンペデータの取得
docker run --rm \
-v ~/pokemon-ai:/work \
-v ~/.kaggle:/root/.kaggle \
-w /work \
gcr.io/kaggle-images/python \
kaggle competitions download -c pokemon-tcg-ai-battle-challenge-strategy
これでデータのダウンロードが完了しました。
圧縮ファイルを解答します。
% cd ~/pokemon-ai
unzip pokemon-tcg-ai-battle-challenge-strategy.zip
Archive: pokemon-tcg-ai-battle-challenge-strategy.zip
inflating: Card_ID List_EN.pdf
inflating: Card_ID List_EN_.pdf
inflating: Card_ID List_JP.pdf
inflating: Card_ID List_JP_.pdf
inflating: EN Card Data.csv
inflating: EN_Card_Data.csv
inflating: JP Card Data.csv
inflating: JP_Card_Data.csv
Mac pokemon-ai %
Simulation部門にも参加
PTCG AI Battle Challenge Simulation
「Join Competition」を押して、利用規約に同意
下記URLにアクセスして、右上のCopy & Editをクリック
[Beginner Guide] From Deck List to First Valid Sub
② Notebookを実行
自分のNotebookになったら、上から順番にセルを
Shift + Enter または ▶ Run
で実行していきます。
画面の右上にある ⬇ Download をクリック。
するとNotebook(.ipynb)をダウンロードできます。
公式サンプルAIのダウンロード
先ほど開いた
A Sample Rule-Based Agent Mega Lucario ex Deck
の右上にある
Copy & Edit
ではなく、Downloadを押します。
すると
A Sample Rule-Based Agent Mega Lucario ex Deck.ipynb
というNotebookがダウンロードされます。
Kaggleの
A Sample Rule-Based Agent Mega Lucario ex Deck
へ戻る。
そして
Copy & Edit した自分のNotebookで、
一番上から
Run All
を押す。
実行が終わると右側のOutputの中に
submission.tar.gz
が作成されます。
これが提出ファイルです。
このファイルをダウンロード。
submission.tar.gz を解凍します。
この画面にある
submission/
├── cg/
├── deck.csv
└── main.py
これが提出するAIになります。
main.py
ここに
- どのカードを使うか
- どのポケモンを前に出すか
- エネルギーを誰につけるか
- ワザを使うか
- ベンチに出すか
などのAIが書かれています。
これから一番編集するファイルです。
main.py
│
├── デッキ読み込み
├── カード情報読込
├── 攻撃計画を立てる
├── 各行動に点数を付ける
└── 一番点数が高い行動を選ぶ
つまり、
「ルールベースAI」
です。AI学習(強化学習)ではありません。
deck.csv
これはデッキです。
例えば
4,Lucario
2,Riolu
...
のようにカード構成が書かれています。
cg
ここはゲームエンジンです。中身を触ることはないです。
1. test_battle.py を作る
submission と同じ階層
pokemon-ai/
├── submission/
│ ├── main.py
│ ├── deck.csv
│ └── cg/
└── test_battle.py
として、test_battle.py にこれを書きます。
from kaggle_environments import make
import sys
import os
SUBMISSION_DIR = os.path.join(os.path.dirname(__file__), "submission")
os.chdir(SUBMISSION_DIR)
sys.path.insert(0, SUBMISSION_DIR)
from main import agent
deck_path = os.path.join(SUBMISSION_DIR, "deck.csv")
with open(deck_path, "r") as f:
deck = [int(line.strip()) for line in f if line.strip()]
print("deck cards:", len(deck))
env = make(
"cabt",
configuration={
"decks": [deck, deck]
}
)
env.run([agent, agent])
print("Simulation finished.")
print(env.steps[-1])
with open("result.html", "w", encoding="utf-8") as f:
f.write(env.render(mode="html"))
print("result.html を作成しました。")
公式サンプルも基本的にこの構成です。configuration={"decks": [deck, deck]} を指定して、env.run([agent, agent]) で2つのAIを戦わせます。
実行結果
% docker run --rm \
--platform linux/amd64 \
-v "$PWD":/work \
-w /work \
gcr.io/kaggle-images/python \
python test_battle.py
[kaggle_environments.envs.open_spiel_env.open_spiel_env] INFO: Successfully loaded OpenSpiel environments: 24.
[kaggle_environments.envs.open_spiel_env.open_spiel_env] INFO:
〜中略〜
hSBgAEMABBgAEEAHMABBgABBACEAHMABBgABCACEAHMABBgABDACEAHMABBgABEACEAHMABBgABFACEAsEAEqgADDIBACBmABQACMgEADEADBnALDAECQAVBADWAWEACFQAWQAI=='}, 'status': 'DONE'}]
Traceback (most recent call last):
File "/work/test_battle.py", line 34, in <module>
f.write(env.render(mode="html"))
^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/dist-packages/kaggle_environments/core.py", line 373, in render
window_kaggle, self.html_renderer(*args[: self.html_renderer.__code__.co_argcount])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/dist-packages/kaggle_environments/envs/cabt/cabt.py", line 197, in html_renderer
with open(jspath, encoding="utf-8") as f:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/usr/local/lib/python3.12/dist-packages/kaggle_environments/envs/cabt/cabt.js'
What's next:
Debug this container error with Gordon → docker ai "help me fix this container error"
対戦は終わっているが、対戦をブラウザで表示するためのJavaScriptファイルがDockerイメージに入っていない、というだけです。
先程のプログラムの後半を修正
from kaggle_environments import make
import sys
import os
SUBMISSION_DIR = os.path.join(os.path.dirname(__file__), "submission")
os.chdir(SUBMISSION_DIR)
sys.path.insert(0, SUBMISSION_DIR)
from main import agent
deck_path = os.path.join(SUBMISSION_DIR, "deck.csv")
with open(deck_path, "r") as f:
deck = [int(line.strip()) for line in f if line.strip()]
print("deck cards:", len(deck))
env = make(
"cabt",
configuration={
"decks": [deck, deck]
}
)
env.run([agent, agent])
print("Simulation finished.")
print(env.steps[-1])
winner = env.steps[-1][0]["reward"]
if winner == 1:
print("Player1 WIN")
elif winner == -1:
print("Player2 WIN")
else:
print("DRAW")
今度は2つのデッキで対戦してみましょう
===============================
まずフォルダを2つに分けます
現在の submission を、元AIと改造AIとしてコピーします。
Macのターミナルで、
cd ~/pokemon-ai
mkdir -p agents
cp -R submission agents/v1
cp -R submission agents/v2
こうなればOKです。
pokemon-ai/
├── agents/
│ ├── v1/
│ │ ├── main.py
│ │ ├── deck.csv
│ │ └── cg/
│ │
│ └── v2/
│ ├── main.py
│ ├── deck.csv
│ └── cg/
│
├── submission/
└── tournament.py ← これから作る
v1 はサンプルAIそのまま、v2 はこれから改造するAIです。
2. tournament.py を作ります
VS Codeで、
~/pokemon-ai/tournament.py
を新規作成して、以下コードを書きます。
from kaggle_environments import make
import importlib.util
import os
import sys
import time
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
V1_DIR = os.path.join(BASE_DIR, "agents", "v1")
V2_DIR = os.path.join(BASE_DIR, "agents", "v2")
def load_deck(agent_dir):
"""deck.csv を読み込む"""
deck_path = os.path.join(agent_dir, "deck.csv")
with open(deck_path, "r") as f:
deck = [
int(line.strip())
for line in f
if line.strip()
]
if len(deck) != 60:
raise ValueError(
f"{deck_path}: deck must contain 60 cards "
f"(actual={len(deck)})"
)
return deck
def load_agent(agent_dir, module_name):
"""
main.py を別々のPythonモジュールとして読み込む。
v1 と v2 で plan / pre_turn / ability_used などの
グローバル変数が混ざらないようにする。
"""
main_path = os.path.join(agent_dir, "main.py")
# cg を import できるようにする
if agent_dir not in sys.path:
sys.path.insert(0, agent_dir)
# main.py は deck.csv を相対パスで読み込むため、
# import 中だけカレントディレクトリを変更する
old_cwd = os.getcwd()
try:
os.chdir(agent_dir)
spec = importlib.util.spec_from_file_location(
module_name,
main_path
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
finally:
os.chdir(old_cwd)
return module.agent
def run_game(agent1, agent2, deck1, deck2):
"""1試合実行"""
env = make(
"cabt",
configuration={
"decks": [deck1, deck2]
},
debug=False,
)
env.run([agent1, agent2])
final_step = env.steps[-1]
reward1 = final_step[0]["reward"]
reward2 = final_step[1]["reward"]
return reward1, reward2
def main():
games = 100
print("=== Pokemon TCG AI Local Tournament ===")
print(f"Games: {games}")
print()
deck_v1 = load_deck(V1_DIR)
deck_v2 = load_deck(V2_DIR)
v1_win = 0
v2_win = 0
draw = 0
error = 0
start_time = time.time()
for game_no in range(1, games + 1):
try:
#
# 重要:
# 毎試合モジュールを読み直します。
#
# main.py 内には plan / pre_turn / ability_used
# という状態変数があるためです。
#
agent_v1 = load_agent(
V1_DIR,
f"agent_v1_game_{game_no}"
)
agent_v2 = load_agent(
V2_DIR,
f"agent_v2_game_{game_no}"
)
# 先攻・後攻による偏りを減らすため、
# 奇数試合と偶数試合で左右を入れ替える
if game_no % 2 == 1:
reward1, reward2 = run_game(
agent_v1,
agent_v2,
deck_v1,
deck_v2
)
v1_reward = reward1
v2_reward = reward2
else:
reward1, reward2 = run_game(
agent_v2,
agent_v1,
deck_v2,
deck_v1
)
v2_reward = reward1
v1_reward = reward2
if v1_reward > v2_reward:
v1_win += 1
result = "v1 WIN"
elif v2_reward > v1_reward:
v2_win += 1
result = "v2 WIN"
else:
draw += 1
result = "DRAW"
print(
f"{game_no:3d}/{games} "
f"{result:7s} "
f"| v1={v1_win:3d} "
f"v2={v2_win:3d} "
f"draw={draw:3d}"
)
except Exception as e:
error += 1
print(
f"{game_no:3d}/{games} ERROR: "
f"{type(e).__name__}: {e}"
)
elapsed = time.time() - start_time
valid_games = v1_win + v2_win + draw
print()
print("================================")
print("RESULT")
print("================================")
print(f"Total : {games}")
print(f"Completed : {valid_games}")
print(f"Errors : {error}")
print()
print(f"v1 wins : {v1_win}")
print(f"v2 wins : {v2_win}")
print(f"Draw : {draw}")
if valid_games > 0:
print()
print(
f"v1 win rate : "
f"{v1_win / valid_games * 100:.1f}%"
)
print(
f"v2 win rate : "
f"{v2_win / valid_games * 100:.1f}%"
)
print()
print(f"Time : {elapsed:.1f} sec")
if __name__ == "__main__":
main()
3. Dockerで100試合させます
~/pokemon-ai で、
cd ~/pokemon-ai
実行します。
docker run --rm \
--platform linux/amd64 \
-v "$PWD":/work \
-w /work \
gcr.io/kaggle-images/python \
python tournament.py
最初はこのまま100試合でOKです。
結果はこんな感じになります。
=== Pokemon TCG AI Local Tournament ===
Games: 100
1/100 v1 WIN | v1= 1 v2= 0 draw= 0
2/100 v2 WIN | v1= 1 v2= 1 draw= 0
3/100 v1 WIN | v1= 2 v2= 1 draw= 0
...
100/100 v2 WIN | v1= 47 v2= 53 draw= 0
================================
RESULT
================================
Total : 100
Completed : 100
Errors : 0
v1 wins : 52
v2 wins : 48
Draw : 0
v1 win rate : 52.0%
v2 win rate : 48.0%
Time : 54.4 sec
4. なぜ単純に env.run([agent, agent]) にしないのか
ここはかなり重要です。
今回のサンプル main.py には、
plan = AttackPlan()
pre_turn = 0
ability_used = False
というグローバルな状態変数があります。
さらにターンが変わるたびに、
global plan
global pre_turn
global ability_used
として状態を変更しています。
したがって、単純に
env.run([agent, agent])
とすると、場合によっては両プレイヤーが同じPythonモジュールの状態を共有する可能性があります。
今回の tournament.py は、v1とv2を別モジュールとしてロードしています。さらに各試合でロードし直すため、前の試合の plan 等が次の試合に残らないようにしています。
これは勝率測定ではかなり大事です。
5. 最初の実験
まずはまだ v2/main.py を変更しない状態で100試合してください。
つまり、
v1 = 元AI
v2 = 元AI
です。
理想的には試行回数を増やすと、
v1 約50%
v2 約50%
に近づきます。
いきなりAIを改造するより、まずこれで対戦・集計システム自体が公平そうか確認します。
となるか試すのが非常に分かりやすいです。
まずはv1/v2を変更せずに100試合させてみてください。実行結果を貼ってもらえれば、50:50からどれくらい偏っているか確認したうえで、最初のv2改造を一緒に1か所だけ行いましょう。
最初に改造する場所
このAIは攻撃対象を
def pokemon_score(...)
で決めています。
例えば
score += len(pokemon.energies) * 150
となっています。
つまり
エネルギーが1枚付いている相手
↓
150点
私は最初にここを変更したいです。
例えば
score += len(pokemon.energies) * 250
にすると
エネルギーを大量につけた相手を積極的に倒すAI
になります。
Submit Angenをクリック

「Upload Submission」が表示される場合
そこに、
Macで作成した
submission.tar.gz
をドラッグ&ドロップします。
そのまま提出できます。

アップロードが完了しました。

アップロードすると、すぐにほかのデッキとの対戦結果が確認できます。
Episodesのアイコンをクリック
★
ただし、V2をそのまま提出するには、submission.tar.gz をV2から作り直す必要があります。
今の状況はこうなっています。
pokemon-ai/
├── agents/
│ ├── v1/
│ │ ├── main.py
│ │ ├── deck.csv
│ │ └── cg/
│ │
│ └── v2/
│ ├── main.py ← ← これを提出したい
│ ├── deck.csv
│ └── cg/
方法①(おすすめ・Macだけで完結)
agents/v2 に移動して、
cd ~/pokemon-ai/agents/v2
次に
tar -czf submission.tar.gz main.py deck.csv cg
これで
submission.tar.gz
が作られます。
中身は
submission.tar.gz
├── main.py
├── deck.csv
└── cg/
となり、提出形式と同じになります。
===============================
ここまで来れば、Kaggle上だけでなくMac上で自由にAIを改良できるようになります。
自分で環境を作りながら、ブログを書いたので、途中抜けがあるかもしれませんが、
皆さんのお役に立てればと思います。
おつかれさまでした。
余裕があれば、AIを使ってさらに勝率をあげられるようにしていきます。



コメント