Attack vs Defense e chi è il vincitore? [chiuso]


12

Sono in procinto di creare un nuovo gioco semplice per dispositivi mobili e ho trascorso diversi giorni nella parte seguente.

Per semplicità, diciamo che ho due combattenti. L'unico attributo di questi è Attacco e Difesa. Quando i primi attacchi, l'unica cosa che conta è l'attacco di lui e la difesa dell'avversario. E viceversa.

Non hanno equipaggiamento, oggetti, resistenza o salute. Just Attack vs Defense.

Esempio:

  • Combattente 1:

    Attacco: 50, Difesa: 35

  • Combattente 2:

    Attacco 20, Difesa: 80

Il processo di combattimento sarà solo un singolo attacco che determinerà il vincitore. Quindi, nessun attacco o round multipli. Non voglio renderlo deterministico, ma aggiungere una versione leggera di imprevisto. Un combattente con un attacco più basso sarà in grado di vincere un altro combattente con una maggiore difesa (ma ovviamente non tutte le volte)

La mia prima idea era di renderlo lineare e chiamare un generatore di numeri casuali uniforme.

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

Esempio con attacco 50 e difesa 80, il combattente attaccante avrà circa il 38% per vincere. Tuttavia, mi sembra che l'imprevisto sia troppo lontano e che i peggiori combattenti vinceranno molto.

Mi chiedevo come hai lavorato in situazioni simili.

PS Ho cercato molto in questo QnA e in altre fonti e ho trovato domande simili menzionate come troppo ampie per SE. Ma quelli hanno avuto molti attributi, armi, oggetti, classi ecc. Che potrebbero renderlo troppo complicato. Penso che la mia versione sia molto più semplice per adattarla allo stile QnA della SE.


1
Quali sono i casi che stai cercando? Quale intervallo di valori per l'attacco e la difesa stai osservando e due numeri in quegli intervalli dovrebbero mai avere un risultato fisso? Ad esempio, un combattente con attacco 10 può sconfiggere un combattente in difesa 90?
Niels,

@utente2645227 Potrei dire che l'intervallo è compreso tra 1 e 400. No, non voglio prendere decisioni deterministiche e dare la possibilità di attaccare 1 per vincere la difesa 400, ma in casi davvero rari.
Tasos,

1
Quindi se prendi Att (min) -def (max) e Att (max) -def (min) che ti dà un intervallo di 800 da -400 a +400. Vorrai che il tuo intervallo casuale copra l'intero intervallo. Difesa - L'attacco ti darà un margine di ridimensionamento sotto forma di una soglia che dovrai colpire per vincere. Ciò dovrebbe ridurre un po 'la casualità. Per centralizzare ulteriormente i risultati, puoi usare l'esempio di Philipps o armeggiare in qualsiasi punto fino a quando non colpisci la curva che stai cercando.
Niels,

Risposte:


24

Se vuoi che i risultati del tuo combattimento siano più prevedibili ma non completamente deterministici, usa il meglio di n sistema.

Ripeti i ntempi di combattimento (dove ndovrebbe essere un numero irregolare) e dichiara il combattente il vincitore che ha vinto più spesso. Maggiore è il tuo valore per nmeno sorprese e vittorie avrai.

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
} 

Questo sistema funziona solo nel caso speciale in cui un combattimento è un semplice risultato binario di vittoria o perdita. Quando un combattimento ha risultati più complessi, come quando il vincitore perde ancora alcuni punti ferita a seconda di quanto è stata vicina la vittoria, questo approccio non funziona più. Una soluzione più generale è quella di cambiare il modo in cui generi numeri casuali. Quando generi più numeri casuali e poi prendi la media, i risultati si raggrupperanno vicino al centro dell'intervallo e risultati più estremi saranno più rari. Per esempio:

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

avrà una curva di distribuzione come questa:

Distribuzione di 3d20 / 3

(per gentile concessione di anydice - uno strumento davvero utile per la progettazione di formule meccaniche di gioco che implicano casualità, non solo per giochi da tavolo)

Nel mio progetto attuale sto usando una funzione helper che consente di impostare una dimensione del campione arbitraria:

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

Sembra un approccio migliore. Una domanda. Nella funzione averagedRandom3 (), dovresti usare +invece di *o ho capito male cosa fa?
Tasos,

@Tasos sì, dovrebbe essere +, non *. Ho anche una funzione casuale che moltiplica più campioni. Questo ti dà una funzione di numero casuale con una forte propensione per valori più bassi, che può anche essere utile in alcune situazioni.
Philipp,

1
Terrò la domanda aperta per 1-2 giorni e se non ho un'altra risposta, sceglierò la tua. L'ho votato ma voglio dare anche la possibilità di altre risposte se non ti dispiace.
Tasos,

