Hoe modelleer ik een fudge dobbelworp met herrollen in Anydice?

Hier is een alternatieve oplossing:

FUDGE: {-1, 0, +1}function: ROLL:s reroll up to SKILL:n { N: ] result: NdFUDGE + {1 .. #ROLL-N}@ROLL}loop SKILL over {0..4} { output named "skill "}

De functie zou voor het grootste deel zelfverklarend moeten zijn; het enige deel dat misschien uitleg behoeft is {1 .. #ROLL-N}@ROLL, dat alle elementen van de reeks ROLL bij elkaar optelt, behalve de laatste N. Standaard sorteert AnyDice dobbelstenen in aflopende numerieke volgorde, zodat de laatste elementen de laagste zijn.

In de grafische modus ziet de output van dit programma er als volgt uit:

Graph

Merk op dat de verschillen tussen vaardigheidsniveaus 2, 3 en 4 tamelijk klein zijn, aangezien het onwaarschijnlijk is dat je drie of vier -1’s gooit op 4dF.

BTW, het bovenstaande programma gaat ervan uit, zoals u aan het eind van uw vraag zegt, dat spelers conservatief zijn en alleen negatieve worpen zullen herrollen. Als uw spelers graag risico’s nemen, kunnen ze besluiten om ook nullen te herrollen. In dat geval zouden de resultaten er als volgt uitzien:

Grafiek

Merk op dat de gemiddelden nog steeds hetzelfde zijn, maar dat de resultaten voor hogere vaardigheden veel meer variatie vertonen. In het bijzonder zijn de kansen om een perfecte vier te gooien met een positieve vaardigheid veel hoger op deze manier.

(Het enige verschil tussen de programma’s die gebruikt zijn om de twee bovenstaande grafieken te genereren is dat de tweede gebruikt in plaats van .)

In het bijzonder, als uw spelers proberen te gooien tegen een specifiek minimum doelgetal, kan het zinvol zijn voor hen om alleen zoveel nullen te gooien als nodig is om hun kans te maximaliseren om het doel te halen.

De optimale strategie in dit geval hangt af van de vraag of de spelers de dobbelstenen één voor één kunnen hergooien en na elke worp kunnen beslissen of ze verder willen hergooien, of dat ze eerst moeten beslissen welke dobbelstenen ze willen hergooien en ze dan allemaal tegelijk moeten gooien.

In het eerste geval (d.w.z. sequential rerolls) kan het optimale beslissingsproces worden gesimuleerd met een recursieve AnyDice functie:

FUDGE: {-1, 0, +1}function: first N:n of SEQ:s { FIRST: {} loop I over {1..N} { FIRST: {FIRST, I@SEQ} } result: FIRST}function: ROLL:s reroll up to SKILL:n target TARGET:n { if ROLL + 0 >= TARGET { result: 1 } \- success -\ if #ROLL = 0 | SKILL = 0 | #ROLL@ROLL = 1 { result: 0 } \- failure -\ FIRST: result: \- reroll -\}loop TARGET over {-3..4} { loop SKILL over {0..4} { output named "target , skill " }}

Hier geeft de hoofdfunctie ROLL reroll up to SKILL target TARGET 1 terug als de gegeven worp gelijk is aan of groter dan het doel, en 0 als het minder is dan het doel en geen verbetering mogelijk is (d.w.z. er zijn geen dobbelstenen meer over in de pool, geen rerolls zijn meer toegestaan of de laagste dobbelsteen is al een +1). Anders verwijdert het de laagste dobbelsteen uit de pool (met behulp van een helperfunctie, aangezien AnyDice geen geschikte functie heeft ingebouwd), vermindert het aantal resterende rerolls met één, trekt 1dF af van de doelwaarde om een enkele reroll te simuleren en roept zichzelf dan recursief op.

De uitvoer van dit programma is een beetje lastig te ontleden vanuit AnyDice’s normale staaf / lijngrafiekweergave, dus ik heb het in plaats daarvan geëxporteerd en het door het Python-script van dit eerdere antwoord laten lopen om er een mooi tweedimensionaal raster van te maken dat ik in Google Sheets kon importeren. De resultaten, als een heat map en als een multi-bar grafiek, zien er als volgt uit:

Screenshot

In het tweede geval (d.w.z. alle rerolls tegelijk) moeten we eerst uitzoeken wat de optimale strategie eigenlijk is. Even nadenken leert dat:

  • Een willekeurige -1 moet altijd opnieuw worden gegooid, omdat het resultaat daardoor nooit lager kan uitvallen. Aangezien het verwachte gemiddelde resultaat van een herwinningsworp 0 is, is het verwachte gemiddelde na het herwinnen van alle -1’s gelijk aan het aantal +1’s in de oorspronkelijke worp.

  • Herwissen van een nul verandert het verwachte gemiddelde resultaat niet, maar verhoogt wel de variantie, d.w.z. het werkelijke resultaat zal waarschijnlijk verder weg liggen van het gemiddelde in beide richtingen. Men moet dus alleen nullen hergooien als het verwachte gemiddelde resultaat na het hergooien van alle -1’s (d.w.z. het aantal +1’s in de oorspronkelijke worp) onder het streefcijfer ligt.

Toepassing van deze logica in AnyDice resulteert in iets als dit programma:

FUDGE: {-1, 0, +1}function: ROLL:s reroll up to SKILL:n target TARGET:n { if >= TARGET { N: ] } else { N: ] } result: (NdFUDGE + {1 .. #ROLL-N}@ROLL) >= TARGET}loop TARGET over {-3..4} { loop SKILL over {0..4} { output named "target , skill " }}

Export van de uitvoer van dit script en het uitvoeren ervan door hetzelfde Python-script en spreadsheet levert de volgende heatmap en staafdiagram op:

Screenshot

Zoals je ziet, zijn de resultaten eigenlijk niet zo verschillend van het geval van sequentiële herrollen. De grootste verschillen treden op bij hoge vaardigheden en tussenliggende doelgetallen: als je bijvoorbeeld bij een vaardigheid van 4 de rerolls één voor één kunt uitvoeren en op elk moment kunt stoppen, stijgt het gemiddelde slagingspercentage van 75,3% naar 81% bij een doel van +1, of van 51,6% naar 58,3% bij een doel van +2.

Ps. Ik ben er wel in geslaagd om een manier te vinden om AnyDice de “success rate vs. target” waarden uit de twee bovenstaande programma’s te laten verzamelen in een enkele verdeling voor elke vaardigheidswaarde, waardoor ze direct door AnyDice kunnen worden getekend als staafdiagrammen of lijngrafieken (in “tenminste” modus) zonder Python of spreadsheets te hoeven gebruiken.

Helaas is de AnyDice code om dat te doen allesbehalve eenvoudig. Het moeilijkste(!) deel bleek het vinden van een manier om AnyDice twee kansen te laten aftrekken (bijv. 1/2 – 1/3 = 1/6). De beste manier die ik ken om deze schijnbaar triviale taak in AnyDice uit te voeren, vereist niet-triviale manipulatie van voorwaardelijke kansen en een iteratieve lus. En het laat AnyDice crashen als je er 0 – 0 mee probeert te berekenen.*

Hier is voor de volledigheid de AnyDice-code voor het berekenen en plotten van de verdeling van het “hoogst te verslaan doel” voor verschillende vaardigheidsniveaus (en voor elk van de twee hierboven beschreven rerolling-mechanica) met wat commentaar toegevoegd voor de leesbaarheid:

\- predefine a fudge die -\FUDGE: d{-1, 0, +1}\- miscellaneous helper functions used in the code below -\function: first N:n of SEQ:s { FIRST: {} loop I over {1..N} { FIRST: {FIRST, I@SEQ} } result: FIRST}function: exclude RANGE:s from ROLL:n { if ROLL = RANGE { result: d{} } else { result: ROLL }}function: sign of NUM:n { result: (NUM > 0) - (NUM < 0)}function: if COND:n then A:d else B:d { if COND { result: A } else { result: B }}\- a helper function to subtract two probabilities (given as {0,1}-valued dice) -\function: P:d minus Q:d { DIFF: P - Q loop I over {1..20} { TEMP: DIFF: (DIFF != 0) * } result: }\- this function calculates the probability of meeting or exceeding the target -\- value, assuming that each die in the initial roll can be rerolled once and -\- that the player may stop rerolling at any point -\function: ROLL:s reroll one at a time up to SKILL:n target TARGET:n { if ROLL + 0 >= TARGET { result: 1 } \- success -\ if #ROLL = 0 | SKILL = 0 | #ROLL@ROLL = 1 { result: 0 } \- failure -\ FIRST: \- remove last (=lowest) original roll -\ TNEW: TARGET - 1dFUDGE \- adjust target value depending on reroll -\ result: \- reroll -\}\- this function calculates the probability of meeting or exceeding the target -\- value, assuming that each die in the initial roll can be rerolled once but -\- the player must decide in advance how many of the dice they'll reroll; the -\- optimal(?) decision rule in this case is to always reroll all -1s and to -\- also reroll 0s if and only if the number of +1s in the initial roll is less -\- than the target number -\function: ROLL:s reroll all at once up to SKILL:n target TARGET:n { if >= TARGET { N: ] } else { N: ] } result: (NdFUDGE + {1 .. #ROLL-N}@ROLL) >= TARGET}\- this function collects the success probabilities given by the two functions -\- above into a single custom die D, such that the probability that D >= N is -\- equal to the probability of the player meeting or exceeding the target N; -\- the SEQUENTIAL flag controls which of the functions above is used -\function: collect results for SKILL:n from MIN:n to MAX:n sequential SEQUENTIAL:n { BOGUS: MAX + 1 DIST: 0 PREV: 1 loop TARGET over {MIN..MAX} { if SEQUENTIAL { PROB: } else { PROB: } DIST: then TARGET else BOGUS]] PREV: PROB } result: }\- finally we just loop over possible skill values and output the results -\loop SKILL over {0..4} { output named "skill , one at a time"}loop SKILL over {0..4} { output named "skill , all at once"}

en een screenshot van de output (in “ten minste” lijngrafiekmodus):

Graph

Een opmerking over de interpretatie van de output die door het bovenstaande programma wordt gegenereerd: De kansverdelingen in de bovenstaande grafiek komen niet overeen met de resultaten van een enkele dobbelsteenworpstrategie, maar zijn kunstmatig geconstrueerde verdelingen (d.w.z. “aangepaste dobbelstenen” in AnyDice jargon) zodanig dat de kans dat een speler ten minste \$N\$ gooit met een enkele worp van de aangepaste dobbelsteen gelijk is aan de kans dat de speler ten minste \$N\$ gooit op 4dF met het gegeven terugworp mechanisme (één voor één vs. alle tegelijk) en het gegeven maximum aantal herrollen, in de veronderstelling dat de speler de optimale herrolstrategie voor dat specifieke doel gebruikt

Met andere woorden, als we naar de uitvoer kijken in de “minstens”-modus, zien we dat een speler met vaardigheidsniveau 4 een kans van 51,62% heeft om +2 of meer te gooien (met het “alle tegelijk herrollen”-mechanisme) als hij zijn beschikbare herrollen gebruikt op de manier die die kans maximaliseert. De output laat ook correct zien dat dezelfde speler 75.28% kans heeft om +1 of meer te gooien als hij daarvoor kiest, maar hij heeft verschillende strategieën nodig om die twee doelen te bereiken.

En de “kans” van 23.65% om precies +1 te gooien op de aangepaste dobbelsteen zoals hierboven beschreven heeft eigenlijk geen zinnige betekenis, behalve dat het (bij benadering, door afronding) het verschil is tussen 75.28% en 51.62%. Daarom is het zo moeilijk te berekenen met AnyDice 😛 Ik veronderstel dat je het zou kunnen interpreteren als een maatstaf voor hoeveel moeilijker een doel van +2 te halen is met de gegeven vaardigheid en herwinningsmechaniek dan een doel van +1, in zekere zin, maar dat is het zo’n beetje.

*) Die crash kan te maken hebben met een bug in AnyDice waarvan ik vrij zeker ben dat het een bug is die ik gevonden heb tijdens het ontwikkelen van deze code, waardoor een van mijn vroege testprogramma’s hele rare uitvoer genereerde met dingen als 97284.21% kansen(!). Het testprogramma crasht uiteindelijk ook als je het aantal iteraties verder opvoert.