遺伝的アルゴリズムを用いた適正テラスタイプの考察【SV発売前考察】

マイニンテンドーストアより

初めに

とうとう明日はポケットモンスター スカーレット・ヴァイオレットの発売日です!

第9世代のパルデア地方では、「テラスタル」という特殊な要素があります。

ポケモンはそれぞれのタイプとは別にテラスタイプという第3のタイプを持っています。

バトル中に一度だけ自身のタイプをテラスタイプに書き換えるということができます。

例えば、かくとうタイプが4倍弱点であるバンギラスがゴーストテラスタルになることによって、かくとうタイプの技を受けなくなるというとても強力なものとなっています。 攻撃面においても、テラスタイプに加えて元のタイプの技にもタイプ一致が乗るので強力です。

ポケモン公式YouTubeより
ということで、第9世代が始まる前にテラスタルの考察をすることにしました!!

考察目的

第9世代序盤では、各ポケモンの強いテラスタイプが何なのか、それが分かるとバトルに勝てると思います。 特に環境の回らない序盤では、強みを押し付ける方が強い。

ということで、各ポケモンごとの全体的に強いテラスタイプを求めることを目標とします。

ポケモンについてはほとんど情報がないため、内定ポケモンのみを対象とし、その中から独断と偏見で20匹選びました。 これらのポケモンについて適正テラスタイプを求めます。

対象ポケモン

  1. ドラパルト
  2. カイリュー
  3. サザンドラ
  4. バンギラス
  5. ギャラドス
  6. リザードン
  7. ゲンガー
  8. ジバコイル
  9. ハッサム
  10. カバルドン
  11. パルシェン
  12. ミミッキュ
  13. キノガッサ
  14. ラッキー
  15. ルカリオ
  16. ウォッシュロトム
  17. ヒートロトム
  18. ニンフィア
  19. エーフィ
  20. ストリンダー

考察方法

手法としてはタイトルにある通り遺伝的アルゴリズム(GA)を用います。

※以下、筆者はGAを詳しく学んでいるわけではないので雰囲気で書いています。

今回の探索範囲は、20匹18タイプで

 \displaystyle
18^{20} = 1.2\times 10^{25}

通りであり、有限個で離散的なので遺伝的アルゴリズムで、いけそうだと考えました。

今回用いた方法を以下に示す。

  1. 上記の20匹のポケモン持った、Playerを100人用意

  2. それぞれのPlayerに対して、20匹のポケモンそれぞれにランダムでテラスタイプをあてる

  3. 敵役のPlayer(環境)も同様に100人用意しランダムでテラスタイプをあてる。

  4. Playerと敵役のPlayerを総当たり(100×100通り)で戦わせ、各Playerの勝利数を得る

  5. 勝利数の高いPlayerを親として、各Playerを更新(遺伝)ここがめっちゃ適当

  6. 上記の4,5を繰り返す

  7. 10回に1回適役のPlayerにPlayerを上書きする(環境の更新)

  8. 1000回繰り返す

  9. 最終的なPlayerの上位10人のテラスタイプから最も多いものを最適なテラスタイプとした。

こんな感じ↓

typename_list=["無","闘","飛","毒","地","岩","虫","霊","鋼","炎","水","草","電","超","氷","竜","悪","妖"]
players = [Player() for _ in range(PLAYER_NUM)]
for player in players:
  typelist = random.choices(typename_list, k=POKEMON_NUM)    
  player.set_teratype(typelist)
  
