プログラミングでポケモンのダメージ計算をしてみよう

adventar.org

Pokémon RNG Advent Calendar 2017 7日目です。
昨日は ポケモンの実数値や努力値をプログラミングで計算 するという内容を書きました。
今日はダメージ計算をしてみようと思います。

この記事はプログラミングがわからない人には難しい内容ですが、ダメージ計算アプリがどういう苦しみを経てあの計算結果を算出しているかが少しでも伝わればいいと思っています。
プログラムが長々と書かれているところは読み飛ばして、最後のところだけ読んでもらっても構いません 🙏

ダメージ計算してみる

ダメージは以下の計算式で求めることができます。

{(攻撃側のレベル × 2 ÷ 5 + 2)× 技の威力 × 攻撃側の能力値 ÷ 防御側の能力値 ÷ 50 + 2}×乱数(85~100)÷ 100 ×タイプ一致×タイプ相性1×タイプ相性2
ダメージ計算と実数値【マンキーでもわかるポケモン講座】 - ポケモンORASのトリセツ

ただの四則演算ですね。紙に書いて、丁寧に値を当てはめれば計算できそうです。
これをプログラムに落とし込んでいきましょう、 Swift というプログラミング言語です。

// (攻撃側のレベル × 2 ÷ 5 + 2)
let level = floorf((Float(攻撃側のレベル * 2) / 5.0) + 2.0)

// { level × 技の威力 × 攻撃側の能力値 ÷ 防御側の能力値 ÷ 50 + 2}
let baseDamage = floorf(
    floorf(
        (level * 技の威力 * 攻撃側の能力値) / 防御側の能力値
    ) / 50
) + 2.0

// baseDamage × 乱数(85~100)÷ 100 ×タイプ一致×タイプ相性1×タイプ相性2
var damages: [Int] = []
for i in 0...15 {
    var damage: Float = floorf(baseDamage * Float(85 + i) / 100.0)

    if タイプ一致攻撃 {
        damage = 五捨六入(damage * 1.5)
    }

    // タイプ相性は割愛 (今回のダメージ計算では使わないため)

    damages.append(Int(damage))
}

難しいかもしれないですが、よくみるとなんとなく理解できると思います。
レベルの計算をして、基本ダメージの計算をして、乱数を 0.85~1.00 、タイプ相性を計算という流れになっています。

パラメータを埋め込んでみる

上記の計算式に実際に数値を埋め込んでみましょう。
ピカチュウがメガトンパンチをメガガルーラに打つ、というケースで考えてみます。

let 攻撃側のレベル = 50
let 技の威力: Float = 80
let 攻撃側の能力値: Float = 75
let 防御側の能力値: Float = 120
let タイプ一致 = false

let level = floorf((Float(攻撃側のレベル * 2) / 5.0) + 2.0)
let baseDamage = floorf(
    floorf(
        (level * 技の威力 * 攻撃側の能力値) / 防御側の能力値
    ) / 50
) + 2.0

var damages: [Int] = []
for i in 0...15 {
    var damage: Float = floorf(baseDamage * Float(85 + i) / 100.0)

    if タイプ一致 { // タイプ不一致
        damage = 五捨六入(damage * 1.5)
    }

    damages.append(Int(damage))
}

print(damages)
// => [20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24]

計算結果が 20~24 になりました。
ダメージ計算Z での計算結果を見てみると、見事一致しています。

f:id:star__hoshi:20171207024614p:plain

これで一般的なダメージ計算はできるようになりました 👏

おやこあいに対応してみる

次は特性: おやこあいに対応してみましょう。

おやこあいの仕様として

  • 2回攻撃
  • 2回目はダメージが 0.25 倍される

という 2 つの要素があります。
この要素をプログラムに落とし込む必要があります。

