ハピナスオーバーフローチャレンジ

 

初めに

本日はWCS2023の開幕日です!大会に参加している人、配信で観戦している人も多いかと思います。横浜では様々なイベントがあり非常に盛り上がっていますが、ポケモンSVでもイベントが行われています。ハピナスレイドです。
ハピナスレイドは3度目ですが、テラピースをたくさん集めることができ、とても重要になっています。

友達3人でレイドを回していたところ、A+6のテツノカイナのてだすけワイルドボルトでB-6の水ハピナスを倒せないという事件が起きました。

さすがにおかしいと思い、オーバーフローの可能性を計算してみることにしました。

 

 

目的

ハピナスを倒せなかった原因がオーバーフローの可能性があるか確認

オーバーフローの発生条件の確認

 

前提条件

ハピナスレイド☆5

相手:ハピナス レベル75

☆5レイドバトルでは、相手HPが20倍

味方:

①テツノカイナ レベル100 A特化@命の珠 はらだいこ ワイルドボルト

ブラッキー レベル100 いやなお てだすけ

ブラッキー レベル100 いやなお てだすけ

 

観測データ(体感)

・A+6のテツノカイナのてだすけワイルドボルトでB-6の水ハピナスに6割

・A+6のテツノカイナのてだすけワイルドボルトでB-6の水ハピナスに1割

・A+6のテツノカイナのてだすけワイルドボルトでB-6の水ハピナスに10割 数回

・A+6のテツノカイナのてだすけワイルドボルトでB-6の等倍ハピナスに10割 10回以上

 

アプローチ

ハピナスのB実数値ごとにダメージ計算を行い、考えられる最小ダメージ最大ダメージからオーバーフローの値の予想

・16bitの最大値65536

・16bit符号あり最大値32768

・すばやさのオーバーフローの値10000

あたりから調査

 

結果

実数値

ハピナスのB実数値


以上からわかるように、ハピナスのB実数値は2倍以上の差がある。

したがって個体値によって、ダメージも2倍以上変動することになる。

 

テツノカイナのA実数値は416である。

 

ハピナスのHP

レイドバトルでは、☆の数によって倍率が変わる。☆5ハピナスでは、おおむね10000弱であり、防御実数値ほど個体値の影響を受けない。

 

ダメージ計算

以上の実数値に沿って、A+6のテツノカイナのてだすけワイルドボルトをB-6の水ハピナスに使った時のダメージ計算を行った。

最低乱数をだったとしても必ず倒せることが分かる。しかし実際には、想定よりも小さいダメージしか与えられていないときがある。

やはり、オーバーフローかその他のバグが発生している。

10000や32768でオーバーフローだとすると、水ハピナス以外の等倍ハピナスに対してもオーバーフローが容易に起こるので、観測結果と矛盾する。

おおよそ50000以上100000以下でオーバーフローが発生している可能性が高い。

 

65536でオーバーフローと仮定したときのダメージは次のようになる。

個体値0補正値なし、個体値0上昇補正の時にオーバーフローが発生し、ダメージが4桁になりレイドハピナスが倒せない場合があることが分かる。

他の個体値の時についても計算すると、実数値が20~23の時に乱数によってはハピナスを倒せない。

ちなみに実数値20~24で防御ランクが-6の時、1/4にしてあと切り捨てた値は一緒になるので、ダメージ計算結果も同じである。

実数値20~24の時のダメージを全乱数について、下に示す。



乱数によって、オーバーフローしハピナスダメージは1割~8割になる場合がある。

これは観測データとも矛盾しないので、ダメージが65536でオーバーフローすると考えてもおかしくない。

 

確率計算

ハピナスレイドでオーバーフローが発生する確率を考える。

ハピナスの防御実数値が20~23の時11/16で発生するので、防御実数値が20~23になる確率を考える。

防御実数値は性格補正と個体値で決まる。

防御性格補正の割合は

上昇補正:4/25

補正なし:17/25

下降補正:4/25

である。

