Come evitare le strisce "troppo" fortunate / sfortunate nella generazione casuale di numeri?


30

Attualmente mi sto occupando di un sistema di combattimento multiplayer in cui il danno inflitto dai giocatori viene sempre moltiplicato per un fattore casuale compreso tra 0,8 e 1,2.

In teoria, un RNG veramente casuale può eventualmente produrre lo stesso numero molte volte (vedi il dilemma di Tetris ). Ciò potrebbe tradursi in un match in cui il giocatore è sempre facendo molto alto danno, mentre l'altro fa sempre molto bassa danni.

Cosa posso fare per assicurarmi che ciò non accada? Alcuni RNG sono migliori di altri nell'evitare la ripetizione?


Non vedo come funziona. Ovviamente otterrai una sequenza di x1, x2, x3, x4 .. dove tutte le x sono grandi. Non è solo casuale?
Il comunista Duck il

Risposte:


26

Puoi risolverlo allo stesso modo di Tetris, creando un elenco predefinito di risultati dei danni e mescolando.

Supponiamo che tu sappia che il giocatore infliggerà danni da 0,8 a 1,2 volte con una distribuzione lineare. Prendi l'elenco [0.8, 0.9, 1.0, 1.1, 1.2]. Mischialo in modo casuale , in modo da ottenere ad esempio [1.2, 1.0, 0.8, 0.9, 1.1].

La prima volta che il giocatore infligge danno, infliggono 1,2x. Quindi 1x. Quindi, ecc., A 1.1x. Solo quando l'array è vuoto è necessario generare e mescolare un nuovo array.

In pratica, probabilmente vorrai farlo in 4+ array contemporaneamente (es. Iniziare con [0.8.0.8,0.8,0.8,0.9,0.9,0.9,0,9, ...]). Altrimenti il ​​periodo della sequenza è abbastanza basso da consentire ai giocatori di capire se il loro prossimo colpo è "buono" oppure no. (Anche se questo può anche aggiungere più strategia al combattimento, come nel tavolo Hoimi di Dragon Quest IX , che le persone hanno capito come sondare guardando i numeri di cura e modificando fino a quando non ti viene garantito un calo raro.)


3
Per renderlo un po 'più casuale, potresti sempre avere metà dell'elenco come numeri casuali e l'altra metà calcolata come (2-x) per ottenere la media corretta.
Adam,

2
@Adam: quel metodo funziona davvero solo per questo esempio particolare; se distribuisci pezzi Tetris anziché moltiplicatori di danno, cos'è il blocco 2 - S?

6
Il termine abituale per questo è una specie di sistema "casuale senza sostituzione". È semplicemente analogo all'utilizzo di un mazzo di carte anziché di dadi, davvero.
Kylotan,

Ancora meglio, si potrebbe fare la metà dei numeri realmente casuali, e solo la metà di essi soggetti a questa regola.
o0 '.

1
Può comunque comportare che la distribuzione locale non assomigli alla distribuzione globale, che è esattamente ciò che la domanda non vuole. Termini come "veramente casuale" sono vaghe pseudomatematiche; più definisci quali proprietà statistiche desideri, più chiare saranno le tue intenzioni e il design del gioco.

5

In realtà ho scritto del codice per farlo . L'essenza sta usando le statistiche per correggere le serie sfortunate. Il modo in cui puoi farlo è tenere traccia di quante volte si è verificato l'evento e usarlo per distorcere il numero generato dal PRNG.

Innanzitutto, come tenere traccia della percentuale di eventi? Il modo ingenuo di fare questo sarebbe quello di mantenere tutti i numeri mai generati in memoria e di farne una media: che funzionerebbe ma è terribilmente inefficiente. Dopo un po 'di riflessione mi è venuto in mente quanto segue (che è fondamentalmente una media mobile cumulativa ).

Prelevare i seguenti campioni PRNG (dove si procede se il campione è> = 0,5):

