simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Hearthstone: ハースストーンのスプリットダメージの期待値 その2

下記の続きです。

simplestar-tech.hatenablog.com

ダメージ回数が2の場合のスプリットダメージについて、様々な盤面における
カードごとの撃破率をみていきます。

■問題1
場に2枚のカードがあり
すべてのカードのライフが1だった場合
★答え1
いずれのカードも撃破率は 1 です。

すべてのカードのライフ合計がダメージ回数以下の場合は無条件で 1 です。

■問題2
場に3枚のカードがあり
すべてのカードのライフが1だった場合
★答え2
いずれのカードも撃破率は 2/3 です。

前回調べた通り、ダメージ回数が1だった場合は撃破率 1/3 でした。
今回は 2/3 の確率で生き残った後、場のカード数が 3 から 2 に減った状態で
ダメージを与えられる確率は 1/2 です。
2/3 * 1/2 という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
1/3 + 2/3 * 1/2 = 2/3

■問題3
場に2枚のカードがあり
一方のカードのライフが1の場合
一方のカードのライフが2の場合
★答え3
カードのライフが1のカードの撃破率は 3/4
カードのライフが2のカードの撃破率は 1/4

カードのライフが1のカードをAとします。
Aはダメージ回数1のときに1/2で撃破される可能性があることは前回示しました。
1/2の確率で生き残った後、場のカード数が2から変動しない状態で
ダメージを与えられる可能性は 1/2 です。
1/2 * 1/2 という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
1/2 + 1/2 * 1/2 = 3/4

カードのライフが2のカードをBとします。
Bはダメージ回数1のときに 0 で撃破される可能性がないことは前回示しました。
1/2 でダメージ回数1のダメージを受けた後、場のカード数が2から変動しない状態で
1/2 でダメージ回数2のダメージを受けた場合にのみ撃破されます。
1/2 でダメージ回数1のダメージを受けなかった後、場のカード数が 2 から 1 に減った状態で
この時ダメージ回数よりもライフ数の方が大きいため無条件で撃破率は 0 です。
1/2 * 1/2 + 0という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
0 + (1/2 * 1/2 + 0) = 1/4

■問題4
場に3枚のカードがあり
一枚目のカードのライフが1の場合
二枚目のカードのライフが2の場合
三枚目のカードのライフが2の場合
★答え4
一枚目のカードのライフが1のカードの撃破率は 5/9
ニ枚目のカードのライフが2のカードの撃破率は 1/9
三枚目のカードのライフが2のカードの撃破率は 1/9

カードのライフが1のカードをAとします。
Aはダメージ回数1のときに1/3で撃破される可能性があることは前回示しました。
2/3の確率で生き残った後、場のカード数が3から変動しない状態で
ダメージを与えられる可能性は 1/3 です。
2/3 * 1/3 という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
1/3 + 2/3 * 1/3 = 5/9

カードのライフが2のカードをBとします。
Bはダメージ回数1のときに 0 で撃破される可能性がないことは前回示しました。
1/3 でダメージ回数1のダメージを受けた後、場のカード数が3から変動しない状態で
1/3 でダメージ回数2のダメージを受けた場合にのみ撃破されます。
2/3 でダメージ回数1のダメージを受けなかった後、
場のカード数が 3 から変動しない状態のになる確率が 1/2
場のカード数が 3 から 2 に減った状態のになる確率が 1/2
いずれの場合もダメージ回数よりもライフ数の方が大きいため無条件で撃破率は 0 です。
1/3 * 1/3 + 0という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
0 + (1/3 * 1/3 + 0) = 1/9

■問題5
場に4枚のカードがあり
一枚目のカードのライフが1の場合
ニ枚目のカードのライフが1の場合
三枚目のカードのライフが2の場合
四枚目のカードのライフが2の場合
★答え4
一枚目のカードのライフが1のカードの撃破率は 11/24
ニ枚目のカードのライフが1のカードの撃破率は 11/24
三枚目のカードのライフが2のカードの撃破率は 1/16
四枚目のカードのライフが2のカードの撃破率は 1/16

