Jak v Anydice namodelovat fudge hod kostkou s opakovanými hody?

Tady je alternativní řešení:

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

Funkce by měla být většinou srozumitelná; jediná část, která může vyžadovat vysvětlení, je {1 .. #ROLL-N}@ROLL, která sčítá všechny prvky sekvence ROLL kromě posledních N. Ve výchozím nastavení AnyDice seřadí hody kostkou sestupně podle čísel, takže poslední prvky jsou nejnižší.

V grafickém režimu vypadají výstupy tohoto programu takto:

Graf

Všimněte si, že rozdíly mezi úrovněmi dovedností 2, 3 a 4 jsou poměrně malé, protože hod třemi nebo čtyřmi -1 na 4dF je pro začátek dost nepravděpodobný.

Výše uvedený program předpokládá, jak říkáš na konci své otázky, že hráči jsou konzervativní a budou přehazovat pouze záporné hody. Pokud vaši hráči rádi riskují, mohou se rozhodnout rerollovat i nuly, a v tom případě by výsledky místo toho vypadaly takto:

Graf

Všimněte si, že průměry jsou stále stejné, ale výsledky pro vyšší dovednosti mají mnohem větší rozptyl. Zejména pravděpodobnosti hodu perfektní čtyřky s kladnou dovedností jsou tímto způsobem mnohem vyšší.

(Jediný rozdíl mezi programy použitými k vygenerování obou výše uvedených grafů je ten, že druhý používá místo )

Zejména pokud se vaši hráči snaží házet proti určitému minimálnímu cílovému číslu, může pro ně mít smysl házet pouze tolik nul, kolik je potřeba, aby maximalizovali svou šanci na splnění cíle.

Optimální strategie v těchto případech závisí na tom, zda hráči mohou přehazovat kostky jednu po druhé a po každém hodu se rozhodnout, zda chtějí pokračovat v přehazování, nebo zda se musí nejprve rozhodnout, které kostky chtějí přehazovat, a pak je přehazovat všechny najednou.

V prvním případě (tj. v případě, že hráči hází kostkami, které chtějí přehazovat, se musí nejprve rozhodnout, které kostky chtějí přehazovat, a pak je přehodit všechny najednou. postupné přehazování) lze optimální rozhodovací proces simulovat pomocí rekurzivní funkce AnyDice:

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

Zde hlavní funkce ROLL reroll up to SKILL target TARGET vrací 1, pokud je daný hod stejný nebo větší než cíl, a 0, pokud je menší než cíl a není možné žádné zlepšení (tj. v zásobě už nejsou žádné kostky, není povoleno žádné další přehazování nebo nejnižší kostka je již +1). V opačném případě odstraní nejnižší kostku z fondu (pomocí pomocné funkce, protože AnyDice náhodou nemá vhodnou funkci zabudovanou v sobě), sníží počet zbývajících opakovaných hodů o jedna, odečte 1dF od cílové hodnoty, aby simuloval jeden opakovaný hod, a pak se rekurzivně zavolá.

Výstup tohoto programu je trochu nepohodlné analyzovat z normálního zobrazení sloupcového/čárového grafu AnyDice, takže jsem ho místo toho vyexportoval a prohnal skriptem Pythonu z této dřívější odpovědi, abych z něj udělal pěknou dvourozměrnou mřížku, kterou jsem mohl importovat do Google Sheets. Výsledky v podobě tepelné mapy a vícesloupcového grafu vypadají takto:

Snímek obrazovky

V druhém případě (tj. všechny rerolly najednou) musíme nejprve zjistit, jaká je vlastně optimální strategie. Chvilka přemýšlení ukáže, že:

  • Vždy je třeba rerollovat všechny -1, protože tím nikdy nelze snížit výsledek. Protože očekávaný průměrný výsledek přehození je 0, očekávaný průměr po přehození všech -1 se rovná počtu +1 v původním hodu.

  • Přehození nuly nemění očekávaný průměrný výsledek, ale zvyšuje rozptyl, tj. zvyšuje pravděpodobnost, že skutečný výsledek bude vzdálenější od průměru v obou směrech. Nuly by se tedy měly přehazovat pouze tehdy, pokud je očekávaný průměrný výsledek po přehazování všech -1 (tj. počet +1 v původním hodu) nižší než cílové číslo.

Při použití této logiky v AnyDice vznikne něco jako tento program:

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

Exportováním výstupu tohoto skriptu a jeho spuštěním přes stejný skript Pythonu a tabulkový procesor získáme následující heatmapu a sloupcový graf:

Snímek obrazovky

Jak vidíte, výsledky se vlastně příliš neliší od případu sekvenčního rerollu. Největší rozdíly se objevují u vysokých dovedností a středních cílových čísel: například u dovednosti 4 možnost provádět rerolly po jednom a zastavit se v libovolném bodě zvyšuje průměrnou úspěšnost ze 75,3 % na 81 % pro cíl +1 nebo z 51,6 % na 58,3 % pro cíl +2.

P. Podařilo se mi přijít na způsob, jak přimět AnyDice, aby shromáždil hodnoty „úspěšnost vs. cíl“ z obou výše uvedených programů do jediného rozdělení pro každou hodnotu dovednosti, což umožní, aby je AnyDice vykreslil přímo jako sloupcové nebo spojnicové grafy (v režimu „alespoň“), aniž by musel použít Python nebo tabulky.

Naneštěstí kód AnyDice, který to umožňuje, je všechno, jen ne jednoduchý. Nejtěžší(!) se ukázalo být nalezení způsobu, jak donutit AnyDice odečíst dvě pravděpodobnosti (např. 1/2 – 1/3 = 1/6). Nejlepší způsob, o kterém vím, jak tuto zdánlivě triviální úlohu v AnyDice provést, zahrnuje netriviální manipulaci s podmíněnými pravděpodobnostmi a iterovanou smyčku. A pokud se s ním pokusíte vypočítat 0 – 0, dojde k pádu AnyDice.*

Každopádně, jen pro úplnost, zde je kód AnyDice pro výpočet a vykreslení rozložení „nejvyššího porazitelného cíle“ pro různé úrovně dovedností (a pro každou ze dvou výše popsaných mechanik přetáčení) s několika komentáři přidanými kvůli čitelnosti:

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

a snímek obrazovky s výstupem (v režimu „alespoň“ čárového grafu):

Graf

Poznámka k interpretaci výstupu generovaného výše uvedeným programem: Jedná se spíše o uměle vytvořená rozdělení (tj. v žargonu AnyDice „vlastní kostky“) tak, že pravděpodobnost hodu alespoň \$N\$ při jediném hodu vlastní kostkou se rovná pravděpodobnosti, že hráč bude schopen hodit alespoň \$N\$ na 4dF s danou mechanikou přehazování (po jednom proti jednomu). najednou) a daným maximálním počtem přehození za předpokladu, že hráč použije optimální strategii přehození pro daný cíl \$N\$.

