10 votes

Comment calculer les probabilités pour les pools de dés éliminatoires (mécanique d'annulation des dés) dans Neon City Overdrive ?

Le jeu Neon City Overdrive utilise le mécanisme de résolution suivant pour les contrôles :

  1. créer une réserve de dés d'action et (éventuellement) une autre réserve de dés de danger de couleur différente (tous des d6, généralement jusqu'à 5 ou 6 dés dans chaque réserve)
  2. lancer tous les dés
  3. chaque dé de danger annule un dé d'action de même valeur - les deux sont défaussés
  4. le dé d'action le plus élevé restant (s'il y en a un) est le résultat (dont la signification précise n'est pas pertinente aux fins de la présente question).
    • chacun supplémentaire le dé d'action restant montre un 6 (c'est-à-dire tout deuxième, troisième, etc. au-delà du premier "6" qui est lu comme le résultat du jet) donne un succès critique (appelé "6"). boon )

J'ai du mal à trouver la bonne façon de modéliser les probabilités de ce mécanisme dans Anydice.

Je me rends compte qu'un bon point de départ serait cette réponse a une question très similaire concernant le mécanicien en Technoir (qui a manifestement été une source d'inspiration pour Neon City Overdrive ). Malheureusement, malgré tous mes efforts, je ne peux pas dire que je comprenne parfaitement le fonctionnement du code fourni, et il y a une différence importante entre les deux jeux : en Technoir a unique La "filière négative" élimine tous correspondant à un "dé positif", alors qu'en SOUS-OFFICIER cela se fait sur une base individuelle.

Je vous serais très reconnaissant pour toute aide.

10voto

Carcer Points 60981

Le texte suivant La fonction anydice calculera les résultats attendus pour la mécanique que vous décrivez :

function: nco AD:s DD:s {
  if [count 6 in AD] > [count 6 in DD] { result: 6 + ([count 6 in AD] - [count 6 in DD] - 1)}
  loop X over {5,4,3,2,1} {
    if [count X in AD] > [count X in DD] { result: X }
  }
  result: 0
}