func calc(count: Int) -> [Int] {
    let 攻撃側のレベル = 50
    let 技の威力: Float = 80
    let 攻撃側の能力値: Float = 145
    let 防御側の能力値: Float = 60
    let タイプ一致 = true
    let おやこあい = true

    let level = floorf((Float(攻撃側のレベル * 2) / 5.0) + 2.0)
    let baseDamage = floorf(
        floorf(
            (level * 技の威力 * 攻撃側の能力値) / 防御側の能力値
            ) / 50
        ) + 2.0

    var damages: [Int] = []
    for i in 0...15 {
        var damage: Float = floorf(baseDamage * Float(85 + i) / 100.0)

        if タイプ一致 { // タイプ一致
            damage = 五捨六入(damage * 1.5)
        }

        if おやこあい && count == 2 {
            damage = 五捨六入(damage * 0.25)
        }

        damages.append(Int(damage))
    }

    return damages
}

var damages = calc(count: 1)
let parentalBondDamages = calc(count: 2)
// 2 つの計算結果を合計する
for (i, d) in parentalBondDamages.enumerated() {
    damages[i] += d
}
print(damages)
// => [136, 139, 140, 142, 144, 146, 147, 150, 150, 151, 154, 155, 157, 159, 161, 162]

同じ計算処理を2回しないといけないので、 func という関数を利用して再利用できるようにしています。
この関数を2回実行して、2回目はダメージを0.25倍しています。

計算結果は 136 ~ 162 と出ました。これもダメージ計算Zと一致していますね。

f:id:star__hoshi:20171207024555p:plain

おやこあいグロウパンチに対応する

次はグロウパンチの要素を入れて考えてみましょう。
グロウパンチ

  • 2回目のダメージ計算の時にはランク補正が1上昇する

という仕様が追加されます。これに対応できるようにしましょう。

func calc(count: Int) -> [Int] {
    let 攻撃側のレベル = 50
    let 攻撃側の能力値: Float = 145
    let 防御側の能力値: Float = 60
    let タイプ一致 = false
    let グロウパンチ = true
    let おやこあい = true
    let 技の威力: Float
    if おやこあい && グロウパンチ && count == 2 {
        技の威力 = 40 * 1.5
    } else {
        技の威力 = 40
    }

    let level = floorf((Float(攻撃側のレベル * 2) / 5.0) + 2.0)
    let baseDamage = floorf(
        floorf(
            (level * 技の威力 * 攻撃側の能力値) / 防御側の能力値
        ) / 50
    ) + 2.0

    var damages: [Int] = []
    for i in 0...15 {
        var damage: Float = floorf(baseDamage * Float(85 + i) / 100.0)

        if タイプ一致 { // タイプ不一致
            damage = 五捨六入(damage * 1.5)
        }

        if おやこあい && count == 2 {
            damage = 五捨六入(damage * 0.25)
        }

        damages.append(Int(damage))
    }

    return damages
}

var damages = calc(count: 1)
let parentalBondDamages = calc(count: 2)
for (i, d) in parentalBondDamages.enumerated() {
    damages[i] += d
}
print(damages)
// => [51, 51, 52, 52, 53, 53, 55, 55, 55, 56, 56, 57, 58, 59, 59, 60]

どんどん複雑になっていきますね。
おやこあい + グロウパンチ + 2回目の時だけ技の威力が 1.5 倍されるようになりました。

ダメージ計算Z とも一致しています。

f:id:star__hoshi:20171207024537p:plain

おわり

今回は「おやこあいグロウパンチ」に特化したダメージ計算式をプログラムで書きました。

おやこあいの対応ですらプログラムは複雑です。
実際のポケモン対戦では特性はポケモンによって違うし、テクニシャンで技の威力が60以下だったら... というような分岐が大量に発生していくことになります。

特性や技、道具、ポケモンによる条件の違いなどを1つ1つ真心をこめて定義しているのがダメージ計算アプリです。
これらの条件を意識しつつ人間が計算するのはかなり難しいので、ポケモン廃人の皆さんはアプリを使って効率的にダメージ計算を行いましょう。

ダメージ計算Z for ポケモン ウルトラサンムーン

ダメージ計算Z for ポケモン ウルトラサンムーン

  • Kensuke Hoshikawa
  • ユーティリティ
  • 無料

もっと興味がある人は

このライブラリで抽象的なダメージ計算ができるようになっています。

github.com

let pikachu = ...
let メガガルーラ = ...
let battle = Battle(attackPokemon: pikachu, defensePokemon: メガガルーラ)
print(battle.calculate())

未実装のものも多いですが、これだけでダメージ計算ができます 💪