Angriff gegen Verteidigung und wer ist der Gewinner? [geschlossen]

12

Ich bin gerade dabei, ein neues einfaches Spiel für Handys zu erstellen, und habe mehrere Tage mit dem folgenden Teil verbracht.

Nehmen wir zur Vereinfachung an, ich habe zwei Kämpfer. Das einzige Attribut von ihnen ist Angriff und Verteidigung. Bei den ersten Angriffen kommt es nur auf den Angriff und die Verteidigung des Gegners an. Und umgekehrt.

Sie haben keine Ausrüstung, Gegenstände, Ausdauer oder Gesundheit. Nur Angriff gegen Verteidigung.

Beispiel:

  • Kämpfer 1:

    Angriff: 50, Verteidigung: 35

  • Kämpfer 2:

    Angriff 20, Verteidigung: 80

Der Kampfprozess wird nur ein einziger Angriff sein, der den Gewinner bestimmt. Also keine Mehrfachangriffe oder Runden. Ich möchte es nicht deterministisch machen, sondern eine leichte Version von Unerwartetem hinzufügen. Ein Kämpfer mit geringerem Angriff kann einen anderen Kämpfer mit höherer Verteidigung gewinnen (aber natürlich nicht jedes Mal)

Meine erste Idee war, es linear zu machen und einen einheitlichen Zufallszahlengenerator aufzurufen.

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

Beispiel: Angriff 50 und Verteidigung 80: Der angreifende Kämpfer hat ungefähr 38% zu gewinnen. Es scheint mir jedoch, dass das Unerwartete zu weit ist und die schlimmsten Kämpfer viel gewinnen werden.

Ich habe mich gefragt, wie Sie an ähnlichen Situationen gearbeitet haben.

PS Ich habe in dieser QnA und anderen Quellen viel gesucht und ähnliche Fragen gefunden, die für SE als zu weit gefasst bezeichnet wurden. Aber diese hatten viele Attribute, Waffen, Gegenstände, Klassen usw., die es zu kompliziert machen könnten. Ich denke, meine Version ist viel einfacher, um sie in den QnA-Stil der SE zu integrieren.

Tasos
quelle
1
Was sind die Fälle, die Sie suchen? Welchen Wertebereich für Angriff und Verteidigung betrachten Sie und sollten zwei Zahlen in diesen Bereichen jemals ein festes Ergebnis haben? Kann zum Beispiel ein Kämpfer mit Angriff 10 einen Kämpfer mit Verteidigung 90 besiegen?
Niels
@ user2645227 Ich könnte sagen, dass der Bereich zwischen 1 und 400 liegt. Nein, ich möchte keine deterministischen Entscheidungen treffen und die Möglichkeit geben, 1 anzugreifen, um die Verteidigung 400 zu gewinnen, aber in wirklich seltenen Fällen.
Tasos
1
Wenn Sie also Att (min) -def (max) und Att (max) -def (min) nehmen, erhalten Sie einen Bereich von 800 von -400 bis +400. Sie möchten, dass Ihr zufälliger Bereich den gesamten Bereich abdeckt. Verteidigung - Angriff gibt Ihnen einen Skalierungsspielraum in Form eines Schwellenwerts, den Sie treffen müssen, um zu gewinnen. Dies sollte die Zufälligkeit etwas reduzieren. Um die Ergebnisse weiter zu zentralisieren, können Sie Philipps Beispiel verwenden oder in beliebigen Würfeln herumfummeln, bis Sie die gewünschte Kurve erreichen.
Niels

Antworten:

24

Wenn Sie möchten, dass Ihre Kampfergebnisse vorhersehbarer, aber nicht vollständig deterministisch sind, haben Sie das Beste aus n System verwenden.

Wiederholen Sie die Kampfzeiten n(wobei neine ungerade Zahl sein sollte) und erklären Sie den Kämpfer zum Sieger, der häufiger gewonnen hat. Je höher Ihr Wert für ndie weniger überraschenden Gewinne und Verluste, die Sie haben werden.

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
} 

