Comment puis-je modéliser un jet de dés truqué avec des relances dans Anydice?
Voici une solution alternative:
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 "}
La fonction devrait être principalement auto-explicative ; la seule partie qui peut nécessiter une explication est {1 .. #ROLL-N}@ROLL
, qui additionne tous les éléments de la séquence ROLL
sauf les derniers N
. Par défaut, AnyDice trie 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 comment les différences entre les niveaux de compétence 2, 3 et 4 sont assez mineures, puisque lancer trois ou quatre -1 sur 4dF est assez improbable pour commencer.
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 également des zéros, auquel cas les résultats ressembleraient plutôt à ceci :
Notez comment les moyennes sont toujours les mêmes, mais 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 au lieu de
.)
En particulier, si vos joueurs essaient de lancer contre un nombre cible minimum spécifique, il peut être logique pour eux de ne lancer qu’autant de zéros que nécessaire pour maximiser leur chance d’atteindre la cible.
La stratégie optimale dans ces 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, puis les lancer tous en même temps.
Dans le premier cas (c’est-à-dire. relances séquentielles), le processus de décision optimal peut être simulé avec 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: result: \- reroll -\}loop TARGET over {-3..4} { loop SKILL over {0..4} { output named "target , skill " }}
Ici, la fonction principale ROLL reroll up to SKILL target TARGET
renvoie 1 si le jet donné est égal ou supérieur à la cible, et 0 s’il est inférieur à la cible et qu’aucune amélioration n’est possible (c’est-à-dire qu’il n’y a plus de dés dans le pool, qu’aucune relance n’est autorisée ou que le dé le plus bas est déjà un +1). Sinon, il retire le dé le plus bas du pool (en utilisant une fonction d’aide, puisque AnyDice ne se trouve pas avoir une 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 ensuite récursivement.
La sortie de ce programme est un peu maladroite à analyser à partir de la vue normale de graphique à barres / lignes d’AnyDice, donc je l’ai plutôt exportée et exécutée à travers le script Python de cette réponse précédente pour la transformer en une belle grille bidimensionnelle que je pourrais importer dans Google Sheets. Les résultats, sous forme de carte thermique et de graphique à barres multiples, ressemblent à ceci :
Dans le deuxième cas (c’est-à-dire tous les rerolls à la fois), nous devons d’abord déterminer quelle est réellement la stratégie optimale. Un moment de réflexion montre que :
-
On devrait toujours relancer tous les -1, puisque le faire ne peut jamais diminuer le résultat. Puisque le résultat moyen attendu d’un reroll est 0, la moyenne attendue après avoir rerollé tous les -1 est égale au nombre de +1 du jet initial.
-
Reroller un zéro ne change pas le résultat moyen attendu, mais augmente la variance, c’est-à-dire qu’il 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.
Appliquer cette logique dans AnyDice donne quelque chose comme ce programme :
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 " }}
Exporter la sortie de ce script et l’exécuter à travers le même script Python et la feuille de calcul donne la carte thermique et le graphique à barres suivants :
Comme vous pouvez le voir, les résultats ne sont en fait pas si différents du cas des relances séquentielles. Les plus grandes différences se produisent avec des compétences élevées et des nombres de cibles intermédiaires : par exemple, avec une compétence de 4, le fait de pouvoir effectuer les rerolls un par un et de s’arrêter à tout 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 à la cible » des deux programmes ci-dessus en une seule distribution pour chaque valeur de compétence, ce qui permet de les dessiner directement par AnyDice sous forme de diagrammes à barres 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. La partie la plus difficile( !) s’est avérée être de trouver un moyen de faire en sorte qu’AnyDice soustraie deux probabilités (par exemple 1/2 – 1/3 = 1/6). La meilleure façon que je connaisse pour réaliser cette tâche apparemment triviale dans AnyDice implique une manipulation non triviale des probabilités conditionnelles et une boucle itérative. Et elle fait planter AnyDice si vous essayez de calculer 0 – 0 avec elle.*
En tout cas, juste pour être complet, voici le code AnyDice pour calculer et tracer la distribution de la « plus haute cible battable » pour différents niveaux de compétences (et pour chacune des deux mécaniques de relances décrites ci-dessus) avec quelques commentaires ajoutés pour la lisibilité :
\- 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"}
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é présentées sur le graphique ci-dessus ne correspondent pas aux résultats d’une stratégie unique de lancer de dés ; il s’agit plutôt de distributions construites artificiellement (c’est-à-dire des » dés personnalisés » dans le jargon d’AnyDice) telles que la probabilité de lancer au moins \$N\$ sur un seul lancer du dé personnalisé est égale à la probabilité que le joueur puisse lancer au moins \$N\$ sur 4dF avec la mécanique de relancement donnée (un à la fois vs. En d’autres termes, en regardant la sortie 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 le mécanisme de relance tout d’un coup) s’il utilise ses relances disponibles de la manière qui maximise cette chance particulière. La sortie montre aussi correctement que le même joueur a 75,28% de chances de réussir à obtenir +1 ou plus s’il choisit d’optimiser pour cela à la place, mais il aura besoin de stratégies de relances différentes pour atteindre ces deux objectifs.
Et la « probabilité » de 23,65% de réussir à obtenir exactement +1 sur le dé personnalisé décrit ci-dessus n’a vraiment aucune signification sensible, si ce n’est que c’est (approximativement, à cause des arrondis) 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, mais c’est à peu près tout.
*) Ce crash pourrait être lié à ce que je suis à peu près sûr est un bug dans AnyDice que j’ai trouvé tout en développant ce code, causant l’un de mes premiers programmes de test pour générer des sorties 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.