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

ここに別の解決策があります:

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

関数はほとんど自明であるべきですが、説明が必要な部分は {1 .. #ROLL-N}@ROLL で、シーケンス ROLL の最後のN要素以外のすべての合計を算出します。 デフォルトでは、AnyDice はサイコロの出目を数値の降順でソートするので、最後の要素が最も低くなります。

グラフ モードでは、このプログラムの出力は次のようになります。

ところで、上記のプログラムは、質問の最後にあるように、プレイヤーが保守的で、負の出目だけをリロールすることを想定しています。 その場合、結果は次のようになります:

Graph

平均はまだ同じですが、高い技能の結果はより多くの分散を持っていることに注意してください。 特に、正のスキルでパーフェクト4を出す確率はこの方法でかなり高くなる。

(上の2つのグラフを生成するために使用したプログラムの唯一の違いは、2番目のグラフがの代わりにを使用していることだ。)

特に、プレイヤーが特定の最小目標数に対してロールしようとしている場合、目標を満たすチャンスを最大化するために必要なだけ0をロールすることは理にかなっているかもしれません。

この場合の最適戦略は、プレイヤーがサイコロを1つずつ転がし、転がすたびに転がし続けるかどうかを決めるか、最初に転がすサイコロを決めてから一度に全部転がすかによって決まる。

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

ここで、メイン関数ROLL reroll up to SKILL target TARGETは、与えられた出目が目標と同じかそれ以上であれば1を返し、目標より小さく改善が不可能な場合(つまり、プール内にもうサイコロが残っていない、これ以上の再ロールは許されない、一番下のダイスが既に+1である)には0を返します。) そうでなければ、プールから最も低いダイスを取り除き(AnyDice には適切な関数が組み込まれていないのでヘルパー関数を使用)、残りのリロールの数を 1 つ減らし、目標値から 1dF を引いて 1 つのリロールをシミュレートし、再帰的に自分自身を呼び出します。

このプログラムの出力は、AnyDice の通常の棒/折れ線グラフ ビューから解析するのが少し厄介なので、代わりにそれをエクスポートして、この以前の回答からの Python スクリプトを通して実行し、Google Sheets にインポートできる素敵な 2 次元グリッドに変更しました。 結果は、ヒート マップおよび複数棒グラフとして、次のようになります:

Screenshot

2番目のケース(つまり、一度にすべてのリロール)では、まず、最適戦略が実際に何であるかを把握する必要があります。 少し考えてみると、次のことがわかります。

  • -1の場合は常にリロールする必要があります。

  • 0を再ロールしても平均結果は変わらないが、分散が大きくなる、つまり、実際の結果がどちらかの方向に平均から離れる可能性が大きくなる。 したがって、すべての-1を再ロールした後の予想平均結果(つまり、最初のロールの+1の数)が目標数より低い場合にのみ、0を再ロールする必要がある。

AnyDice でこのロジックを適用すると、次のようなプログラムになります:

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

このスクリプトの出力をエクスポートして、同じ Python スクリプトとスプレッドシートで実行すると、次のヒート マップと棒グラフになります:

Screenshot

見てわかるように、結果は実際には順次再ロールのケースとそれほど変わりません。 たとえば、スキル 4 の場合、リロールを 1 回ずつ実行して任意の時点で停止できるため、平均成功率は、ターゲットが +1 の場合は 75.3% から 81% に、ターゲットが +2 の場合は 51.6% から 58.3% に上昇します

Ps. 私は、AnyDice が上記の 2 つのプログラムから「成功率 vs 目標」の値を各スキル値の単一の分布に収集し、Python またはスプレッドシートを使用せずに、棒グラフまたは折れ線グラフ(「少なくとも」モード)として AnyDice によって直接描画できるようにする方法を考え出すことに成功しました。 最も困難な (!) 部分は、AnyDice に 2 つの確率 (たとえば、1/2 – 1/3 = 1/6) を減算させる方法を見つけることであることが判明しました。 AnyDiceでこの一見つまらないタスクを実行するために私が知っている最良の方法は、条件付き確率の非自明な操作と反復ループを含んでいます。 そして、それで 0 – 0 を計算しようとすると、AnyDice がクラッシュします。*

とにかく、完全を期すために、さまざまなスキルレベル (および上記の 2 つの再ロールのメカニズムのそれぞれ) の「最高ビート可能ターゲット」の分布を計算およびプロットする AnyDice コードは、読みやすいようにいくつかのコメントを追加したものです。

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

そして、出力のスクリーンショット (「少なくとも」折れ線グラフ モード):

Graph

上記プログラムによって生成された出力の解釈に関するメモ。 上のグラフに示された確率分布は、単一のサイコロ転がし戦略の結果に対応するものではなく、カスタムダイスの1回の転がりで少なくとも” \$N$ “を出す確率が、与えられた転がり方(1回ずつか、それとも1回ずつ)でプレイヤーが4dFで少なくとも “Γ$N$ “を出せる確率に等しいよう人工的に作られた分布(AnyDice用語で「カスタムダイス」)であることを意味します。 つまり、”at least “モードでの出力を見ると、スキルレベル4のプレイヤーは、利用可能なリロールを最大化する方法で使用した場合、(all-at-once rerolling mechanicを使用して)+2以上を成功させる51.62%の確率を持つことがわかります。 出力は、同じプレイヤーが代わりに最適化することを選択した場合、+1 以上を転がす確率が 75.28% であることも正しく示しますが、これら 2 つの目標を達成するには異なる再ロール戦略が必要になります。

また、上記のカスタム ダイスで正確に +1 を転がす 23.65% という「確率」には、(丸めのため約) 75.28% と 51.62% との差を除いて、意味のあるものではありません。 そのため、AnyDice で計算するのは非常に困難です。 😛 ある意味、与えられたスキルと再ロールのメカニズムを使用して、+2 の目標を達成するのが +1 の目標よりもどれほど難しいかを示す指標と解釈することもできると思いますが、それはそれとして。) このクラッシュは、このコードを開発しているときに見つけたAnyDiceのバグと関係があるのかもしれません。 テスト プログラムはまた、反復回数をさらに増やすと最終的にクラッシュします。