Values: 0.1, 0.5, 0.9, 0.4, 0.8
Events: 0  , 1  , 1  , 0  , 1
Percentage: 60%

Si noti che ogni valore contribuisce a 1/5 del risultato finale. Diamo un'occhiata in un altro modo:

Values: 0.1, 0.5
Events: 0  , 1

Si noti che 0contribuisce al 50% del valore e 1contribuisce al 50% del valore. Preso leggermente oltre:

Values: [0.1, 0.5], 0.9
Events: [0  , 1  ], 1

Ora i primi valori contribuiscono al 66% del valore e all'ultimo 33%. Fondamentalmente, possiamo distillare questo fino al seguente processo:

result = // 0 or 1 depending on the result of the event that was just generated
new_samples = samples + 1

average = (average * samples / new_samples) + (result * 1 / new_samples)
// Essentially:
average = (average * samples / new_samples) + (result / new_samples)

// You might want to limit this to, say, 100.
// Leaving it to carry on increasing can lead to unfairness
// if the game draws on forever.
samples = new_samples

Ora dobbiamo distorcere il risultato del valore campionato dal PRNG, perché stiamo andando per una probabilità percentuale qui le cose sono molto più facili (contro, diciamo, quantità casuali di danno in un RTS). Questo sarà difficile da spiegare perché "mi è appena venuto in mente". Se la media è inferiore significa che dobbiamo aumentare la possibilità che si verifichi l'evento e viceversa. Quindi alcuni esempi

average = 0.1
desired = 0.5
corrected_chance = 83%

average = 0.2
desired = 0.5
corrected_chance = 71%

average = 0.5
desired = 0.5
corrected_change = 50%

Ora quello che mi è venuto in mente è che nel primo esempio l'83% era solo "0,5 su 0,6" (in altre parole "0,5 su 0,5 più 0,1"). In termini di eventi casuali ciò significa:

procced = (sample * 0.6) > 0.1
// or
procced = (sample * 0.6) <= 0.5

Quindi, al fine di generare un evento, useresti sostanzialmente il seguente codice:

total = average + desired
sample = rng_sample() * total // where the RNG provides a value between 0 and 1
procced = sample <= desired

E quindi ottieni il codice che ho inserito in sintesi. Sono abbastanza sicuro che tutto ciò possa essere usato nello scenario del caso di danno casuale, ma non ho avuto il tempo di capirlo.

Dichiarazione di non responsabilità: si tratta di statistiche nazionali, non ho alcuna istruzione in questo campo. I miei test unitari superano comunque.


Sembra un errore nel tuo primo esempio perché sia ​​un valore 0,1 che un valore 0,9 generano un evento 0. Ma sostanzialmente stai descrivendo il mantenimento di una media mobile cumulativa ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) e correggendoti in base a quello. Un rischio è che ogni risultato sia significativamente inversamente correlato al risultato precedente, sebbene questa correlazione diminuisca nel tempo.
Kylotan,

1
Sarei tentato di modificarlo per utilizzare invece un sistema "che perde integratore": inizia con la media inizializzata su 0,5 e invece di contare i campioni scegli un valore costante arbitrario (ad es. 10, 20, 50 o 100) che non viene incrementato . Quindi almeno la correlazione tra 2 valori successivi è costante durante l'uso del generatore. Puoi anche modificare il valore costante: valori più grandi significano correzione più lenta e casualità più evidente.
Kylotan,

@Kylotan grazie, grazie per aver fornito il nome. Non sono sicuro di cosa intendi esattamente con il tuo secondo commento - forse fornire una nuova risposta?
Jonathan Dickinson,

È abbastanza intelligente e non ha i limiti degli array. Comprendo il suggerimento di Kylotan, che è quello di inizializzare samplesal suo valore massimo (in questo caso, 100) dall'inizio. In questo modo, non ci vogliono 99 iterazioni per stabilizzare l'RNG. Ad ogni modo, l'unico svantaggio che posso vedere con questo metodo è che non garantisce l' equità, ma semplicemente garantisce una media costante.
Utente non trovato il