上昇補正時、個体値が2以下の時、つまり3/32で実数値が20~23になる。

補正なし時、個体値が5以下の時、つまり6/32で実数値が20~23になる。

下降補正時、個体値が4以上9以下の時、つまり6/32で実数値が20~23になる。

したがって、ハピナスの実数値が20~23になる確率は、

4/25*3/32+17/25*6/32+4/25*6/32=(12+102+24)/800=138/800

であり、17%程度である。

したがって、オーバーフローが発生する確率は

138/800*11/16=0.1186

であり、12%程度である。

 

観測データを考えると、数回の試行で2回オーバーフローが発生したことになるので、発生しすぎなようにも思える。

しかし、パオジアンのつらら落としを数回打って2回外すことなんていくらでもあり、珍しいことでもない。

 

したがって、ポケモンSVにおいて、ダメージが65536を超えるとオーバーフローが発生するということは十分考えられ、否定はできないと結論付ける。

実際の仕様については調べてないので、文献あれば教えてください。

 

終わりに

疑問に思った通りに、確かにオーバーフローが発生してそうなことは分かった。

しかし、夏休み初日にWCSを配信で見ながらこんなことをやってて良いのか非常に怪しいし、せめて横浜の現地イベントに参加するべきではなかったのかと思う。

次回は結果を残して構築記事を書きたいです。

 

遺伝的アルゴリズムを用いた適正テラスタイプの考察【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世代ではアウトプットも少しはしていこうと思います。 次回はもっと図を入れたりして読みやすくします。

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

【新春!おみくじバトル】ゆびふり集計結果の考察【最終1704 77位】

f:id:yuyutech0:20220116233529p:plain
初めに

ポケモン公式大会「新春!おみくじバトル」に参加しました!

この大会、ゆびをふるのみの運ゲ大会かと思いきや、見せ合い63ダイマックスありの謎ルールだったので考察して臨みました。

いろいろ考察しましたが、上位の人の考察結果とほとんど同じだったので割愛します。

 

結果は29勝13敗 レート1704 77位と結果だけは自分の中では初の2桁順位と良いものでした。

しかし、ほぼ正解を見つけられてはいたので、運が良ければもっと行けたのではないかと思い、ゆびをふるで出た技を集計して「自分は運が悪かっただけだ!」と言い訳することにしました。

 

その前に、使用した構築を紹介します。

 

目次

 

構築紹介

ノワール

f:id:yuyutech0:20220116233013p:plain

ヨノワール@たべのこし

性格:ゆうかん

特性:プレッシャー

H151(244)-A167(252+)-B157(12)-C85-D155-S58(-)

HAベースで毒のダメージを減らすために8n-1

16n+7でもあるので、あと8振っても食べ残し回復量は増えない

この調整がミスったのが一番の敗因。記事の後半でいろいろ言っているけどHBにしておくべきでした。

HAかHBか悩んだけど、相手を削った方が強いと思ったのでHAにした。一応B特化ゲンガーをポルターガイストで一撃で落とせる。

でもほとんどゆびをふれなかったからA特化があまり活きませんでした。

 

ゲンガー

f:id:yuyutech0:20220116232613p:plain

ゲンガー@くろいヘドロ

性格:のんき

特性:のろわれボディ

H167(252)-A85-B123(252+)-C152(4)-D95-S117(-)

HBぶっぱ

とくになし

ほかの人と同じ

 

オーロンゲ

f:id:yuyutech0:20220116233056p:plain

オーロンゲ@ジャポのみ

性格:ゆうかん

特性:わるいてぐせ

H183(100)-A189(252+)-B105(156)-C115-D95-S72(-)

HAベース物理耐久優先

定数ダメをあまり受けないように、食べ残しの回復割合を減らさないように

8n-1であり、6n-3でゴツゴツメットのダメージもそこそこに16n+7でどちらかというと回復効率も良いほう

オーロンゲも結局たべのこし持ったり、ゲンガー相手だったりでゆびふりにくいので、HBでもよかったかもしれないです。

