
技術書典でゲットした書籍を読みました。
ポケモン、ゲットだぜ!!(古)
購入のキッカケ
11月16日(日)に行った技術書典でゲットしたこちらの書籍(購入ページはリンク先)。
購入のキッカケは、下記2点です。
・そもそも筆者が無類のポケモン好きであるため
・ブースに置いてあったサンダーが可愛かったから
特に私はポケモン対戦を見るのが大変好きであり、対戦においてAIを活用したこちらの書籍はとても興味深かったです。
なお、過去ポケモンの大会において、AI(ポケモンバトルスコープ)が活用されたことがあります。
詳しくは、下記の動画をご参照ください。
ざっくり内容
ポケモン対戦の戦略を、AIに考えさせるといった内容です。
「なんだ、そんなシンプルなことか…」と思われた方、大変甘いです。
ポケモン対戦の戦略をAIに考えさせることは、大きな苦労を伴います。
その大きな理由は下記です。
・そもそもポケモンの数が膨大である(なんと1,000以上)
・わざの種類も多い(なんと600以上)
・ポケモンに固有の特殊能力である「とくせい」も考慮しなければならない(例:てんねん…相手の能力変化を無視する)
・ランダム性が大きい(例:同じ威力でのダメージ幅、クリティカルヒットの確率)
・カスタマイズ性が大きい(例:いわゆる「努力値」…どの能力を注力して育てるかはプレイヤーしだい)
(※数値や情報は、こちらの書籍の前提となっているスカーレット・バイオレット環境を前提)
書いているだけでも頭が痛くなるこの要素を考慮して、実際の対戦において勝率を上げられるよう、AI活用にチャレンジ…
こちらの書籍は、そんなポケモンとITを愛する方による、努力と試行の軌跡です。
ここがスゴい!ポイント3選
個人的にスゴい!と思ったポイント3選です。
①実際の大会で使えるようチャレンジされている
②全身全霊をもってAIやテクノロジーを利用し尽くしている
③しっかり結果を出されている
まず①ですが、仲間大会(仲間内かつ複数人で行う非公式の大会)での活用を念頭に置かれています。
その大きな理由は、制約が大きく情報量が比較的少ない特殊ルール(例:1つの技しか使えない制約ルール)であるためと推察しますが…。
それでも依然として準備しなければいけない情報は膨大であり、それでいて実戦の場で試された事実に、ただただ感服しました。
そして②ですが、勝つための戦略を「本気」で考えられています。
詳細は書籍を見て欲しいですが、例えば対戦のシミュレーションを数十万回行い、計算に数時間を要した…といった記載が当たり前のように出てきます。
要は「ちょっと試してみました」というレベルを大きく逸脱している、ということです。
さらに③ですが、ちゃんと結果を出されているのも素晴らしいと思いました。
運の要素を大きく含むポケモン対戦では、常勝はいかなる強者でもあり得ないです。
そのようなポケモン対戦において、高い勝率を叩き出されたことは、本当に凄いです。
ちょっと試してみた
ということで、少しばかり試してみました。
ケースは「ゆびをふる1on1」。
「ゆびをふる」のみ使える特殊ルールで強いポケモンを考えてもらいます。
(シンプルにするため、「もちもの」は考慮しないものとしました)
↓下記、Deep Researchに考えさせた最低限のPythonコードです。
わざの種類もダメージ計算もポケモンも、何もかも最低限の内容です。
import random
import math
# ゆびをふるの技リストは約200技
# ここでは簡易化(本番版では全技リストを入れる)
METRONOME_MOVES = ["Flamethrower", "Thunderbolt", "Surf", "Ice Beam", "Solar Beam"]
# ダメージ計算(簡易)
def calc_damage(level, atk, df):
base = random.randint(5, 20)
modifier = random.uniform(0.85, 1.0)
return math.floor(((2 * level / 5 + 2) * base * atk / df) / 50 + 2) * modifier
class Pokemon:
def __init__(self, name):
self.name = name
self.hp = 200
self.atk = random.randint(50, 150)
self.df = random.randint(50, 150)
def metronome(self, other):
move = random.choice(METRONOME_MOVES)
dmg = calc_damage(50, self.atk, other.df)
other.hp -= dmg
return move, dmg
def battle(p1, p2):
log = []
turn = 1
while p1.hp > 0 and p2.hp > 0:
move, dmg = p1.metronome(p2)
log.append(f"T{turn} {p1.name} → {p2.name} : {move} ({dmg:.1f})")
if p2.hp <= 0:
return p1.name, log
move, dmg = p2.metronome(p1)
log.append(f"T{turn} {p2.name} → {p1.name} : {move} ({dmg:.1f})")
turn += 1
return (p2.name, log) if p1.hp <= 0 else (p1.name, log)
if __name__ == "__main__":
wins = {}
trials = 100
POKES = ["ミュウ", "ピッピ", "ラッキー", "バリヤード", "ピクシー"]
for _ in range(trials):
a, b = random.sample(POKES, 2)
w, log = battle(Pokemon(a), Pokemon(b))
wins[w] = wins.get(w, 0) + 1
print("勝率:")
for k, v in wins.items():
print(f"{k}: {v}勝")ここから何度かAIと対話を続け、5匹のポケモンでの勝率を出してみました。
採用手法は、モンテカルロ法(確率でランダムにサンプルを生成し、繰り返して平均を取る方法)。
コードは大変長いので割愛しますが、結果はこちら↓
Win Rate Ranking:
1. Munkidori: 81.24%
2. Wigglytuff: 62.86%
3. Clefable: 59.91%
4. Clefairy: 23.06%
5. Jigglypuff: 22.93%上から、Munkidori(マシマシラ)、Wigglytuff(プクリン)、Clefable(ピクシー)、Clefairy(ピッピ)、Jigglypuff(プリン)。
…うーん、正直、個人的な感覚とは少し違う結果です。
(ピクシーがプクリンより勝率が低いのは、ちょっと考えにくい…)
書籍に載っている通り、まずは手作業での環境考察を行うのが良いと感じました。
まとめ
新たな書籍との出会いは、新たな情報との出会いだと再認識しました!
今後も、技術書はただ読んで「ふーん」と終わらせるのではなく、手を動かすところまでセットにして、自分の血肉にします!