Per prima cosa, vorrei conoscere le probabilità che si verifichi un evento. Prendi carta e penna, lo faremo a mano.
Rendi tutto indipendente quando puoi. Ad esempio, quando vedi la serie di if all'inizio:
if rand(20) >= 2 then
//Make Object A
if rand(20) >= 5 then
//Make another Object A
if rand(20) >= 14 then
//Make a third Object A
if rand(20) >= 18 then
//Make one last Object A
Dovremmo riconoscere che si tratta di 4 estrazioni indipendenti. Sotto ognuno di essi abbiamo due possibilità: creare un oggetto A o "non fare nulla". Assegna a ciascuna una probabilità utilizzando i rapporti, quindi la probabilità della prima istanza dell'istruzione di creare un oggetto è 2/20.
Altre sezioni non ti permetteranno di trattarle come disegni indipendenti a causa dei pattern "elseif".
if rand(100) >= 25 then
if rand(10) < 8 then
//Make Object B
elseif rand(10) < 8 then
//Make Object C
else
//Make Object D
richiederà un albero per descrivere le probabilità
X
/ \
p=25/100 / \ p=75/100
Do nothing X
/ \
p=8/10 / \ p=2/10
Make B X
/ \
p=8/10 / \ p=2/10
Make C Make D
Ora questo è solo il tentativo di capire cosa è stato scritto. Il prossimo passo è cercare di semplificarlo. L'idea è che puoi mescolare i rotoli in giro, purché le probabilità siano le stesse. Ad esempio, solo con qualche moltiplicazione posso vedere le probabilità di creare un B C o D.
X
/ \
p=25/100 / \ p=75/100
Do nothing X
(p=.25) / \
p=8/10 / \ p=2/10
Make B X
(p=.75*.8=.6) / \
p=8/10 / \ p=2/10
Make C Make D
(p=.75*.2*.8=.12) (p=.75*.2*.2=.03)
Ora, osservando tutte queste probabilità, possono essere tutte espresse come un rapporto razionale con 100 in basso (.12 = 12/100; .03 = 3/100). Di conseguenza, possiamo gestire tutto questo con un singolo rotolo, il che rende la logica molto più semplice.
roll = rand(100) -- a random roll from [0,100) is sufficient for the whole block
if roll < 60 then -- we'll hit this with a probability of .6
// make object B
elseif roll < 72 then -- 72-60 = 12, so we'll hit this with a probability of .12
// make object C
elseif roll < 75 -- 75-72 = 3, so we'll hit this with a probability of .03
// make object D
else -- 100-75=25, so we'll hit this with a probability of .25
-- do nothing. This is the equivalent of not entering the
-- outermost if statement, with a probability of .25.
end
L'altro schema che vedo qui è che ci sono molti disegni indipendenti che creano tutti lo stesso oggetto. Hai ragione: sarebbe bello semplificarli. Puoi usare una tabella di probabilità congiunta per unirti a loro. Ad esempio, possiamo prendere i primi due casi "make object A".
Nothing(p=.3) Make A (p=.7) <-- second if
v--- first 'if' +-------------------------+--------------------------+
Nothing(p=.15) | Nothing (p=.3*.15=.045) | Make A (p=.7*.15=.105) |
+-------------------------+--------------------------+
Make A (p=.85) | Make A (p=.3*.85=.255) | Make 2x A (p=.7*.85=.595 |
+-------------------------+--------------------------+
Puoi applicare questo processo tutte le volte che vuoi, finché non ottieni una tabella contenente la probabilità di ottenere 1A, 2A, 3A o 4A.
Quando hai finito con tutto questo, ciò che dovresti vedere è un piccolo numero di estrazioni, seguito da una ricerca da poche tabelle per determinare il risultato in base a tali estrazioni. Questo dovrebbe essere sufficiente per afferrare saldamente il codice spaghetti di probabilità.
Da lì, è possibile utilizzare alcune programmazioni funzionali per rendere questo più pulito. È possibile creare un oggetto tabella che ricerca un'estrazione e restituisce una funzione che eseguirà l'attività corretta per quello slot nella tabella. Questo ha il vantaggio di essere un molto modo standard per ottenere risultati probabilistici, quindi qualsiasi sviluppatore futuro che lo guardi riconoscerà immediatamente la tabella di estrazione casuale e si sentirà a suo agio con esso.