19 votes

Combien de jets de D20 devraient être égaux ou supérieurs (20 - nombre d'essais) ?

J'ai ce qui est principalement une question de probabilité / de conception de jeu, mais qui a un objectif clair de jeu de rôle derrière elle.

Supposons que j'ai un test dans un système de jeu, utilisant un seul dé à 20 faces, qui nécessite un résultat supérieur ou égal à un nombre cible. Ce nombre cible commence à 20, mais à chaque essai suivant, il est réduit de 1.

Essentiellement, l'objectif à battre/égaler (sur un D20) = 20 - a donde a est le nombre de tentatives déjà effectuées (à partir de 0).

Je suppose que je peux continuer à essayer indéfiniment (bien qu'il y ait une limitation, comme une fois par jour ou le coût de l'essai).

Quel est le nombre moyen attendu de jets pour obtenir un succès dans ce cas (battre ou égaler l'objectif), et pouvez-vous montrer comment le calculer de manière générique ? Comment pourrait-il varier si l'objectif ou le taux de réduction changeait (par exemple, >= 15 - 2a) ?

Je serais également intéressé de savoir si vous avez déjà rencontré ce type de mécanisme et comment il a été utilisé.

24voto

Dave Points 541

Le nombre moyen de rouleaux est d'environ 5,294 pour le cas spécifique de la question.

La distribution de probabilité pour le cas spécifique est la suivante

$$ p(1) = \frac {1}{20} $$

pour \$n=1\$ et

$$ p(n) = \frac {n}{20} \prod_ {i=1}^{n-1} \left ( \frac {20-i}{20} \right ) $$

pour \$n>1\$ y \$p(n)\$ indique la probabilité que le succès se produise lors du énième jet.

La structure générale est la suivante

$$ p(n) = q_n \prod_ {i=0}^{n-1} (1-q_i) $$ donde \$0 \le q_i \le 1\$ est la probabilité de réussite du i'ème jet, avec la convention \$q_0=0\$ (vous ne pouvez pas avoir réussi avant votre premier jet)

Vous pouvez considérer le processus comme une sorte d'arbre :

        (1-q\_0)=1
       /   \\
     q\_1  (1-q\_1)                    # first roll
            /  \\
           q\_2 (1-q\_2)               # second roll
                /   \\ 
               q\_3  (1-q\_3)          # third roll
                      ...

Les branches de gauche indiquent la réussite du jet en question et les branches de droite indiquent l'échec de ce même jet. L'évaluation de la probabilité de réussite d'un jet donné consiste simplement à prendre le produit des facteurs le long du chemin menant à la branche "réussite" de ce niveau.

Pour le cas spécifique mentionné dans la question :

        start=1
       /   \\
     1/20  19/20                    # first roll
            /  \\
           2/20 18/20               # second roll
                /   \\ 
               3/20  17/20          # third roll
                      ...

À partir de là, nous pouvons tabuler les probabilités pour le cas spécifique :

n

p(n)

\Npour p(n)\Npour p(n)\Npour p(n)\Npour p(n)

1

0.05

0.05

2

0.095

0.19

3

0.12825

0.38475

4

0.14535

0.5814

5

0.14535

0.72675

6

0.130815

0.78489

7

0.106832

0.747824

8

0.0793611

0.6348888

9

0.0535687

0.4821183

10

0.0327364

0.327364

11

0.0180050

0.198055

12

0.00883884

0.10606608

13

0.00383016

0.04979208

14

0.00144368

0.02021152

15

0.000464039

0.006960585

16

0.000123743

0.001979888

17

2.62956e-05

0.0004470252

18

4.17635e-06

7.51743e-5

19

4.40937e-07

8.377803e-6

20

2.32020e-08

4.6404e-7

A partir de là, vous pouvez obtenir la moyenne de 5,294 comme la somme de \Npour p(n)\Npour p(n)\Npour p(n)\Npour p(n) .

19voto

import random Points 141

Pour ce type de question, il est probablement plus facile de le brancher sur un ordinateur. Cela permet également de bricoler plus facilement votre scénario. Voici un peu de code python :

import random
from statistics import mean

results = []
rounds = 1000000

for _ in range(rounds):
    tries = 0
    while True:
        target = 20 - tries
        tries += 1
        if random.randint(1,20) >= target: # randint includes endpoint
            results.append(tries)
            break
print(f"Mean of {rounds:,} turns is {mean(results):.3f}")

Un million de tours prend quelques secondes et m'indique que le nombre attendu de lancers est d'environ 5.3 .

Mean of 1,000,000 turns is 5.295

Si je change la cible en 20 - (2*attempts) ce qui donne 4,1 rouleaux attendus. 15 - (2*attempts) est de 2,4 rouleaux prévus.

Si vous n'avez pas python, il y a sites qui vous permettra d'exécuter du code. Si vous fixez un nombre de tours trop élevé, ils mettront fin à votre script. Dix millions de rouleaux prendront dix fois plus de temps et ne changeront probablement que le troisième ou le quatrième chiffre. Vous pouvez même partager le code pour que d'autres puissent l'inspecter, le modifier et le faire fonctionner.

Une autre alternative est AnyDice Il s'agit d'un site qui permet de créer facilement des scénarios tels que ceux que vous décrivez. Vous pouvez également consulter anydice a marqué des questions .

EDIT : Ajout de la distribution, suite du python précédent :

import matplotlib.pyplot as plt
import numpy as np
text = f"Mean of {rounds:,} rounds, [20 - a] is {mean(results):.3f}"
fig, ax = plt.subplots()
ax.set_xlabel('Rolls')
ax.set_title(text)
labels, counts = np.unique(results, return_counts=True)
ax.bar(labels, counts, align='center')  # bar > hist
plt.gca().set_xticks(labels)
plt.show()

Distribution Graph

11voto

Someone_Evil Points 42173

En moyenne ~5.294 rouleaux

En fait, ce n'est pas très compliqué, mais il y a quelques calculs à faire, que vous voudrez confier à un ordinateur (à moins que vous ne soyez passionné par ce genre de choses).

La première chose à prendre en compte est la probabilité d'obtenir exactement k qui exige que nous échouions d'abord k - 1, et réussir le k th.

$$ P(k) = \rho (k) \prod_ {i=1}^{k-1} (1- \rho (i)) $$

Dónde \$ \rho (x)\$ est la probabilité que le x Le jet est réussi : $$ \rho (x) = \frac {20-T+n(x-1)+1}{20} $$ donde T est notre objectif initial et n est l'abaissement de l'échelon par rapport à la tentative précédente.

Nous pouvons maintenant trouver la valeur de l'espérance de notre distribution de probabilité (c'est-à-dire le nombre moyen de tentatives) comme suit : \begin {align} E &= \sum_k k P(k) = \sum_k\left [ k \rho (k) \prod_ {i=1}^{k-1} (1- \rho (i)) \right ] \\ &= \sum_k\left [ k \frac {21-T+n(k-1)}{20} \prod_ {i=1}^{k-1} \frac {T+n-ni-1}{20} \right ] \end {align} Nous demandons alors à un ordinateur d'effectuer les calculs. Le calcul se simplifie considérablement pour n \=1, ce qui permet de réécrire la partie produit en termes de factorielle. Il convient de noter que vous devrez faire la somme de toutes les valeurs valides de k qui est aussi longue que \$k \leq 1+ \frac {T-1}{n}\$ . L'exécution du calcul pour T \= 20, n \= 1, nous obtenons \$E \approx5.294\ $ .

6voto

Eddymage Points 10140

Les outils de probabilité de base (avec l'aide d'une calculatrice) peuvent fournir une réponse facile.

Analysons le cas de la réussite au premier essai, au deuxième essai et au troisième essai, puis généralisons.

L'objectif au premier essai ( \$a=1\$ ) est de 20, la probabilité de succès est donc de 1/20.

En cas d'échec, avec une probabilité de 19/20, la cible devient 19 : le succès au 2ème jet est donné par

$$ \begin {eqnarray} P( \text {succès au 2ème jet}) &=& P( \text {échec au 1er lancer})\N- P( \text {succès au 2ème jet}) \\ &=& \frac {19}{20}\, \frac {1}{20}= \frac {19}{400}. \end {eqnarray} $$

Si nous avons besoin d'une troisième tentative, la formule est la suivante $$ \begin {eqnarray} P( \text {succès au 3ème jet}) &=& P( \text {échec au 1er lancer})\N- P( \text {échec à 2n rouleaux})\N- P( \text {succès au 3ème jet}) \\ &=& \frac {19}{20} \frac {18}{20} \frac {3}{20} \\ &=& \frac {1026}{8000}. \end {eqnarray} $$

Nous pouvons généraliser, en obtenant que la probabilité de succès à la \$i\$ -Le cinquième rouleau est $$ P( \text {success at } i \text {-ième rouleau}) = \frac { \displaystyle\prod_ {k=1}^{i-1}(20-k)}{20^{i-1}} \frac {i}{20}. $$ Ensuite, par formule classique pour la valeur attendue nous donne $$ { \rm E}[ \text {nombre de jets avant d'obtenir un succès}] = \sum_ {i=1}^{20} iP( \text {success at } i \text {) = 5,2936, $$

Il faut donc attendre entre 5 et 6 lancers avant d'obtenir un succès : le temps de calcul est de 1 à 2 heures. répondre par importation aléatoire confirme ce calcul.

Vous trouverez ci-dessous le tracé de la distribution, ainsi qu'une comparaison avec la distribution de Poisson.

Plot of the distribution, comparison with Poisson distribution for reference.

En tant que justforplaylists notes, le médiane est de 5 : cela signifie que 50 % du temps, vous attendez un nombre de jets inférieur ou égal à 5 pour obtenir un succès, dans les 50 % restants, vous devez attendre de 6 à 19 jets.

2voto

Leon Radley Points 1314

Si j'ai bien compris, la demande fondamentale est centrée sur la capacité à itérer (efficacement) sur les variations d'un mécanisme de jeu. Ceux qui sont plus compétents que moi sont mieux placés pour dériver une formule généralisée mathématiquement, mais je peux au moins fournir quelques moyens de bricolage, dans ce cas en utilisant dyce ¹ pour le calcul et anydyce ² pour une interaction rudimentaire.

Vous pouvez l'essayer dans votre navigateur : Try dyce [ source ]

base screenshot

Bien qu'un peu maladroite, l'interface permet à l'utilisateur de sélectionner un dé polygonal standard, de définir une cible et de choisir une fonction d'ajustement parmi deux méthodes prédéfinies identifiées dans la question initiale. Elle permet également des entrées personnalisées pour les utilisateurs avancés (plus d'informations à ce sujet ci-dessous).

Utilisation dyce pour modéliser le mécanicien

dyce utilise le calcul discret, ce qui signifie qu'il atteint la précision sans échantillonnage aléatoire. Il ne prend pas en compte les distributions continues, mais il vous donnera des résultats précis, à condition qu'il puisse modéliser le problème de manière adéquate.

Première tentative - Approche naïve (non performante)

Lorsque l'on définit le mécanicien dans des termes qui dyce peut comprendre, on pourrait faire quelque chose comme ce qui suit :

from dyce import H
from dyce.evaluation import HResult, foreach

def degrading_target_nonperformant(
  die: H,
  initial_target: int,
  prior_tries: int = 0,
) -> H:
  adjusted_target = initial_target - prior_tries
  current_tries = prior_tries + 1

  def _callback(d_result: HResult):
    if d_result.outcome >= adjusted_target:
      return current_tries
    else:
      return degrading_target_nonperformant(d_result.h, initial_target, prior_tries=current_tries)

  return foreach(
    _callback,
    die,
    limit=-1,  # do not limit recursion
  )

Bien que ce qui précède soit techniquement exact, le fait de chercher à savoir si chaque résultat atteint le seuil permet d'obtenir des performances de l'ordre de O( n !). En pratique, cela ne fonctionne que pour les petits dés ou les petites cibles. A mourir de H(20) y cible de 20 prendra des éons (peut-être littéralement).

d20 with target 3 --> 394 µs ± 4.57 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
d20 with target 4 --> 1.22 ms ± 9.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
d20 with target 5 --> 4.63 ms ± 81.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
d20 with target 6 --> 23.3 ms ± 239 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
...

Deuxième tentative - Approche affinée (performante)

La bonne nouvelle, c'est que nous pouvons introduire une astuce de comptage pour nous faciliter la tâche. Plutôt que de faire des branchements sur chaque résultat, nous pouvons faire des branchements sur la probabilité d'atteindre une cible particulière :

def degrading_target_performant(
  die: H,
  initial_target: int,
  prior_tries: int = 0,
) -> H:
  adjusted_target = initial_target - prior_tries
  current_tries = prior_tries + 1
  succeeds_or_fails_at_adjusted_target_h = die.ge(adjusted_target)

  def _callback(ge_result: HResult):
    if ge_result.outcome == 1:
      return current_tries
    elif ge_result.outcome == 0:
      return degrading_target_performant(die, initial_target, proir_tries=current_tries)
    else:
      assert False, "should never be here"

  return foreach(
    _callback,
    succeeds_or_fails_at_adjusted_target_h,
    limit=-1,  # do not limit recursion
  )

Notre facteur de branchement passe ainsi de n (le nombre de faces de notre dé) à 2, ce qui conduit à une performance de O( n ), ce qui est tout à fait raisonnable.

---- performant approach ----
d20 with target 3 --> 97.9 µs ± 542 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
d20 with target 6 --> 190 µs ± 1.55 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
d20 with target 9 --> 286 µs ± 2.69 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
d20 with target 12 --> 382 µs ± 4.02 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
d20 with target 15 --> 469 µs ± 7.75 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
d20 with target 18 --> 580 µs ± 4.27 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
...

Cela nous donne ce que nous voulons :

>>> d20 = H(20)
>>> h = degrading_target_performant(d20, 20)
>>> print(h.format(scaled=True))
avg |    5.29
std |    2.59
var |    6.68
  1 |   5.00% |#################
  2 |   9.50% |################################
  3 |  12.83% |############################################
  4 |  14.54% |##################################################
  5 |  14.54% |##################################################
  6 |  13.08% |############################################
  7 |  10.68% |####################################
  8 |   7.94% |###########################
  9 |   5.36% |##################
 10 |   3.27% |###########
 11 |   1.80% |######
 12 |   0.88% |###
 13 |   0.38% |#
 14 |   0.14% |
 15 |   0.05% |
 16 |   0.01% |
 17 |   0.00% |
 18 |   0.00% |
 19 |   0.00% |
 20 |   0.00% |

Amélioration - Fonction cible ajustée paramétrée

Nous pouvons maintenant passer à l'expérimentation. Nous avons déjà paramétré le dé et la cible que nous utilisons. Les codeurs astucieux noteront que nous pouvons refactoriser ce qui précède pour paramétrer également les moyens par lesquels nous calculons la cible ajustée. Le code source de notre version peut être trouvé à l'adresse suivante ici .

Entrées personnalisées

Pour ceux qui connaissent Python et dyce Il est possible de saisir son propre histogramme pour le dé (par ex, 2 @ H(6) pour 2d6) et on peut même définir sa propre fonction d'ajustement de cible personnalisée qui prend deux arguments ( cible y essais_précédents ) et renvoie la cible ajustée. La fonction personnalisée peut être définie comme un lambda ou il peut contenir du code Python à part entière (à condition qu'il définisse ou assigne un appelable de la signature appropriée à l'expression _ symbole).

Par exemple, pour voir comment le mécanisme fonctionnerait avec un d8 plus un d12 et une fonction d'ajustement qui réduit la cible à chaque nouvel essai, on pourrait utiliser un dé personnalisé de H(8) + H(12) et une fonction d'ajustement personnalisé de lambda target, prior_tries: target - prior_tries // 2 ou, si l'on préfère, quelque chose comme :

def my_adjustment_func(target: int, prior_tries: int) -> int:
  return target - prior_tries // 2

_ = my_adjustment_func  # <-- "export" the function by assigning it to the _ variable

customization screenshot

Une mise en garde s'impose lorsque vous expérimentez votre propre fonction d'ajustement : elle doit converger, c'est-à-dire qu'elle doit éventuellement avoir 100 % de chances de réussir (par exemple, une cible de la valeur minimale de l'intervalle de dé), ou le calcul ne se terminera jamais. Je laisse au lecteur le soin de réfléchir à la manière d'introduire une mesure de sauvegarde.

Réflexions finales

Je n'ai jamais trouvé qu'une moyenne autonome était un outil utile pour évaluer un mécanisme de jeu. Le d20 et le 4d4 ont tous deux la même moyenne, mais sont des animaux très différents. Dans la conception d'un jeu, les tests de jeu sont essentiels, mais un bon outil de visualisation peut vous aider à éliminer beaucoup de déchets avant même qu'ils n'arrivent sur la table. C'est probablement une question de goût, anydyce Les graphiques de "l'éclatement" de la Commission sont ma meilleure tentative (jusqu'à présent) d'obtenir une "sensation" pour éclairer ce processus d'élimination. J'espère que cela vous aidera à explorer votre mécanisme plus en profondeur !


¹ dyce est ma bibliothèque de probabilités de dés en Python.

² anydyce est ma couche de visualisation pour dyce est un substitut approximatif d'AnyDice.

AlleGamers.com

AlleGamers est une communauté de gamers qui cherche à élargir la connaissance des jeux vidéo.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X