Cette fonction s'attend à être alimentée par deux pools de dés (A et D au début du lien) - les dés d'action, AD et le dé de danger, DD qui sont transformées en séquences dans la fonction ( :s ) afin de les réparer et de les inspecter. La caractéristique principale est que pour une valeur X donnée, nous pouvons facilement déterminer s'il y a des dés non annulés montrant X dans la réserve de dés d'action en comptant le nombre de dés montrant X et en le comparant au nombre de dés similaires dans la réserve de dés de danger. Ainsi, si [count X in AD] est supérieur à [count X in DD] nous savons qu'il y a des dés non éliminés montrant X dans la réserve de dés d'action. La fonction itère sur les valeurs de X en partant de 6 jusqu'à 1 - le premier dé non éliminé qu'elle trouve est donc le résultat. (Si tous les dés d'action sont éliminés, le résultat est 0).

J'ai également ajouté un cas spécial pour les cas où le résultat est un 6, afin de pouvoir compter tous les avantages possibles - encore une fois simplement calculés en comparant le nombre de 6 dans AD a DD et en ajoutant 1 au résultat pour tout 6 supplémentaire, de sorte qu'un résultat final de 7 doit être lu comme 6 et 1 avantage, par exemple.

Malheureusement, bien qu'algorithmiquement simple, j'ai découvert que cette fonction ne respectait pas la limite de calcul de 5 secondes d'anydice lorsqu'on lui donnait des pools de dés situés à l'extrémité supérieure des plages spécifiées - l'espace des possibilités de séquences pour des pools de 5d6 ou 6d6 est très grand, et anydice abandonnera si les deux pools sont de 5d6 ou plus. Réponse d'Ilmari décrit toutefois une approche plus complexe sur le plan conceptuel, mais plus efficace sur le plan des calculs, qui permet de gérer des pools plus importants que mon script naïf.

10voto

trjh Points 11

Il es Il est possible de calculer ces résultats de manière efficace en utilisant AnyDice. Ce n'est tout simplement pas très facile ou intuitif.

Tout d'abord, cette version optimisée de Code de Carcer fonctionne bien jusqu'à 5d6 contre 5d6, mais ne fonctionne pas pour des pools plus importants :

function: neon city overdrive ACTION:s DANGER:s {
  BOON: (ACTION = 6) - (DANGER = 6)
  if BOON > 0 { result: 5 + BOON }
  loop N over {5,4,3,2,1} {
    if (ACTION = N) > (DANGER = N) { result: N }
  }
  result: 0
}

output [neon city overdrive 5d6 5d6]

Tout ce que j'ai fait pour accélérer le code de Carcer, c'est de remplacer les appels à la fonction intégrée [count N in SEQ] avec la fonction (SEQ = N) qui fait la même chose mais un peu plus rapidement. Il est certainement possible de micro-optimiser davantage le code par exemple en déroulant la boucle et en revenant plus tôt si le jet d'action le plus élevé n'est pas annulé, mais aucune de ces optimisations n'aide à franchir la barrière 5d6 contre 5d6. Avec plus de 10 dés au total, il y a trop de combinaisons pour les énumérer.


En général, la meilleure façon de faire en sorte que le code d'AnyDice gère plus rapidement les grands ensembles de dés est de ré-étiqueter les dés pour fusionner les faces équivalentes, afin qu'AnyDice ne perde pas de temps à tester un trop grand nombre de jets équivalents possibles. Par exemple, si vous voulez calculer le nombre de jets atteignant ou dépassant un certain seuil 1 T 6 en N d6, puis Nd(d6 >= T) est un lot plus rapide que [count {T..6} in Nd6] . En général, ce type d'optimisation fonctionne très bien dans toutes les situations où, si l'on lance un dé physiquement, on divise les dés obtenus en quelques groupes grossiers (comme "au moins T "et "en dessous T ") et comptez le nombre de dés dans chaque groupe, ou mettez de côté certains des dés lancés comme "ceux-là n'ont pas d'importance". Mais il n'y a pas de moyen évident d'appliquer ce type d'optimisation ici, puisque tous les jets de dés potentiellement si tous les autres sont annulés.

ou existe-t-il ?

N'oubliez pas que les petits jets n'ont d'importance que dans le cadre de ce mécanisme si tous les jets de dé d'action supérieure ont été annulés. Imaginons donc que nous lancions les dés et que nous les comptions pas à pas, comme suit :

  1. Divisez les dés roulés en deux groupes : "six" et "tout ce qui est inférieur à six". Si le groupe "six" contient plus de dés d'action que de dés de danger, arrêtez : le résultat est de 6 (plus des points d'orgue égaux au nombre de six non annulés moins un).

  2. Sinon, mettez de côté tous les six et divisez le reste des dés en deux groupes : "cinq" et "tout ce qui est inférieur à cinq". Si le groupe "cinq" contient plus de dés d'action que de dés de danger, arrêtez : le résultat est 5.

  3. Sinon, mettez de côté tous les cinq et divisez le reste des dés en deux groupes : "quatre" et "tout ce qui est inférieur à quatre". Si le groupe "quatre" contient plus de dés d'action que de dés de danger, arrêtez : le résultat est 4.

et ainsi de suite jusqu'à ce que, finalement, si tous les dés ayant obtenu un résultat de 2 ou plus ont été annulés, nous finissons par comparer simplement le nombre de dés d'action et de dés de danger restants pour déterminer si le résultat est 1 ou 0.

La caractéristique importante de ce processus est la suivante, à chaque étape les nombres réels lancés sur les dés dans le "tout ci-dessous N "n'ont pas d'importance - ils sont tous équivalents. En fait, nous pourrions relancer tous les dés du groupe "tout ce qui est inférieur à". N "après chaque étape (en s'assurant que tous les rerolls sont toujours inférieurs à la valeur de N ) et la répartition des résultats ne changerait pas.

Ou, si nous voulions vraiment être bizarres, nous pourrions commencer par lancer un tas de dés à six faces personnalisés avec toutes les faces sauf 6 vierges .