でも、ヨノワールゲンガーにタイプ相性有利で一発逆転もまれにあったので、HAベースなのは正解だったと思う。

謎の調整をしたけど、ほぼ意味ありません。物理耐久に寄せたのはよかったはず。

 

カビゴン

f:id:yuyutech0:20220116232758p:plain

カビゴン@こだわりハチマキ

性格:ゆうかん

特性:めんえき

H235-A178(252+)-B1117(252)-C85-D132(4)-S45(-)

ハチマキの方が強いと思ったけど、上位ではゴツゴツメットの方が多かったかも。よくわからない。

選出2回

 

トゲキッス


f:id:yuyutech0:20220116232933p:plain

トゲキッス@こだわりメガネ

性格:れいせい

特性:てんのめぐみ

H192(252)-A70-B116(12)-C189(252+)-D135-S90(-)

ひと枠余ったので適当に入れました。ピクシーとかの方が良かったかも。

選出0回

 

ルージュラ

f:id:yuyutech0:20220116232823p:plain

ルージュラ@ゴツゴツメット

性格:のんき

特性:かんそうはだ

H172(252)-A70-B95(252+)-C136(4)-D115-S103(-)

唯一無二の特性かんそうはだ。1/8回復だからひそかに流行るんじゃないかと思って、その対策として採用したけどそんなことなかった。

一回ぐらい活躍させたくてニョロトノ入りに投げたけど、あめふらしじゃなくて悲しかった。

 

選出

カビゴンを2回、ルージュラを1回出した以外はゲンガーヨノワールオーロンゲ

 

集計結果

さて、本題のゆびをふるの集計です。

f:id:yuyutech0:20220117002250p:plain

注目すべきは、一撃必殺の被弾率です。3回0.4%

自分が当てたのは0回なので当たりすぎなような気がします。

ゆびをふるで出る技は555種類くらいらしく、一撃必殺技は「つのドリル」「ハサミギロチン」「じわれ」「ぜったいれいど」の4種類。このうち2種類はノーマルタイプの技であり、基本的に場に出ているのがゴーストタイプなので当たらないとすると、ゆびをふって一撃必殺を命中させる確率は

0.0072/2*0.3=0.001 0.1%程度

744回しかゆびをふられていないので3回被弾は多い。運が悪い。

これでは定性的なので、検定して定量的に運が悪いことを評価する。

 

検定

ということで、統計の検定を行う。

帰無仮説:自身が受けるゆびをふるによる一撃必殺技を被弾する確率は0.001である

対立仮説:自身が受けるゆびをふるによる一撃必殺技を被弾する確率は0.001より大きい

とする。

有意水準は0.05とする。

 

ゆびをふるの結果として、一撃必殺技が出て命中するかしないかの2通りを結果として考えるとN回ゆびをふってk回一撃必殺技を命中させる確率P(N,k)は二項分布として次のように表せる。

f:id:yuyutech0:20220116235047p:plain

N=744,p=0.001を代入するとP(744,k)は次のようになる。

f:id:yuyutech0:20220117000142p:plain

3回以上一撃必殺技が命中する確率は約0.039である。

0.039<0.05より

744回ゆびをふられて一撃必殺を3回被弾することは滅多に起こり得ない(5%も起こりえない)ので帰無仮説を棄却する。

したがって、自身の剣盾が壊れていることを主張します。

 

というわけで、検定のやり方があってるかわかりませんが、運が悪かったのは示せたと思います。

自分は運が悪かっただけだ!本来ならもっと上を目指せたんだ!2桁前半はいけたはずなんだ

 

そもそもおみくじバトルなので運が良いやつを決める大会なので運が悪い時点で負けです。

 

3連休ずっとゆびをふっている時点でやばいのに、終わってもゆびをふるについて考えているのはやばいのでもう終わります。

ありがとうございました。

最後に一緒に考察してくれた友人、本当に運が悪いのか検証してくれた友人、3連休にもかかわらず必死にゆびをふってくれた大会参加者には感謝いたします。

不幸自慢はやめましょう。