Penso che questa risposta abbia già abbastanza voti da rendere questa risposta idonea a contrassegnarla come risposta: P
Hamza Hasan

1
Sarei anche curioso di sapere se alcune persone escogitano approcci alternativi. Una persona ha votato in negativo questa risposta. Forse vorrebbero fornirne uno alternativo.
Philipp,

8

Questo è ciò che ho usato per determinare il vincitore di una battaglia nella mia applet di Lords of Conquest Imitator. In questo gioco, simile alla tua situazione, c'è solo un valore di attacco e un valore di difesa. La probabilità che l'attaccante vinca è maggiore maggiore è il numero di punti che l'attaccante ha, e minore è il numero di punti che la difesa ha, con valori uguali che valutano una probabilità del 50% dell'attacco riuscito.

Algoritmo

  1. Lancia una moneta casuale.

    1 bis. Teste: la difesa perde un punto.

    1b. Code: la testa perde un punto.

  2. Se sia la difesa che l'attaccante hanno ancora punti, torna al passaggio 1.

  3. Chiunque è a 0 punti perde la battaglia.

    3a. Attaccante fino a 0: l'attacco fallisce.

    3b. Difesa fino a 0: l'attacco ha successo.

L'ho scritto in Java, ma dovrebbe essere facilmente traducibile in altre lingue.

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

Un esempio

Ad esempio, supponiamo che att = 2 e def = 2, solo per assicurarsi che la probabilità sia del 50%.

La battaglia sarà decisa in un massimo di n = att + def - 1lanci di monete, o 3 in questo esempio (qui è essenzialmente il migliore dei 3). Esistono 2 n combinazioni possibili di lanci di monete. Qui, "W" significa che l'attaccante ha vinto il lancio della moneta, e "L" significa che l'attaccante ha perso il lancio della moneta.

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

L'attaccante vince in 4/8, ovvero il 50% dei casi.

La matematica

Le probabilità matematiche derivanti da questo semplice algoritmo sono più complicate dell'algoritmo stesso.

Il numero di combinazioni in cui esattamente x L è dato dalla funzione di combinazione:

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

L'attaccante vince quando ci sono tra 0e att - 1Ls. Il numero di combinazioni vincenti è uguale alla somma delle combinazioni da 0attraverso att - 1, una distribuzione binomiale cumulativa:

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

La probabilità di vincita l'attaccante è w diviso per 2 n , una probabilità cumulativa binomiale:

p = w / 2^n

Ecco il codice in Java per calcolare questa probabilità per arbitrari atte defvalori:

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

Codice di prova:

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

Produzione:

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

osservazioni

Le probabilità sono 0.0se l'attaccante ha 0punti, 1.0se l'attaccante ha punti ma la difesa ha 0punti, 0.5se i punti sono uguali, meno che 0.5se l'attaccante ha meno punti della difesa e maggiore rispetto 0.5se l'attaccante ha più punti della difesa .

Prendendo att = 50e def = 80, ho dovuto passare a BigDecimals per evitare trabocco, ma ho una probabilità di circa 0,0040.

È possibile avvicinare la probabilità a 0,5 modificando il attvalore in modo che sia la media dei valori atte def. Att = 50, Def = 80 diventa (65, 80), che produce una probabilità di 0,1056.


1
Un altro approccio interessante. L'algoritmo potrebbe anche essere facilmente visualizzato, il che potrebbe sembrare piuttosto eccitante.
Philipp,

5

È possibile modificare l'attacco con un numero casuale campionato da una distribuzione normale. In questo modo la maggior parte delle volte il risultato sarà quello che ti aspetti, ma a volte un attacco più alto perderà contro una difesa più bassa o un attacco più basso vincerà contro una difesa più alta. La probabilità che ciò accada diminuirà all'aumentare della differenza tra attacco e difesa.

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

La funzione norm(x0, sigma)restituisce un float campionato da una distribuzione normale centrata su x0, con sigma di deviazione standard. La maggior parte dei linguaggi di programmazione fornisce una libreria con una tale funzione, ma se vuoi farlo da solo dai un'occhiata a questa domanda . Dovresti regolare sigma in modo tale che "sembri giusto", ma un valore di 10-20 potrebbe essere un buon punto di partenza.

Per alcuni valori sigma, la probabilità di vittoria per un dato att1 - def2appare così: Probabilità di vittoria


Potrebbe anche valere la pena sottolineare che i valori distribuiti normali non hanno limiti effettivi, quindi quando si utilizzano valori casuali distribuiti normalmente in un gioco può avere senso bloccare il risultato per evitare la situazione improbabile ma non impossibile di generare valori molto estremi che potrebbe interrompere il gioco.
Philipp,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.