カードのライフが1のカードをAとします。
Aはダメージ回数1のときに1/4で撃破される可能性があります。(前回求めた式より)
3/4の確率で生き残った後、
場のカード数が 4 から変動しない状態になる確率は 2/3 (A以外のカード数が3で、ライフ1より大きいカードが2枚あるので)
場のカード数が 4 から 3 に減った状態になる確率が 1/3(A以外のカード数が3で、ライフ1のカードが1枚あるので)
ダメージ回数2のダメージを受ける可能性はそれぞれのケースにおいて 1/4 と 1/3 です。
場のカード数が 4 になっている時にダメージ回数2の攻撃で撃破される確率と
場のカード数が 3 になっている時にダメージ回数2の攻撃で撃破される確率について
撃破率を出すため足し合わせます。
2/3 * 1/4 + 1/3 * 1/3 という割合で撃破されることが考えられます。
ダメージ回数が1のときに撃破される確率と
ダメージ回数が2のときに撃破される確率について
撃破率を出すため足し合わせます。
1/4 + 3/4 * (2/3 * 1/4 + 1/3 * 1/3) = 11/24

カードのライフが2のカードをBとします。
Bはダメージ回数1のときに 0 で撃破される可能性はない。(前回求めた式より)
1/4 でダメージ回数1のダメージを受けた後、場のカード数が4から変動しない状態で
1/4 でダメージ回数2のダメージを受けた場合にのみ撃破されます。
それ以外ではダメージ回数よりもライフ数の方が大きいため無条件で撃破率は 0 です。
撃破率を出すため足し合わせます。
0 + (1/4 * 1/4 + 0) = 1/16

ここまで見てきて、撃破率に関わるパラメータには次のものがあることがわかります。
カードのライフ life
場のカードの数 numCard
場におけるライフ1のカードの数 numLife1Card

これらを利用して、ダメージ回数が2の時についてのカードごとの撃破率の計算式は次のとおりです。

        public void CalcuP(int[] cardLives, int damageCount, float[] defeatRates)
        {
            Dictionary<int, float> backNumber = new Dictionary<int, float>();
            int numCards = cardLives.Length;
            for (int i = 0; i < numCards; i++)
            {
                defeatRates[i] = 0;
                int life = cardLives[i];
                if (backNumber.ContainsKey(life))
                {
                    defeatRates[i] = backNumber[life];
                }
                else
                {
                    float p = 0;
                    if (1 == damageCount)
                    {
                        p = _CalcuOneShot(numCards, life);
                    }
                    else if (2 == damageCount)
                    {
                        p = _CalcuTwoShot(cardLives, i);
                    }
                    defeatRates[i] = p;
                    backNumber[life] = defeatRates[i];
                }
            }
        }

        private float _CalcuTwoShot(int[] cardLives, int i)
        {
            int life = cardLives[i];
            if (3 > life)
            {
                if (2 == life)
                {
                    float p1 = 1 / (float)(cardLives.Length);
                    float p2 = p1;
                    return p1 * p2;
                }
                else
                {
                    float p1 = _CalcuOneShot(cardLives.Length, life);
                    float p2_1 = 1 - p1;
                    int numlife1Card = _GetNumLife1(cardLives, i);
                    float p2 = 0;
                    if (0 < cardLives.Length - 1)
                    {
                        float p2_2 = numlife1Card / (float)(cardLives.Length - 1);
                        float p2_3 = 1 - p2_2;
                        float p2_4 = _CalcuOneShot(cardLives.Length - 1, life);
                        p2 = p2_1 * (p2_3 * p1 + p2_2 * p2_4);
                    }
                    return p1 + p2;
                }
            }
            else
            {
                return 0;
            }
        }

        private static int _GetNumLife1(int[] cardLives, int idx)
        {

            int numlife1Card = 0;
            for (int i = 0; i < cardLives.Length; i++)
            {
                if (1 == cardLives[i] && i != idx)
                {
                    ++numlife1Card;
                }
            }
            return numlife1Card;
        }

        private float _CalcuOneShot(int numCards, int life)
        {
            float p = 1;
            if (1 == life)
            {
                p = 1 / (float)numCards;
            }
            else
            {
                p = 0;
            }
            return p;
        }
    }

上の式を使って
場にカードが5枚あり、すべてのカードのライフが1のとき
それぞれのカードの撃破率が 0.4 と出ました。

行って欲しい計算としては
1/5 + 4/5 * (0 * 1/5 + 1 * 1/4) = 2/5
つまり 0.4 が正解です。

合ってますね。

最終的にはシミュレータを作って
おおよそ一致するのか確認する予定です。
続いて、難しくなってくるダメージ回数が3の場合について見ていきましょう。

simplestar-tech.hatenablog.com