How do I model a fudge dice roll with re-rolls in Anydice?

Tässä on vaihtoehtoinen ratkaisu:

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 "}

Funktion pitäisi olla suurimmaksi osaksi itsestään selvä; ainoa osa, joka saattaa vaatia selittämistä, on {1 .. #ROLL-N}@ROLL, joka laskee yhteen kaikki muut paitsi jakson ROLL viimeiset N elementit. Oletusarvoisesti AnyDice lajittelee nopanheitot alenevassa numerojärjestyksessä, joten viimeiset elementit ovat alimpia.

Graafitilassa tämän ohjelman tuotokset näyttävät tältä:

Graph

Huomaa, kuinka erot taitotasojen 2, 3 ja 4 välillä ovat melko vähäisiä, sillä kolmen tai neljän -1:n heittäminen 4dF:llä on aluksi melko epätodennäköistä.

BTW, yllä oleva ohjelma olettaa, kuten kysymyksesi lopussa sanot, että pelaajat ovat konservatiivisia ja heittävät vain negatiivisia heittoja uudelleen. Jos pelaajasi haluavat ottaa riskejä, he saattavat päättää heittää uudelleen myös nollia, jolloin tulokset näyttäisivät sen sijaan tältä:

Graph

Huomaa, että keskiarvot ovat edelleen samat, mutta korkeampien taitojen tuloksissa on paljon enemmän vaihtelua. Erityisesti todennäköisyydet heittää täydellinen nelonen positiivisella taidolla ovat paljon suuremmat tällä tavalla.

(Ainoa ero yllä olevien kahden kuvaajan tuottamiseen käytettyjen ohjelmien välillä on, että toisessa käytetään :aa :n sijasta.)

Erityisesti, jos pelaajasi yrittävät heittää tiettyä vähimmäistavoitelukua vastaan, voi olla järkevää, että he he heittävät vain niin monta nollaa kuin on tarpeen maksimoidakseen todennäköisyytensä tavoitteen saavuttamiseen.

Optimaalinen strategia näissä tapauksissa riippuu siitä, voivatko pelaajat heittää nopat uudelleen yksi kerrallaan ja päättää jokaisen heiton jälkeen, haluavatko he jatkaa uudelleenheittoa, vai pitääkö heidän ensin päättää, mitkä nopat he haluavat heittää uudelleen, ja sitten heittää ne kaikki kerralla.

Ensimmäisessä tapauksessa (ts. peräkkäiset uusintaheitot) optimaalista päätöksentekoprosessia voidaan simuloida rekursiivisella AnyDice-funktiolla:

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 " }}

Tässä pääfunktio ROLL reroll up to SKILL target TARGET palauttaa arvon 1, jos annettu heitto on yhtä suuri tai suurempi kuin tavoite, ja arvon 0, jos se on pienempi kuin tavoite eikä parannus ole mahdollinen (ts. pooliin ei ole jäänyt enää yhtään noppaa jäljelle, uusintaheittoja ei sallita enempää, tai alimman nopan arvoksi tulee jo +1). Muussa tapauksessa se poistaa alimman nopan poolista (käyttäen apufunktiota, koska AnyDice ei satu olemaan sisäänrakennettuna sopivaa funktiota), vähentää jäljellä olevien uusintakierrosten määrää yhdellä, vähentää tavoitearvosta 1dF simuloidakseen yksittäistä uusintakierrosta ja kutsuu sitten itseään rekursiivisesti.

Tämän ohjelman tuotos on hieman hankala jäsentää AnyDicen normaalista pylväs- / viivakaavionäkymästä, joten sen sijaan vein sen ja juoksutin sen tämän aikaisemman vastauksen Python-skriptin läpi muuttaakseni sen mukavaksi kaksiulotteiseksi ruudukoksi, jonka voisin tuoda Google Sheetsiin. Tulokset lämpökarttana ja monipalkkigraafina näyttävät tältä:

Ruutukaappaus

Kakkostapauksessa (eli kaikki rerollit kerralla) meidän on ensin selvitettävä, mikä oikeastaan on optimaalinen strategia. Hetken miettiminen osoittaa, että:

  • Pitäisi aina rerollata kaikki -1:t, koska näin tekemällä ei voi koskaan pienentää tulosta. Koska uudelleenheiton odotettu keskimääräinen tulos on 0, odotettu keskiarvo kaikkien -1:ien uudelleenheiton jälkeen on yhtä suuri kuin alkuperäisen heiton +1:ien määrä.

  • Nollan uudelleenheitto ei muuta odotettua keskimääräistä tulosta, mutta se kasvattaa varianssia, eli se tekee todellisesta tuloksesta todennäköisemmin kauempana keskiarvosta kumpaankin suuntaan. Näin ollen nollia pitäisi heittää uudelleen vain, jos odotettu keskimääräinen tulos kaikkien -1:ien uudelleen heittämisen jälkeen (eli alkuperäisen heiton +1:ien määrä) on tavoitelukua pienempi.

Tämän logiikan soveltaminen AnyDice-ohjelmassa johtaa jotakuinkin seuraavanlaiseen ohjelmaan:

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 " }}

Tämän skriptin tuloksen vieminen ja sen ajaminen saman Python-skriptin ja taulukkolaskentaohjelman läpi antaa seuraavan lämpökartan ja pylväsdiagrammin:

Screenshot

Kuten huomaatte, tulokset eivät oikeastaan ole kovinkaan paljon erilaisemmat kuin peräkkäisten uusintarullien tapauksessa. Suurimmat erot ilmenevät korkeilla taidoilla ja keskimmäisillä tavoiteluvuilla: esimerkiksi taidolla 4 mahdollisuus suorittaa rerollit yksi kerrallaan ja pysäyttää ne missä vaiheessa tahansa nostaa keskimääräistä onnistumisprosenttia 75,3 %:sta 81 %:iin, kun tavoite on +1, tai 51,6 %:sta 58,3 %:iin, kun tavoite on +2.

Ps. Onnistuin kyllä keksimään tavan saada AnyDice keräämään kahdesta yllä olevasta ohjelmasta saadut ”onnistumisprosentti vs. tavoite” -arvot yhdeksi jakaumaksi jokaiselle taitoarvolle, jolloin AnyDice voi piirtää ne suoraan pylväsdiagrammeina tai viivakaavioina (”ainakin” -tilassa) ilman, että tarvitsee käyttää Pythonia tai laskentataulukoita.

Katastrofaalisesti AnyDicen koodi, jolla tämä voidaan tehdä, on kaikkea muuta kuin yksinkertainen. Vaikein(!) osa osoittautui olevan löytää tapa saada AnyDice vähentämään kaksi todennäköisyyttä (esim. 1/2 – 1/3 = 1/6). Paras tuntemani tapa suorittaa tämä näennäisen triviaali tehtävä AnyDicessa edellyttää ehdollisten todennäköisyyksien ei-triviaalia manipulointia ja iterointisilmukkaa. Ja se kaataa AnyDicen, jos yrität laskea 0 – 0 sen avulla.*

Jokatapauksessa, vain täydellisyyden vuoksi, tässä on AnyDice-koodi ”korkeimman lyötävissä olevan kohteen” jakauman laskemiseksi ja piirtämiseksi eri taitotasoille (ja kummallekin edellä kuvatulle uudelleentulostusmekaniikalle), ja siihen on lisätty joitain kommentteja luettavuuden vuoksi:

\- 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"}

ja kuvakaappaus tulosteesta (”ainakin” viivadiagrammitilassa):

Graph

Huomautus yllä olevan ohjelman tuottaman tulosteen tulkinnasta: Yllä olevassa kuvaajassa näkyvät todennäköisyysjakaumat eivät vastaa minkään yksittäisen nopanheittostrategian tuloksia; pikemminkin ne ovat keinotekoisesti konstruoituja jakaumia (eli ”mukautettuja noppia” AnyDicen jargonissa) siten, että todennäköisyys heittää vähintään \$N\$ yhdellä mukautetun nopan heitolla on yhtä suuri kuin todennäköisyys, että pelaaja pystyy heittämään vähintään \$N\$ 4dF:llä annetulla uudelleenheittomekanismilla (yksitellen vs. 1dF). kaikki kerralla) ja annetulla uudelleenkierrosten maksimimäärällä, olettaen, että pelaaja käyttää optimaalista uudelleenkierrätysstrategiaa kyseiselle kohteelle \$N\$.