Ensuite, après la première étape ci-dessus, nous pourrions remplacer tous les rouleaux vierges par des rouleaux personnalisés. à cinq côtés un dé, dont toutes les faces sauf 5 sont vierges, lancez ce dé et passez à la deuxième étape.

Et si la deuxième étape ne donnait pas non plus de résultat, nous pourrions remplacer les rouleaux vierges par des rouleaux personnalisés. quadruple face un dé, dont toutes les faces sauf 4 sont vierges, lancez ce dé, et ainsi de suite

Évidemment, ce serait une façon vraiment bizarre et inefficace de lancer les dés à la table. Mais c'est une façon très efficace de le faire dans AnyDice, puisque cela signifie que, par exemple, pour les jets qui contiennent un ou plusieurs six non annulés, nous n'avons pas besoin de considérer toutes les combinaisons possibles de jets inférieurs.

Il suffit de écrire une fonction d'aide qui s'appelle elle-même récursivement :

function: nco helper N:n AMAX:n DMAX:n AROLL:n DROLL:n {
  if AROLL > DROLL {
    if N = 6 { result: 5 + AROLL - DROLL }
    result: N
  }
  A: AMAX - AROLL
  D: DMAX - DROLL
  if N = 2 { result: A > D }
  X: d(N-1) = N-1
  result: [nco helper N-1 A D AdX DdX]
}

function: nco A:n D:n {
  X: d6 = 6
  result: [nco helper 6 A D AdX DdX]
}

loop A over {6} {
  loop D over {0..6} {
    output [nco A D] named "action [A]d6, danger [D]d6"
  }
}

Dans le code ci-dessus, N est le résultat le plus élevé possible à l'étape actuelle (initialement 6), et donc aussi le nombre que nous retournerons comme résultat si nous trouvons un jet d'action non annulé à cette étape (avec le nombre de boons ajoutés au résultat si N = 6 ). AMAX y DMAX sont les nombres totaux de dés d'action et de dés de danger restants à cette étape, respectivement, tandis que AROLL y DROLL sont les numéros des dés qui ont été lancés N .

La fonction d'aide compare d'abord AROLL y DROLL Si le premier est plus grand, nous avons trouvé un jet d'action non annulé et nous nous arrêtons. Sinon, il calcule le nombre de jets d'action et de danger restants, A y B et construit un N-1 matrice à deux faces X avec un côté hors de N numéroté 1 et le reste numéroté 0. Cela donne AdX correspondent à la distribution du nombre de N-1 roule dans une piscine de A dés avec N-1 de chaque côté, et de la même manière pour DdX ; ces rouleaux sont ensuite transmis de manière récursive à une autre instance de la fonction d'aide en tant que AROLL y DROLL AnyDice évalue alors l'appel récursif pour chaque combinaison possible de ces jets.

Les boucles situées en bas du code appellent la fonction [nco A D] pour des nombres variables de dés d'action et de dés de danger, et affiche les résultats. Par défaut, le code utilise 6 dés d'action et une plage de dés de danger de 0 à 6, mais vous pouvez bien sûr modifier les plages de boucles comme vous le souhaitez. Le résultat par défaut, représenté sous forme de graphique, ressemble à ceci :

AnyDice screenshot

