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

Itt egy alternatív megoldás:

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

A függvénynek nagyrészt magától értetődőnek kell lennie; az egyetlen magyarázatra szoruló rész a {1 .. #ROLL-N}@ROLL, amely a ROLL sorozat utolsó N elemei kivételével az összeset összeadja. Alapértelmezés szerint az AnyDice a kockadobásokat csökkenő számsorrendbe rendezi, így az utolsó elemek a legalacsonyabbak.

Grafikus módban a program kimenetei így néznek ki:

Graph

Megjegyezzük, hogy a 2., 3. és 4. készségszintek közötti különbségek meglehetősen csekélyek, mivel a 4dF-re dobott három vagy négy -1 elég valószínűtlen, hogy kezdetben három vagy négy -1-et dobjunk.

BTW, a fenti program feltételezi, ahogy a kérdésed végén mondod, hogy a játékosok konzervatívak, és csak negatív dobásokat fognak újra dobni. Ha a játékosok szeretnek kockáztatni, dönthetnek úgy, hogy nullákat is újradobnak, ebben az esetben az eredmények ehelyett így néznének ki:

Graph

Nézd meg, hogy az átlagok még mindig ugyanazok, de a magasabb képességek eredményei sokkal nagyobb szórással rendelkeznek. Különösen a pozitív képességű tökéletes négyes dobás valószínűsége sokkal nagyobb így.

(Az egyetlen különbség a fenti két grafikon létrehozásához használt programok között, hogy a második helyett -t használ.)

Különösen, ha a játékosok egy meghatározott minimális célszám ellen próbálnak dobni, akkor lehet értelme, hogy csak annyi nullát dobjanak, amennyi szükséges ahhoz, hogy maximalizálják az esélyüket a célszám elérésére.

Az optimális stratégia ebben az esetben attól függ, hogy a játékosok egyenként újradobhatják-e a kockákat, és minden dobás után eldönthetik, hogy folytatják-e az újradobást, vagy pedig először el kell dönteniük, hogy melyik kockát akarják újradobni, majd egyszerre kell dobniuk az összeset.

Az első esetben (pl. szekvenciális újradobások) az optimális döntési folyamatot egy rekurzív AnyDice függvénnyel lehet szimulálni:

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

Ez esetben a főfüggvény ROLL reroll up to SKILL target TARGET 1-et ad vissza, ha az adott dobás egyenlő vagy nagyobb, mint a cél, és 0-t, ha kisebb, mint a cél, és nincs lehetőség javításra (azaz nincs több kocka a poolban, nem lehet több újradobást végezni, vagy a legalacsonyabb kocka már +1). Ellenkező esetben eltávolítja a legalacsonyabb kockát a poolból (egy segédfüggvényt használva, mivel az AnyDice véletlenül sem rendelkezik megfelelő beépített függvénnyel), eggyel csökkenti a hátralévő újradobások számát, 1dF-et von le a célértékből, hogy szimulálja az egyszeri újradobást, majd rekurzívan meghívja magát.

A program kimenete egy kicsit nehézkes az AnyDice normál oszlop / vonaldiagram nézetéből elemezni, ezért inkább exportáltam, és lefuttattam a Python szkriptet ebből a korábbi válaszból, hogy egy szép kétdimenziós ráccsá alakítsam, amit a Google Sheets-be tudtam importálni. Az eredmények hőtérképként és többsávos grafikonként így néznek ki:

Screenshot

A második esetben (azaz az összes újradobás egyszerre) először ki kell találnunk, hogy valójában mi az optimális stratégia. Egy pillanatnyi gondolkodás után kiderül, hogy:

  • Minden -1-et mindig újra kell dobni, mivel ezzel soha nem lehet csökkenteni az eredményt. Mivel az újradobás várható átlagos eredménye 0, az összes -1-es újradobása után várható átlag megegyezik az eredeti dobás +1-eseinek számával.

  • A nullák újradobása nem változtatja meg a várható átlagos eredményt, de növeli a varianciát, azaz a tényleges eredmény nagyobb valószínűséggel távolodik el az átlagtól bármelyik irányban. Ezért csak akkor szabad újra nullát dobni, ha az összes -1-es újradobás után várható átlagos eredmény (azaz az eredeti dobás +1-eseinek száma) a célszám alatt van.

Az AnyDice-ban ezt a logikát alkalmazva valami ilyesmi programot kapunk:

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

A szkript kimenetének exportálása és futtatása ugyanazon a Python szkripten és táblázatkezelőn keresztül a következő hőtérképet és oszlopdiagramot adja:

Screenshot

Amint látható, az eredmények valójában nem különböznek annyira a szekvenciális újradobások esetétől. A legnagyobb különbségek magas képességek és köztes célszámok esetén jelentkeznek: például 4-es képesség esetén az, hogy az újradobásokat egyesével végezheted el, és bármelyik ponton megállhatsz, az átlagos sikerességi arányt 75,3%-ról 81%-ra emeli +1-es célszám esetén, vagy 51,6%-ról 58,3%-ra +2-es célszám esetén.

Ps. Sikerült kitalálnom egy módot arra, hogy az AnyDice összegyűjtse a fenti két program “sikerarány vs. cél” értékeit egyetlen eloszlásba minden egyes képességértékre, lehetővé téve, hogy ezeket közvetlenül az AnyDice rajzolja ki oszlopdiagramok vagy vonaldiagramok formájában (“legalább” módban) anélkül, hogy Pythont vagy táblázatkezelőt kellene használni.

Az ehhez szükséges AnyDice kód sajnos minden, csak nem egyszerű. A legnehezebb(!) résznek az bizonyult, hogy megtaláljuk a módját annak, hogy az AnyDice két valószínűséget kivonjon (pl. 1/2 – 1/3 = 1/6). Az általam ismert legjobb módja annak, hogy ezt a látszólag triviális feladatot az AnyDice-ban elvégezzük, feltételes valószínűségek nem triviális manipulációját és egy iterált hurkot foglal magában. És az AnyDice összeomlik, ha megpróbálod kiszámolni vele a 0 – 0-t.*

Mindenesetre, csak a teljesség kedvéért, itt van az AnyDice kódja a “legmagasabb verhető célpont” eloszlásának kiszámítására és ábrázolására különböző képességszintekre (és a fent leírt két újradobási mechanika mindegyikére), néhány megjegyzéssel kiegészítve az olvashatóság érdekében:

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

és egy képernyőkép a kimenetről (“legalább” vonaldiagram módban):

Graph

Egy megjegyzés a fenti program által generált kimenet értelmezéséhez: A fenti grafikonon látható valószínűségi eloszlások nem egyetlen kockadobási stratégia eredményeinek felelnek meg; ezek inkább mesterségesen konstruált eloszlások (azaz “egyéni kockák” az AnyDice zsargonjában) úgy, hogy az egyéni kocka egyetlen dobásával legalább \$N\$ dobás valószínűsége megegyezik annak a valószínűségével, hogy a játékos képes legalább \$N\$ dobni 4dF-en az adott újradobási mechanikával (egyenként vs. egyszerre) és az adott maximális újradobásszámmal, feltételezve, hogy a játékos az adott célhoz optimális újradobási stratégiát használ \$N\$.

Más szóval, ha a kimenetet “legalább” módban nézzük, láthatjuk, hogy egy 4-es szintű képességű játékosnak 51,62% esélye van arra, hogy sikeresen dobjon +2 vagy annál többet (a mindent egyszerre újradobási mechanika használatával), ha a rendelkezésre álló újradobásokat úgy használja fel, hogy maximalizálja az adott esélyt. A kimenet azt is helyesen mutatja, hogy ugyanennek a játékosnak 75,28% esélye van arra, hogy +1-et vagy többet dobjon, ha inkább erre optimalizál, de a két cél eléréséhez különböző újradobási stratégiákra lesz szükségük.

A fent leírt egyéni kockán pontosan +1-et dobott 23,65%-os “valószínűségnek” pedig valójában nincs értelmes jelentése, kivéve, hogy ez (a kerekítés miatt megközelítőleg) a 75,28% és az 51,62% közötti különbség. Gondolom, ezért is olyan nehéz kiszámítani az AnyDice segítségével. 😛 Azt hiszem, úgy is értelmezhetnéd, hogy bizonyos értelemben mennyivel nehezebb egy +2-es célt elérni az adott képességgel és újradobási mechanikával, mint egy +1-es célt, de nagyjából ennyi.

*) Ez az összeomlás kapcsolatban állhat azzal, amiről eléggé biztos vagyok benne, hogy egy hiba az AnyDice-ban, amit a kód fejlesztése közben találtam, és ami miatt az egyik korai tesztprogramom nagyon furcsa kimenetet generált olyan dolgokkal, mint 97284.21% valószínűség(!). A tesztprogram végül is összeomlik, ha tovább növeled az iterációk számát.