for gen in range(GENERATION_NUM):
  print("===generation {} ===".format(gen))
  if gen%10==0:
    # 10回に1回対戦相手を更新
    opponent_players = copy.deepcopy(players)
  #print(opponent_players[0].teratypelist)

  players_score = [0 for _ in range(PLAYER_NUM)]
  for i in range(PLAYER_NUM):
    for j in range(i, PLAYER_NUM):
      wins = battle(players[i], opponent_players[j])
      players_score[i] += wins[0]/6.0/6.0
      #players_score[j] += wins[1]/6.0/6.0
      #print(i,j,wins)
  players_score = np.array(players_score)

  # 経過の表示
  score_rank = np.argsort(players_score)[::-1]
  if gen%10==9:
    K=10
    print(score_rank[:K])
    print(players_score[score_rank[:K]])
    print("ドラパ,ガッサ,リザ,ギャラ,バンギ,ミトム,ルカ,ゲン,ハサム,カバ,パル,ジバコ,エフィ,ニンフ,ヒトム,ミミ,カイ,サザン,ラキ,ストリ")
    for idx in score_rank[:K]:
      print(players[idx].teratypelist)

  # 遺伝上位30%
  # 10%-30%-40%-20%
  topnum = int(PLAYER_NUM*0.1)
  midnum = int(PLAYER_NUM*0.4)
  btmnum = int(PLAYER_NUM*0.8)
  idennum = int(PLAYER_NUM*0.3)
  oya_teratypes_tmp = [players[idx].teratypelist for idx in score_rank[:idennum]]
  oya_teratypes = [[oya_teratypes_tmp[idx][num] for idx in range(idennum)] for num in range(POKEMON_NUM)]
  
  # 遺伝
  for idx in score_rank[topnum:midnum]:
    typelist = [random.choice(oya_teratypes[num]) for num in range(POKEMON_NUM)]
    for _ in range(int(POKEMON_NUM*0.2)):
      rand_idx = np.random.randint(0,POKEMON_NUM*2)
      if rand_idx<POKEMON_NUM:
        typelist[rand_idx] = random.choice(typename_list)
      players[idx].set_teratype(typelist)
  
  # ランダム
  for idx in score_rank[midnum:btmnum]:
    typelist = random.choices(typename_list, k=POKEMON_NUM)
    players[idx].set_teratype(typelist)
  
  # テラスタイプ=タイプ
  for idx in score_rank[btmnum:]:
    typelist = [random.choice(Pokemon(i).data["types"]) for i in range(POKEMON_NUM)]
    players[idx].set_teratype(typelist)

Playerと敵役のPlayerの対戦では、かなり簡略化した対戦とした。

・それぞれのプレイヤーはランダムに6匹を選び、6対6の総当たりで勝ち数の割合を対戦結果とした。

・それぞれのポケモン同士の対戦では、お互いに得たえるダメージ割合で多いほうが勝利とした。

・この時、攻撃側は使う技とテラスタル化の有無を選べ、防御側はテラスタル化の有無を選ぶことができる。 タイプを考慮した火力指数と耐久指数の比を与えるダメージレートとし、ダメージレートを最大化する攻撃側の行動に対して、最小化できる行動を防御側はとる。

def max_damage_value(atk_poke, atk_poke_teratype, def_poke, def_poke_teratype):
  normaltype_values = []
  teratype_values = []

  for move in atk_poke.data["moves"]:
    if move.name == "テラバースト":
      move.move_type = atk_poke_teratype
    if move.move_class =="物理":
      atk_value = atk_poke.data["status"][1]*move.power # 火力指数
      def_value = def_poke.data["status"][2]*def_poke.data["status"][0] # 耐久指数
    elif move.move_class =="特殊":
      atk_value = atk_poke.data["status"][3]*move.power # 火力指数
      def_value = def_poke.data["status"][4]*def_poke.data["status"][0] # 耐久指数

    cpb = Type.compatibility(Type.ret_type_num(move.move_type), [Type.ret_type_num(t) for t in def_poke.data["types"]])
    if move.move_type in atk_poke.data["types"]:
      cpb *= 1.5
      if atk_poke_teratype in atk_poke.data["types"]:
        cpb *=4/3
    normal_value = atk_value/def_value * cpb
    normaltype_values.append(normal_value)

    cpb = Type.compatibility(Type.ret_type_num(move.move_type), [Type.ret_type_num(def_poke_teratype)])
    if move.move_type in atk_poke.data["types"]:
      cpb *= 1.5
      if atk_poke_teratype in atk_poke.data["types"]:
        cpb *=4/3
    tera_value = atk_value/def_value * cpb
    teratype_values.append(tera_value)

    return min(max(normaltype_values), max(teratype_values))

def battle(player1, player2):
  # それぞれ6体選んで対戦
  player1_idxs = random.sample([n for n in range(POKEMON_NUM)], 6)
  player2_idxs = random.sample([n for n in range(POKEMON_NUM)], 6)
  
  wins = [0,0]
  for idx1 in player1_idxs:
    for idx2 in player2_idxs:
      poke1 = player1.pokelist[idx1]
      poke1_teratype = player1.teratypelist[idx1]
      poke2 = player2.pokelist[idx2]
      poke2_teratype = player2.teratypelist[idx2]
      damage_value1 = max_damage_value(poke1, poke1_teratype, poke2, poke2_teratype)
      damage_value2 = max_damage_value(poke2, poke2_teratype, poke1, poke1_teratype)

      if damage_value1 > damage_value2:
        wins[0] +=1
      elif damage_value1 < damage_value2:
        wins[1] +=1
    
  return wins

