攻撃対防御と勝者は誰ですか?[閉まっている]


12

私はモバイルで新しいシンプルなゲームを作成している最中で、次の部分に数日間費やしました。

簡単にするために、私には2人の戦闘機がいるとしましょう。それらの唯一の属性は、攻撃と防御です。最初の攻撃で重要なのは、彼の攻撃と相手の防御です。およびその逆。

彼らは装備、アイテム、スタミナ、健康を持っていません。ただ攻撃対防御。

例:

  • 戦闘機1:

    攻撃:50、防御:35

  • 戦闘機2:

    攻撃20、防御:80

戦闘プロセスは勝者を決定する単一の攻撃になります。したがって、複数の攻撃やラウンドはありません。私はそれを確定的にしたくはありませんが、予想外のライトバージョンを追加します。攻撃力の低い戦闘機は、防御力の高い別の戦闘機に勝つことができます(もちろん、毎回ではありません)

私の最初のアイデアは、それを線形にし、均一な乱数ジェネレータを呼び出すことでした。

If Random() < att1 / (att1 + def2) {
    winner = fighter1
} else {
    winner = fighter2
} 

攻撃50と防御80の例では、攻撃側の戦闘機は約38%の勝率になります。しかし、予想外のことは遠すぎて最悪の戦闘機がたくさん勝つように思えます。

似たような状況でどのように取り組んできたのかと思っていました。

PS私はこのQnAやその他の情報源で多くのことを検索しましたが、SEには広すぎるとして言及されている同様の質問を見つけました。しかし、それらには多くの属性、武器、アイテム、クラスなどがあり、複雑すぎる可能性があります。私のバージョンは、SEのQnAスタイルに合わせてはるかにシンプルだと思います。


1
あなたが探しているケースは何ですか?攻撃と防御のどの範囲の値を調べていますか。これらの範囲内の2つの数値が固定された結果をもたらす必要がありますか?たとえば、攻撃10の戦闘機は防御90の戦闘機を倒すことができますか?
Niels

@ user2645227範囲は1〜400です。いいえ、決定論的な決定をしたくなく、1を攻撃する可能性を与えて防御に勝ちます400ですが、本当にまれなケースです。
Tasos

1
したがって、Att(min)-def(max)およびAtt(max)-def(min)を使用すると、-400から+400までの800の範囲が得られます。ランダムな範囲で範囲全体をカバーする必要があります。防御-攻撃は、勝利するためにヒットする必要があるしきい値の形でスケーリングマージンを提供します。これにより、ランダムさが少し減少します。結果をさらに集中化するために、フィリップスの例を使用したり、探している曲線にぶつかるまで任意のダイスをいじったりできます。
Niels

回答:


24

戦闘結果をより予測可能にしたいが完全に確定的ではないようにしたい場合は、最高のnシステムを使用してください。

戦闘n時間を繰り返し(n不均一な数になるはずです)、より頻繁に勝利した勝者を戦闘員に宣言します。nサプライズの勝利と損失が少なくなるほど、価値が大きくなります。

const int FIGHT_REPETITONS = 5 // best 3 of 5. Adjust to taste.

int fighter1wins = 0;
int fighter2wins = 0;

for (int i = 0; I < FIGHT_REPETITONS; I++) {

    If (Random() < att1 / (att1 + def2)) {
        fighter1wins++;
    } else {
        fighter2wins++;
    } 

}

If (fighter1wins > fighter2wins) {
    winner = fighter1
} else {
    winner = fighter2
} 

このシステムは、戦いが勝敗の単純なバイナリ結果である特別な場合にのみ機能します。勝利がどれだけ近いかに応じて、勝者がまだいくつかのヒットポイントを失うときのように、戦闘がより複雑な結果をもたらすとき、このアプローチはもはや機能しません。より一般的な解決策は、乱数の生成方法を変更することです。複数の乱数を生成してから平均を取ると、結果は範囲の中心付近に集まり、極端な結果はまれになります。例えば:

double averagedRandom3() {
    return (Random() + Random() + Random()) / 3.0;
}

このような分布曲線になります:

3d20 / 3の分布

(画像はanydiceの好意による -テーブルトップゲームだけでなく、ランダム性を伴うゲームメカニック式を設計するための本当に便利なツール)