Au fait, à quel point cette solution récursive est-elle plus rapide ? Eh bien, d'après quelques tests, elle fonctionne bien jusqu'à 7d6 contre 7d6, mais s'arrête pour 8d6 contre 8d6. Ce qui n'est pas si mal, étant donné que le nombre de combinaisons possibles augmente exponentiellement avec le nombre de dés. (Cela pourrait être rendu beaucoup plus rapide si AnyDice supportait mémorisation mais hélas, ce n'est pas le cas). En tout cas, c'est au moins plus que suffisant pour répondre à l'exigence de l'OP de "jusqu'à 5 ou 6 dés dans chaque pool".

6voto

HellSaint Points 35493

Il s'agit d'un problème analytique très difficile

La question que vous avez mise en lien, bien que déjà assez difficile, est plus facile parce qu'elle peut être calculée comme la probabilité qu'un dé soit dans un jeu et pas dans l'autre. Dans votre question, cela se fait sur une base univoque, comme vous l'avez mentionné, et il s'agit d'un problème très difficile.

Peu importe - faites-le de la manière la plus simple possible

À moins que vous n'ayez besoin de l'expression analytique pour quelque chose, je vous recommande d'opter pour Monte Carlo : Vous effectuez une simulation aléatoire un paquet de fois, puis de voir la distribution de ces données. Ce code pour MATLAB le fait - je suis sûr que quelqu'un peut le traduire en Python ou quelque chose qui n'a pas besoin d'être payé, mais je suis plus familier avec MATLAB haha

N_it = 10^5;

N_action = 6;
N_danger = 5;

bins = 1:6;

for i = 1:N_it

    Action_Dice = randi([1 6], N_action, 1);
    Danger_Dice = randi([1 6], N_danger, 1);

    Action_Dice_Count = hist(Action_Dice, bins);
    Danger_Dice_Count = hist(Danger_Dice, bins);

    Result_Count = Action_Dice_Count - Danger_Dice_Count;

    Result = find(Result_Count > 0, 1, 'last');

    if(Result)
        Result_Save(i) = Result;
    else
        Result_Save(i) = 0;
    end
end

Ce code fait donc essentiellement ceci : lancer un nombre déterminé de dés d'action et de dés de danger, compter le nombre de chaque dé obtenu, soustraire le nombre de dés de danger du nombre de dés d'action, puis trouver la dernière valeur supérieure à zéro (c'est-à-dire le dé d'action le plus élevé restant) et enfin, s'il n'y a pas de dé d'action de ce type, mettre le résultat à 0.

Par exemple, pour 6 dés d'action et 5 dés de danger, voici à quoi cela ressemble : enter image description here

Pourquoi cela se présente-t-il ainsi ? Dans l'exemple, j'ai utilisé 6 dés d'action et 5 dés de danger. La probabilité qu'il ne reste aucun dé d'action est évidemment nulle, puisqu'il reste toujours un dé. Mais ce n'est pas tout, il y a une forte probabilité sur 6, pourquoi ? Parce qu'à chaque fois qu'il reste un 6, c'est ce 6 qui sera choisi. Il s'agit en fait d'un problème analytique plus simple. Soit \$X\$ dénote le nombre de six apparaissant dans la réserve de dés d'action, et \$Y\$ est le nombre de six apparaissant dans la réserve de dés de danger. Nous nous intéressons à \N- P(X > Y)\N- P(X > Y)\N- P(X > Y) . Mais ce qui est bien dans ce cas, c'est que nous connaissons la distribution exacte des deux \$X\$ y \$Y\$ qui sont les distributions binomiales le nombre d'essais étant égal au nombre de dés dans la réserve et la probabilité de chaque essai étant égale à 1/6.

Nous pouvons alors calculer \$P(X - Y)\$ , ce qui peut être fait par le convolution de \$P(X)\$ y \$P(-Y)\$ et enfin calculer \NP(X - Y > 0)\NP(X - Y > 0)\NP(X - Y > 0) . Je ne vais pas m'embêter avec tous ces calculs ici, mais si vous les faites, vous découvrirez que la probabilité est de 0,383367984110654. La probabilité trouvée par le code était de 0,386, donc, assez proche, le code semble avoir un sens.

Notez que cette même stratégie de calcul analytique des autres probabilités ne sera pas utile pour d'autres valeurs que 6, puisque, non seulement vous avez besoin que, par exemple, le nombre de cinq dans la réserve Action soit plus grand que le nombre dans la réserve Danger, mais vous avez également besoin que le nombre de six dans la réserve Action soit plus petit ou égal au nombre dans la réserve Danger. Pour 5, il s'agit déjà d'un ensemble de probabilités conditionnelles compliquées, et cela deviendra de plus en plus difficile pour les nombres inférieurs, puisque vous devez à chaque fois prendre en compte les nombres supérieurs.

Calculer les avantages

Le concept analytique précédent peut être utilisé pour déterminer la probabilité de Boons. \$B\$ se produit lorsque \N-X = (Y + B + 1)\N-X = (Y + B + 1)\N-X = (Y + B + 1) c'est-à-dire que le nombre de six dans la réserve Action est plus grand que le nombre de six dans la réserve Danger de \$B\$ . Cette probabilité peut être calculée de la même manière. Ainsi, dans cet exemple, la probabilité d'un boon est de 0,103599655450346, celle de deux boons est de 0,024868242039980, celle de trois boons est de 0,003491966042856, et celle des autres boons n'est pas pertinente. Il est encore difficile de trouver une expression analytique générale, mais pour un nombre donné de dés d'action et de dés de danger, il est facile de trouver l'expression analytique.

Validation du code

Je ne peux penser qu'à un cas trivial, nous pouvons donc vérifier si le code a un sens pour ce cas au moins. Considérons seulement 1 dé d'action et 1 dé de danger. Dans ce cas, la probabilité qu'ils soient égaux est de 1/6, et dans ce cas le résultat est 0 (c'est-à-dire qu'il ne reste aucun dé d'action). Sinon, nous avons une probabilité égale pour tous les dés, soit 5/36. enter image description here