考察結果

求められた最適テラスタイプを以下に示す。

100世代目

  1. ドラパルト   → でんき
  2. カイリュー   → ノーマル
  3. サザンドラ   → ひこう
  4. バンギラス   → はがね
  5. ギャラドス   → ノーマル
  6. リザードン   → じめん
  7. ゲンガー    → ドラゴン
  8. ジバコイル   → でんき
  9. ハッサム    → じめん
  10. カバルドン   → どく
  11. パルシェン   → じめん
  12. ミミッキュ   → はがね
  13. キノガッサ   → じめん
  14. ラッキー    → くさ
  15. ルカリオ    → ドラゴン
  16. ウォッシュロトム→ むし
  17. ヒートロトム  → むし
  18. ニンフィア   → ドラゴン
  19. エーフィ    → ドラゴン
  20. ストリンダー  → でんき

考察

ロトムなどまだ最適化できていないように見える。

500世代目

  1. ドラパルト   → フェアリー
  2. カイリュー   → ノーマル
  3. サザンドラ   → はがね
  4. バンギラス   → はがね
  5. ギャラドス   → フェアリー
  6. リザードン   → フェアリー
  7. ゲンガー    → フェアリー
  8. ジバコイル   → でんき
  9. ハッサム    → じめん
  10. カバルドン   → じめん
  11. パルシェン   → みず
  12. ミミッキュ   → はがね
  13. キノガッサ   → フェアリー
  14. ラッキー    → フェアリー
  15. ルカリオ    → どく
  16. ウォッシュロトム→ フェアリー
  17. ヒートロトム  → ひこう
  18. ニンフィア   → ドラゴン
  19. エーフィ    → ドラゴン
  20. ストリンダー  → でんき

考察

・明らかにフェアリー鋼ドラゴンが多い。ドラゴンタイプが多いため?

1000世代目(結論)

  1. ドラパルト   → はがね
  2. カイリュー   → ノーマル
  3. サザンドラ   → あく
  4. バンギラス   → はがね
  5. ギャラドス   → こおり
  6. リザードン   → どく
  7. ゲンガー    → ドラゴン
  8. ジバコイル   → でんき
  9. ハッサム    → じめん
  10. カバルドン   → くさ
  11. パルシェン   → みず
  12. ミミッキュ   → みず
  13. キノガッサ   → フェアリー
  14. ラッキー    → フェアリー
  15. ルカリオ    → ドラゴン
  16. ウォッシュロトム→ みず
  17. ヒートロトム  → じめん
  18. ニンフィア   → ドラゴン
  19. エーフィ    → ドラゴン
  20. ストリンダー  → でんき

考察

・まず、ドラパルトのはがねテラスタルはとても良いと思う。苦手なフェアリータイプやミミッキュに勝てるようになる(はず)。

カイリューはタイプ一致しんそくが打てるようになるノーマルテラスタル。ノーマルテラスタルハチマキ神速強そう。

ギャラドスハッサムカバルドンなどは、技の範囲を広げている。

サザンドラジバコイルパルシェン、などは自タイプとテラスタイプが一致しており、通りの良いタイプ一致技を強力にしている。

・ゲンガー、ニンフィア、エーフィーなどはドラゴンテラスタルであり、今回多かったドラゴンタイプに強く、また耐性も多く使い勝手が良い?。

ヒートロトムのじめんテラスタル。??? バンギラスに強い??

まとめ

雰囲気でプログラムを書いて雰囲気でまとめてしまったが、やりたかったことができてよかったです。 発売前に間に合って良かった。

結果はそのまま対戦で使えるとは思わないが、注目する点などについては知見が得られたと思います。 先制技なども考慮してないので、ミミッキュは水テラスタルになってしまったが、普通にゴーストテラスタルが強そう。

文章を書くのは苦手だが、9世代ではアウトプットも少しはしていこうと思います。 次回はもっと図を入れたりして読みやすくします。

ここまで読んでくださり本当にありがとうございました。