現在のプロジェクトでは、任意のサンプルサイズを設定できるヘルパー関数を使用しています。

double averagedRandom(int averageness) {
     double result = 0.0;
     for (var i = 0; i < averageness; i++) {
         result += Random();
     }
     return result / (double)averageness;
}

より良いアプローチのようです。一つの質問。averagedRandom3()関数では、+代わりに使用する必要があります*か、それが何をするのかを誤解していますか?
Tasos、2016

@Tasosはい、*ではなく+にする必要があります。複数のサンプルを乗算するランダム関数もあります。これにより、低い値に強いバイアスをかけた乱数関数が得られます。これは、状況によっては役立つ場合もあります。
Philipp

1
質問は1〜2日間開いたままにします。他に回答がない場合は、あなたの質問を選択します。私は賛成していますが、よろしければ、他の回答にもチャンスを与えたいと思います。
Tasos、2016

P:私はこの答えはすでに答えとしてそれをマークするために、この回答適格になり、十分な票を得ると思います
ハムザ・ハサン

1
また、別のアプローチを考え出す人がいるかどうかも知りたいです。一人がこの回答に反対票を投じました。多分彼らは代わりのものを提供したいと思います。
Philipp

8

これが、征服王の模倣者アプレットでの戦いの勝者を決定するために使用したものです。このゲームでは、あなたの状況と同様に、攻撃値と防御値しかありません。攻撃者が勝つ確率は、攻撃者が持っているポイントが多いほど高く、防御が持っているポイントが少ないほど高く、攻撃が成功する確率は50%です。

アルゴリズム

  1. ランダムなコインを裏返します。

    1a。ヘッズ:防御はポイントを失います。

    1b。テール:ヘッドはポイントを失います。

  2. それでも防御側と攻撃側の両方にポイントがある場合は、ステップ1に戻ります。

  3. 0ポイントに下がった人は誰もが戦闘に敗北します。

    3a。攻撃者の0まで:攻撃は失敗します。

    3b。防御を0に下げる:攻撃は成功します。

私はJavaで作成しましたが、他の言語に簡単に翻訳できるはずです。

Random rnd = new Random();
while (att > 0 && def > 0)
{
    if (rnd.nextDouble() < 0.5)
        def--;
    else
        att--;
}
boolean attackSucceeds = att > 0;

たとえば、確率が50%であることを確認するために、att = 2およびdef = 2であるとします。

バトルは最大n = att + def - 1コインフリップで決定されます。この例では3です(ここでは基本的に3のベストです)。コインフリップの可能な組み合わせは2 n通りあります。ここで、「W」は攻撃者がコインフリップに勝ったことを意味し、「L」は攻撃者がコインフリップに負けたことを意味します。

L,L,L - Attacker loses
L,L,W - Attacker loses
L,W,L - Attacker loses
L,W,W - Attacker wins
W,L,L - Attacker loses
W,L,W - Attacker wins
W,W,L - Attacker wins
W,W,W - Attacker wins

攻撃者は4/8、またはケースの50%で勝利します。

数学

この単純なアルゴリズムから生じる数学的確率は、アルゴリズム自体よりも複雑です。

正確にx Lsが存在する組み合わせの数は、組み合わせ関数によって与えられます。

C(n, x) = n! / (x! * (n - x)!)

0att - 1Lの間に攻撃者が勝利します。入賞組合せの数はからの組合せの和に等しい0介してatt - 1、累積二項分布:

    (att - 1)
w =     Σ     C(n, x)
      x = 0

攻撃者が勝つ確率は、wを2 nで割った累積2項確率です。

p = w / 2^n

以下は、任意attdef値のこの確率を計算するJavaのコードです。

/**
 * Returns the probability of the attacker winning.
 * @param att The attacker's points.
 * @param def The defense's points.
 * @return The probability of the attacker winning, between 0.0 and 1.0.
 */
public static double probWin(int att, int def)
{
    long w = 0;
    int n = att + def - 1;
    if (n < 0)
        return Double.NaN;
    for (int i = 0; i < att; i++)
        w += combination(n, i);

    return (double) w / (1 << n);
}

/**
 * Computes C(n, k) = n! / (k! * (n - k)!)
 * @param n The number of possibilities.
 * @param k The number of choices.
 * @return The combination.
 */