Les valeurs résultant du code sont en fait approximativement 1/6 pour 0 et 5/36 pour le reste.

5voto

Leon Radley Points 1314

Pour le plaisir, j'ai décidé d'essayer de reproduire L'approche de @Carcer et une version mémorisée de L'approche de @Ilmari Karonen en utilisant dyce .

Vous pouvez le voir à l'œuvre (et vous amuser avec) dans Binder : Launch Binder [source Gist]

Le lancement d'une nouvelle instance peut prendre un certain temps si le lien n'a pas été suivi depuis longtemps. Binder supprimera une instance lancée après une période d'inactivité, donc téléchargez tout travail que vous souhaitez sauvegarder. Cet environnement est intéressant car il n'impose pas de limites de temps de calcul (comme le fait AnyDice). Ce qui est encore plus cool, c'est qu'avec la mémorisation, la version d'Irmari peut comparer les dés d'action [1-11] et les dés de danger [0-11] en moins d'une seconde. C'est génial !

L'exécution par défaut de ce carnet énumère les éléments suivants anydyce Le graphique en ligne et les graphiques en rafale pour une gamme de dés d'action [1-5] contre des dés de danger [0-5]. Les tracés deviennent assez chaotiques après cela (et commencent à prendre plus de temps à générer que le calcul des distributions elles-mêmes). Voici quelques captures d'écran.


¹ 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.


Neon City Overdrive line plots

Neon City Overdrive burst graphs

3voto

rasher Points 203

Je suis tombé sur cet article en cherchant dans Google quelque chose qui n'avait rien à voir, mais même si je ne suis pas un joueur de RPG, c'est un problème intéressant.

Il est facile de le calculer directement pour le domaine spécifié à l'aide de la distribution multinomiale.

En prenant les compositions faibles de taille (nombre de faces n) pour (nombre de dés d), et en laissant la séquence des catégories dans le multinomial représenter les faces {1, 2, 3..., n}, nous soustrayons toutes les paires de compositions faibles, avec les membres de la paire représentant les dés d'action et de danger respectivement. Nous notons le CMR de chaque membre de la paire et nous les multiplions pour obtenir une probabilité totale, en conservant le résultat ainsi que le résultat de la soustraction entre [0,d].

Il suffit ensuite de parcourir les paires {probabilité totale, résultat tronqué}, en choisissant la plus grande catégorie non nulle (et si cette catégorie est la valeur faciale maximale n, en notant que la valeur de la catégorie moins un est le nombre de boon).

Enfin, nous rassemblons ces résultats en fonction des mêmes maxima et maxima, et nous additionnons les probabilités qu'ils contiennent.

Moins de quelques secondes pour les boîtiers 5D6 et 6D6 par exemple, et pas de problème pour les boîtiers plus grands.

Ils se lisent comme suit : numéros de gauche en haut->valeur faciale maximale restante, numéros de gauche en bas->nombre d'avantages, suivis de gauche à droite avec une probabilité exacte et le même arrondi.

5D6 :

enter image description here

6D6 :

enter image description here

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