Jinými slovy, podíváme-li se na výstup v režimu „alespoň“, vidíme, že hráč s úrovní dovednosti 4 má 51,62% šanci, že úspěšně hodí +2 nebo více (při použití mechaniky přehození najednou), pokud využije své dostupné přehození způsobem, který tuto konkrétní šanci maximalizuje. Výstup také správně ukazuje, že tentýž hráč má 75,28% šanci hodit +1 nebo více, pokud se místo toho rozhodne pro optimalizaci, ale k dosažení těchto dvou cílů bude potřebovat různé strategie přetáčení.

A „pravděpodobnost“ 23,65% pro hod přesně +1 na vlastní kostce popsané výše skutečně nemá žádný rozumný význam, kromě toho, že je to (přibližně, kvůli zaokrouhlování) rozdíl mezi 75,28% a 51,62%. Což je asi důvod, proč se to s AnyDice tak špatně počítá 😛 Předpokládám, že by se to dalo interpretovat jako míra toho, o kolik těžší je splnit cíl +2 při použití dané dovednosti a mechaniky přehození než cíl +1, v nějakém smyslu, ale to je asi tak všechno.

*) Ten pád by mohl souviset s tím, čím jsem si docela jistý, že je to chyba v AnyDice, kterou jsem objevil při vývoji tohoto kódu a která způsobila, že jeden z mých raných testovacích programů generoval opravdu podivný výstup s věcmi jako 97284,21% pravděpodobností(!). Testovací program také nakonec spadne, pokud dále zvýšíte počet iterací.