Voici une solution de rechange :
FUDGE: {-1, 0, +1}
function: ROLL:s reroll up to SKILL:n {
N: [lowest of SKILL and [count -1 in ROLL]]
result: NdFUDGE + {1 .. #ROLL-N}@ROLL
}
loop SKILL over {0..4} {
output [4dFUDGE reroll up to SKILL] named "skill [SKILL]"
}
La fonction devrait être assez explicite ; la seule partie qui peut nécessiter une explication est la suivante {1 .. #ROLL-N}@ROLL
qui résume tout sauf le dernier N
éléments de la séquence ROLL
. Par défaut, AnyDice classe les lancers de dés dans l'ordre numérique décroissant, de sorte que les derniers éléments sont les plus bas.
En mode graphique, les sorties de ce programme ressemblent à ceci :
Notez que les différences entre les niveaux de compétence 2, 3 et 4 sont relativement mineures, car il est assez improbable de lancer trois ou quatre -1 sur 4dF.
BTW, le programme ci-dessus suppose, comme vous le dites à la fin de votre question, que les joueurs sont conservateurs et ne relancent que les jets négatifs. Si vos joueurs aiment prendre des risques, ils pourraient décider de relancer des zéros également, auquel cas les résultats seraient les suivants comme ceci à la place :
Notez que les moyennes sont toujours les mêmes, mais que les résultats pour les compétences supérieures ont beaucoup plus de variance. En particulier, les probabilités de lancer un quatre parfait avec une compétence positive sont beaucoup plus élevées de cette façon.
(La seule différence entre les programmes utilisés pour générer les deux graphiques ci-dessus est que le second utilise [count {0, -1} in ROLL]
au lieu de [count -1 in ROLL]
.)
En particulier, si vos joueurs essaient d'obtenir un nombre cible minimum spécifique, il peut être judicieux qu'ils n'obtiennent qu'autant de zéros que nécessaire pour maximiser leurs chances d'atteindre l'objectif.
La stratégie optimale dans ce cas dépend du fait que les joueurs peuvent relancer les dés un par un, et décider après chaque lancer s'ils veulent continuer à relancer, ou s'ils doivent d'abord décider quels dés ils veulent relancer et les lancer tous en même temps.
Dans le premier cas (c'est-à-dire les relances séquentielles), le processus de décision optimal peut être simulé par la formule suivante une fonction récursive 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: [first #ROLL-1 of ROLL]
result: [FIRST reroll up to SKILL-1 target TARGET - 1dFUDGE] \- reroll -\
}
loop TARGET over {-3..4} {
loop SKILL over {0..4} {
output [4dFUDGE reroll up to SKILL target TARGET] named "target [TARGET], skill [SKILL]"
}
}
Ici, la fonction principale ROLL reroll up to SKILL target TARGET
renvoie 1 si le jet donné est égal ou supérieur à l'objectif, et 0 s'il est inférieur à l'objectif et qu'aucune amélioration n'est possible (c'est-à-dire qu'il n'y a plus de dés dans la réserve, qu'aucun relancement n'est autorisé ou que le dé le plus bas est déjà un +1). Sinon, il retire le dé le plus bas de la réserve (en utilisant une fonction d'aide, puisque AnyDice n'a pas de fonction appropriée intégrée), diminue le nombre de relances restantes de un, soustrait 1dF de la valeur cible pour simuler une seule relance et s'appelle lui-même récursivement.
La sortie de ce programme est un peu difficile à analyser à partir de l'affichage normal du graphique en barres ou en lignes d'AnyDice. l'a exporté et l'a fait passer dans le Script Python de cette réponse précédente pour le transformer en une jolie grille en deux dimensions que je pourrais importation dans Google Sheets . Les résultats, sous forme de carte thermique et de graphique à barres multiples, ressemblent à ceci :
Dans le second cas (c'est-à-dire tous les relances en même temps), nous devons d'abord déterminer quelle est la stratégie optimale. Un moment de réflexion montre que :
-
Il faut toujours relancer tous les -1, car cela ne peut jamais diminuer le résultat. Puisque le résultat moyen attendu d'un relancement est 0, la moyenne attendue après avoir relancé tous les -1 est égale au nombre de +1 du lancer initial.
-
Relancer un zéro ne change pas le résultat moyen attendu, mais il fait augmente la variance, c'est-à-dire qu'elle rend le résultat réel plus susceptible de s'éloigner de la moyenne dans un sens ou dans l'autre. Ainsi, on ne devrait relancer des zéros que si le résultat moyen attendu après avoir relancé tous les -1 (c'est-à-dire le nombre de +1 du lancer initial) est inférieur au nombre cible.
En appliquant cette logique dans AnyDice, on obtient quelque chose comme ce programme :
FUDGE: {-1, 0, +1}
function: ROLL:s reroll up to SKILL:n target TARGET:n {
if [count +1 in ROLL] >= TARGET {
N: [lowest of SKILL and [count -1 in ROLL]]
} else {
N: [lowest of SKILL and [count {0, -1} in ROLL]]
}
result: (NdFUDGE + {1 .. #ROLL-N}@ROLL) >= TARGET
}
loop TARGET over {-3..4} {
loop SKILL over {0..4} {
output [4dFUDGE reroll up to SKILL target TARGET] named "target [TARGET], skill [SKILL]"
}
}
Exportation de la sortie de ce script et l'exécuter à travers le même script Python y feuille de calcul donne la carte thermique et le graphique à barres suivants :
Comme vous pouvez le voir, les résultats ne sont pas vraiment différents de ceux du cas des relances séquentielles. Les plus grandes différences se produisent avec des compétences élevées et des cibles intermédiaires : par exemple, avec une compétence de 4, le fait de pouvoir effectuer les relances une par une et de s'arrêter à n'importe quel moment fait passer le taux de réussite moyen de 75,3% à 81% pour une cible de +1, ou de 51,6% à 58,3% pour une cible de +2.
Ps. J'ai réussi à trouver un moyen de faire en sorte qu'AnyDice rassemble les valeurs de "taux de réussite par rapport à l'objectif" des deux programmes ci-dessus en une seule distribution pour chaque valeur de compétence, ce qui permet à AnyDice de les dessiner directement sous forme d'histogrammes ou de graphiques linéaires (en mode "au moins") sans avoir à utiliser Python ou des feuilles de calcul.
Malheureusement, le code AnyDice pour faire cela est tout sauf simple. Le plus difficile ( !) a été de trouver un moyen de faire en sorte que AnyDice soustraie deux probabilités (par exemple 1/2 - 1/3 = 1/6). La meilleure façon que je connaisse pour réaliser cela tâche apparemment insignifiante dans AnyDice implique une manipulation non triviale de probabilités conditionnelles et une boucle itérative. Et il fait planter AnyDice si vous essayez de calculer 0 - 0 avec lui.*
Quoi qu'il en soit, juste pour être complet, voici le code AnyDice pour calculer et tracer la distribution de la "cible la plus élevée à battre" pour différents niveaux de compétence (et pour chacun des deux mécanismes de relance décrits ci-dessus) avec quelques commentaires ajoutés pour faciliter la lecture :
\- 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: [exclude 0 from DIFF]
DIFF: (DIFF != 0) * [sign of TEMP + TEMP]
}
result: [exclude -1 from DIFF]
}
\- 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: [first #ROLL-1 of ROLL] \- remove last (=lowest) original roll -\
TNEW: TARGET - 1dFUDGE \- adjust target value depending on reroll -\
result: [FIRST reroll one at a time up to SKILL-1 target TNEW] \- 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 [count +1 in ROLL] >= TARGET {
N: [lowest of SKILL and [count -1 in ROLL]]
} else {
N: [lowest of SKILL and [count {0, -1} in ROLL]]
}
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: [4dFUDGE reroll one at a time up to SKILL target TARGET + 1]
} else {
PROB: [4dFUDGE reroll all at once up to SKILL target TARGET + 1]
}
DIST: [if d{MIN..TARGET} < TARGET then DIST else [if [PREV minus PROB] then TARGET else BOGUS]]
PREV: PROB
}
result: [exclude BOGUS from DIST]
}
\- finally we just loop over possible skill values and output the results -\
loop SKILL over {0..4} {
output [collect results for SKILL from -4 to 4 sequential 1] named "skill [SKILL], one at a time"
}
loop SKILL over {0..4} {
output [collect results for SKILL from -4 to 4 sequential 0] named "skill [SKILL], all at once"
}
et une capture d'écran de la sortie ( en mode "au moins" graphique linéaire ) :
Une note sur l'interprétation de la sortie générée par le programme ci-dessus : Les distributions de probabilité montrées sur le graphique ci-dessus ne correspondent pas aux résultats d'une seule stratégie de lancer de dés. mais plutôt des distributions construites artificiellement (c'est-à-dire des "dés personnalisés" dans le jargon d'AnyDice) de telle sorte que la probabilité de lancer au moins \$N\$ sur un seul lancer du dé personnalisé est égale à la probabilité que le joueur soit capable de lancer au moins \$N\$ sur 4dF avec le mécanisme de relancement donné (un à la fois contre tous en même temps) et le nombre maximum de relances donné, en supposant que le joueur utilise la stratégie de relancement optimale. pour cette cible particulière \$N\$ .
En d'autres termes, en regardant le résultat en mode "au moins", nous pouvons voir qu'un joueur avec un niveau de compétence 4 a 51,62% de chances de réussir un jet de +2 ou plus (en utilisant la mécanique de relance tout de suite) s'il utilise ses relances disponibles de la manière qui maximise cette chance particulière . Le résultat montre aussi correctement que le même joueur a 75,28 % de chances d'obtenir +1 ou plus s'il choisit d'optimiser pour cela, mais ils auront besoin de stratégies de relance différentes. pour atteindre ces deux objectifs.
Et la "probabilité" de 23,65% pour le roulement exactement +1 sur le dé personnalisé décrit ci-dessus n'a pas vraiment de sens, sauf qu'il s'agit (approximativement, en raison des arrondis) de la différence entre 75,28% et 51,62%. Je suppose que vous pourriez l'interpréter comme une mesure de la difficulté à atteindre un objectif de +2 en utilisant la compétence et le mécanisme de relance donnés, par rapport à un objectif de +1, dans un certain sens, mais c'est à peu près tout.
*) Ce crash pourrait être lié à ce dont je suis presque sûr est un bug dans AnyDice que j'ai trouvé pendant le développement de ce code, causant un de mes premiers programmes de test pour générer sortie vraiment bizarre avec des choses comme 97284,21% de probabilités( !). Le programme de test finit également par se planter si vous augmentez encore le nombre d'itérations.