Dieses System funktioniert nur in dem speziellen Fall, in dem ein Kampf ein einfaches binäres Ergebnis von Gewinn oder Verlust ist. Wenn ein Kampf komplexere Ergebnisse hat, z. B. wenn der Gewinner immer noch einige Trefferpunkte verliert, je nachdem, wie knapp der Gewinn war, funktioniert dieser Ansatz nicht mehr. Eine allgemeinere Lösung besteht darin, die Art und Weise zu ändern, in der Sie Zufallszahlen generieren. Wenn Sie mehrere Zufallszahlen generieren und dann den Durchschnitt ermitteln, werden die Ergebnisse nahe der Mitte des Bereichs gruppiert und extremere Ergebnisse sind seltener. Beispielsweise:

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

wird eine Verteilungskurve wie folgt haben:

Verteilung von 3d20 / 3

(Bild mit freundlicher Genehmigung von anydice - ein wirklich nützliches Werkzeug zum Entwerfen von Spielmechanikformeln, die Zufälligkeit beinhalten, nicht nur für Tabletop-Spiele)

In meinem aktuellen Projekt verwende ich eine Hilfsfunktion, mit der eine beliebige Stichprobengröße festgelegt werden kann:

double averagedRandom(int averageness) {
     double result = 0.0;
     for (var i = 0; i < averageness; i++) {
         result += Random();
     }
     return result / (double)averageness;
}
Philipp
quelle
Scheint ein besserer Ansatz zu sein. Eine Frage. Sollten Sie in der FunktiongemitteltRandom3 () +anstelle von verwenden, *oder ich habe falsch verstanden, was es tut?
Tasos
@Tasos ja, sollte + sein, nicht *. Ich habe auch eine Zufallsfunktion, die mehrere Stichproben multipliziert. Dies gibt Ihnen eine Zufallszahlenfunktion mit einer starken Neigung zu niedrigeren Werten, was in einigen Situationen auch nützlich sein kann.
Philipp
1
Ich werde die Frage 1-2 Tage offen halten und wenn ich keine andere Antwort habe, werde ich Ihre auswählen. Ich habe es positiv bewertet, möchte aber auch die Möglichkeit für andere Antworten geben, wenn Sie nichts dagegen haben.
Tasos
Ich denke, diese Antwort erhält bereits genug Stimmen, so dass diese Antwort als Antwort markiert werden kann: P
Hamza Hasan
1
Ich wäre auch neugierig, wenn einige Leute alternative Ansätze finden würden. Eine Person hat diese Antwort abgelehnt. Vielleicht möchten sie eine Alternative anbieten.
Philipp
8

Dies ist, was ich verwendet habe, um den Gewinner eines Kampfes in meinem Lords of Conquest Imitator-Applet zu bestimmen. In diesem Spiel gibt es ähnlich wie in Ihrer Situation nur einen Angriffswert und einen Verteidigungswert. Die Wahrscheinlichkeit, dass der Angreifer gewinnt, ist umso größer, je mehr Punkte der Angreifer hat und je weniger Punkte die Verteidigung hat. Bei gleichen Werten ergibt sich eine 50% ige Chance, dass der Angriff erfolgreich ist.

Algorithmus

  1. Wirf eine zufällige Münze.

    1a. Köpfe: Verteidigung verliert einen Punkt.

    1b. Schwänze: Köpfe verlieren einen Punkt.

  2. Wenn sowohl die Verteidigung als auch der Angreifer noch Punkte haben, fahren Sie mit Schritt 1 fort.

  3. Wer 0 Punkte hat, verliert den Kampf.

    3a. Angreifer auf 0: Angriff fehlgeschlagen.

    3b. Verteidigung bis 0: Angriff erfolgreich.

Ich habe es in Java geschrieben, aber es sollte leicht in andere Sprachen übersetzbar sein.

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

Ein Beispiel

Angenommen, att = 2 und def = 2, um sicherzustellen, dass die Wahrscheinlichkeit 50% beträgt.