Toisin sanoen, tarkastelemalla tulosta ”vähintään”-tilassa, näemme, että pelaajalla, jonka taitotaso on 4, on 51,62 %:n todennäköisyys onnistua heittämään +2 tai enemmän (käyttämällä kaikki kerralla uudelleenkierrätysmekaniikkaa), jos hän käyttää käytettävissä olevia uudelleenkierroksia tavalla, joka maksimoi kyseisen mahdollisuuden. Tulos osoittaa myös oikein, että samalla pelaajalla on 75,28 %:n mahdollisuus heittää +1 tai enemmän, jos hän päättää optimoida sen sijaan sen, mutta hän tarvitsee eri uudelleenheittostrategioita saavuttaakseen nämä kaksi tavoitetta.

Ja ”todennäköisyydellä” 23,65 % heittää täsmälleen +1 edellä kuvatulla mukautetulla heittokupongilla ei ole oikeastaan mitään järkevää merkitystä, paitsi se, että se on (likimääräisesti pyöristyksen vuoksi) 75,28 %:n ja 51,62 %:n välinen ero. Siksi sitä on kai niin vaikea laskea AnyDicella. 😛 Kai sen voisi tulkita mittariksi siitä, kuinka paljon vaikeampi +2-tavoitteen saavuttaminen on tietyllä taidolla ja uudelleenheittomekaniikalla kuin +1-tavoitteen saavuttaminen, jossain mielessä, mutta siinäpä se.

*) Tuo kaatuminen saattaa liittyä siihen, että olen melko varma, että AnyDicessa on bugi, jonka löysin kehittäessäni tätä koodia ja joka aiheutti sen, että yksi varhaisista testiohjelmistani tuotti todella outoja tulosteita, joissa oli esimerkiksi 97284.21% todennäköisyyksiä(!). Testiohjelma myös kaatuu lopulta, jos iteraatiomäärää kasvattaa entisestään.