@jSepia - in effetti, si otterrebbero comunque serie di equità / ingiustizia, ma sarebbero seguite (di solito) da una corsa equilibrata. Ad esempio nel mio test unitario ho "forzato" 100 non-proc e ho incontrato ~ 60 proc quando ho fatto i campioni reali. In situazioni non influenzate (se si guarda il codice) un proc del 50% di solito vede, nella peggiore delle ipotesi, corse di 2/3 in entrambe le direzioni. Ma un giocatore potrebbe fare una corsa permettendo loro di sconfiggere l'altro giocatore. Se si vuole pregiudizi più fortemente alla fiera: total = (average / 2) + desired.
Jonathan Dickinson,

3

Quello che stai chiedendo è in realtà l'opposto della maggior parte dei PRNG, una distribuzione non lineare. Inserisci semplicemente una sorta di logica dei rendimenti decrescenti nelle tue regole, Supponendo che tutto oltre 1.0x sia un "colpo critico" di qualche tipo, dì solo che ad ogni round le tue possibilità di ottenere un critico aumentano di X, fino a quando non ne ottieni uno a quale punto reimpostano su Y. Quindi fai due tiri per round, uno per determinare crit o no, e poi un altro per la grandezza effettiva.


1
Questo è l'approccio generale che prenderei, usi la distribuzione uniforme dell'RNG ma lo trasformi. È inoltre possibile utilizzare l'output dell'RNG come input per la propria distribuzione personalizzata che si adatta nuovamente in base alla cronologia recente, ovvero forzare la varianza negli output, in modo che appaia "più casuale" in termini di percezione umana.
Michael,

3
In realtà conosco un MMO che fa qualcosa del genere, ma la possibilità di un critico aumenta ogni volta che ne ottieni uno finché non ne ottieni uno, quindi si reimposta su un valore molto basso. Questo porta a rare serie di crits che sono molto soddisfacenti per il giocatore.
coderanger,

Sembra una buona alternativa, i lunghi periodi di siccità sono sempre stati frustranti, ma non portano a strane serie di crisi.
Michael,

2
La correzione di questo non richiede una distribuzione non lineare, ma richiede solo che sottoinsiemi sequenziali a breve termine della distribuzione abbiano le stesse proprietà della distribuzione stessa.

è così che fanno i giochi Blizzard, almeno da Warcraft 3
dreta il

2

Sid Meier ha tenuto un eccellente discorso su GDC 2010 proprio su questo argomento e sui giochi di Civilization. Proverò a trovare e incollare il link in seguito. In sostanza, la casualità percepita non è la stessa della casualità vera. Per rendere le cose giuste è necessario analizzare i risultati precedenti e prestare attenzione alla psicologia dei giocatori.

Evita le serie di sfortuna a tutti i costi (se i due turni precedenti sono stati sfortunati, il prossimo dovrebbe essere garantito per essere fortunato). Il giocatore dovrebbe essere sempre più fortunato dell'avversario AI.


0

Usa una propensione al cambiamento

Il generatore di base utilizza una distribuzione uniforme tra e per generare . Inizialmente impostare un valore di polarizzazione, , su .01rb0

La distribuzione complessiva sarà distorta dalla seguente formula:

rexp(b)

L'effetto qui è che quando è positivo, il numero risultante sarà distorto verso . Quando è negativo, il numero risultante sarà distorto verso .b1b0

Prendi questo numero e ridimensionalo in modo appropriato all'intervallo desiderato.

Ogni volta che un giocatore tira favorevolmente, sottrai dal pregiudizio. Ogni volta che il giocatore tira sfavorevolmente, aggiungi al bias. L'importo modificato potrebbe essere ridimensionato in base a quanto (non) favorevole il rotolo è o potrebbe essere un importo fisso (o una combinazione). Dovrai regolare valori specifici per adattarli all'atmosfera che stai cercando.

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.