Der Kampf wird in maximal zwei n = att + def - 1Münzwürfen oder 3 in diesem Beispiel entschieden (hier ist es im Wesentlichen das Beste aus 3). Es gibt 2 n mögliche Kombinationen von Münzwürfen. Hier bedeutet "W", dass der Angreifer den Münzwurf gewonnen hat, und "L" bedeutet, dass der Angreifer den Münzwurf verloren hat.

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

Der Angreifer gewinnt in 4/8 oder 50% der Fälle.

Die Mathematik

Die mathematischen Wahrscheinlichkeiten, die sich aus diesem einfachen Algorithmus ergeben, sind komplizierter als der Algorithmus selbst.

Die Anzahl der Kombinationen, bei denen genau x Ls vorhanden sind, ergibt sich aus der Kombinationsfunktion:

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

Der Angreifer gewinnt, wenn zwischen 0und att - 1Ls liegen. Die Anzahl der Gewinnkombinationen entspricht der Summe der Kombinationen von 0bis att - 1, einer kumulativen Binomialverteilung:

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

Die Wahrscheinlichkeit des Angreifers zu gewinnen , ist w geteilt durch 2 n , eine kumulative binomische Wahrscheinlichkeit:

p = w / 2^n

Hier ist der Code in Java, um diese Wahrscheinlichkeit für beliebige attund defWerte zu berechnen :

/**
 * 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;
}

Testcode:

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));
}

Ausgabe:

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

Beobachtungen

Die Wahrscheinlichkeiten sind, 0.0wenn der Angreifer 0Punkte hat, 1.0wenn der Angreifer Punkte hat, aber die Verteidigung 0Punkte hat, 0.5wenn die Punkte gleich sind, weniger als 0.5wenn der Angreifer weniger Punkte als die Verteidigung hat und größer als 0.5wenn der Angreifer mehr Punkte als die Verteidigung hat .

Mit att = 50und def = 80musste ich auf BigDecimals umschalten, um einen Überlauf zu vermeiden, aber ich erhalte eine Wahrscheinlichkeit von etwa 0,0040.

Sie können die Wahrscheinlichkeit näher an 0,5 bringen, indem Sie den attWert so ändern , dass er der Durchschnitt der Werte attund defist. Att = 50, Def = 80 wird (65, 80), was eine Wahrscheinlichkeit von 0,1056 ergibt.

rgettman
quelle
1
Ein weiterer interessanter Ansatz. Der Algorithmus könnte auch leicht visualisiert werden, was ziemlich aufregend aussehen könnte.
Philipp
5

Sie können den Angriff durch eine Zufallszahl ändern, die aus einer Normalverteilung stammt. Auf diese Weise wird das Ergebnis meistens das sein, was Sie erwarten, aber gelegentlich verliert ein höherer Angriff gegen eine niedrigere Verteidigung oder ein niedrigerer Angriff gewinnt gegen eine höhere Verteidigung. Die Wahrscheinlichkeit, dass dies geschieht, wird geringer, wenn der Unterschied zwischen Angriff und Verteidigung zunimmt.

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

Die Funktion norm(x0, sigma)gibt einen Float zurück, der aus einer bei x0 zentrierten Normalverteilung mit Standardabweichung Sigma abgetastet wurde. Die meisten Programmiersprachen bieten eine Bibliothek mit einer solchen Funktion. Wenn Sie sie jedoch selbst erstellen möchten, schauen Sie sich diese Frage an . Sie müssten Sigma so einstellen, dass es sich „richtig anfühlt“, aber ein Wert von 10-20 könnte ein guter Anfang sein.

Für einige Sigma-Werte att1 - def2sieht die Wahrscheinlichkeit eines Sieges für einen bestimmten so aus: Wahrscheinlichkeit des Sieges

rauben
quelle
Es kann auch erwähnenswert sein, dass normalverteilte Werte keine tatsächlichen Grenzen haben. Wenn Sie also normalverteilte Zufallswerte in einem Spiel verwenden, kann es sinnvoll sein, das Ergebnis zu klemmen, um die unwahrscheinliche, aber nicht unmögliche Situation zu vermeiden, dass sehr extreme Werte erzeugt werden könnte das Spiel brechen.
Philipp