public static long combination(int n, int k)
{
    long c = 1;
    for (long i = n; i > n - k; i--)
        c *= i;
    for (long i = 2; i <= k; i++)
        c /= i;
    return c;
}

テストコード:

public static void main(String[] args)
{
    for (int n = 0; n < 10; n++)
        for (int k = 0; k <= n; k++)
            System.out.println("C(" + n + ", " + k + ") = " + combination(n, k));

    for (int att = 0; att < 5; att++)
        for (int def = 0; def < 10; def++)
            System.out.println("att: " + att + ", def: " + def + "; prob: " + probWin(att, def));
}

出力:

att: 0, def: 0; prob: NaN
att: 0, def: 1; prob: 0.0
att: 0, def: 2; prob: 0.0
att: 0, def: 3; prob: 0.0
att: 0, def: 4; prob: 0.0
att: 1, def: 0; prob: 1.0
att: 1, def: 1; prob: 0.5
att: 1, def: 2; prob: 0.25
att: 1, def: 3; prob: 0.125
att: 1, def: 4; prob: 0.0625
att: 1, def: 5; prob: 0.03125
att: 2, def: 0; prob: 1.0
att: 2, def: 1; prob: 0.75
att: 2, def: 2; prob: 0.5
att: 2, def: 3; prob: 0.3125
att: 2, def: 4; prob: 0.1875
att: 2, def: 5; prob: 0.109375
att: 2, def: 6; prob: 0.0625
att: 3, def: 0; prob: 1.0
att: 3, def: 1; prob: 0.875
att: 3, def: 2; prob: 0.6875
att: 3, def: 3; prob: 0.5
att: 3, def: 4; prob: 0.34375
att: 3, def: 5; prob: 0.2265625
att: 3, def: 6; prob: 0.14453125
att: 3, def: 7; prob: 0.08984375
att: 4, def: 0; prob: 1.0
att: 4, def: 1; prob: 0.9375
att: 4, def: 2; prob: 0.8125
att: 4, def: 3; prob: 0.65625
att: 4, def: 4; prob: 0.5
att: 4, def: 5; prob: 0.36328125
att: 4, def: 6; prob: 0.25390625
att: 4, def: 7; prob: 0.171875
att: 4, def: 8; prob: 0.11328125

観察

確率は0.0、攻撃者が0ポイントを持っている場合、攻撃者がポイントを1.0持っているが防御が0ポイントがある0.5場合、ポイントが等しい0.5場合、攻撃者が防御よりもポイントが少ない場合よりも少なく、攻撃者が防御よりも0.5ポイントが多い場合よりも大きい。

とを使用するatt = 50def = 80BigDecimalオーバーフローを回避するためにs に切り替える必要がありましたが、約0.0040の確率が得られます。

あなたは、変更することにより、0.5に近い確率作ることができるattの平均値であることををattしてdef値。Att = 50、Def = 80は(65、80)となり、0.1056の確率になります。


1
別の興味深いアプローチ。アルゴリズムは簡単に視覚化することもでき、非常にエキサイティングに見えます。
Philipp

5

正規分布からサンプリングされた乱数によって攻撃を変更できます。このようにすると、ほとんどの場合、結果は期待どおりになりますが、時折、高い防御力が低い防御力に負けたり、低い防御力が高い防御力に勝ったりします。攻撃と防御の違いが大きくなるにつれて、この事態が発生する可能性は低くなります。

if (att1 + norm(0, sigma) - def2 > 0) {
  winner = fighter1;
}
else {
  winner = fighter2;
}

この関数norm(x0, sigma)は、x0を中心とする正規分布からサンプリングされた浮動小数点数を標準偏差シグマで返します。ほとんどのプログラミング言語は、そのような関数を備えたライブラリを提供しますが、それを作りたい場合は、この質問をご覧ください。シグマを「適切に感じる」ように調整する必要がありますが、10〜20の値から始めるのが良いでしょう。

いくつかのシグマ値の場合、特定の勝利の確率はatt1 - def2次のようになります。 勝利の確率


正規分布値には実際の境界がないことも指摘する価値があるかもしれません。そのため、ゲームで正規分布ランダム値を使用する場合、非常に極端な値が生成される可能性は高いが不可能ではない状況を回避するために結果をクランプすることは理にかなっています。ゲームを壊すかもしれません。
Philipp
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.