Comment éviter les séquences "trop" chanceuses / malchanceuses dans la génération de nombres aléatoires?


30

Je suis actuellement confronté à un système de combat multijoueur où les dégâts infligés par les joueurs sont toujours multipliés par un facteur aléatoire entre 0,8 et 1,2.

En théorie, un RNG vraiment aléatoire peut éventuellement donner plusieurs fois le même nombre (voir le dilemme Tetris ). Cela pourrait entraîner un match où le joueur fait toujours des dégâts très élevés tandis que l'autre fait toujours des dégâts très faibles.

Que puis-je faire pour m'assurer que cela ne se produise pas? Certains RNG sont-ils meilleurs que d'autres pour éviter la répétition?


Je ne vois pas comment cela fonctionne. Bien sûr, vous allez obtenir une séquence de x1, x2, x3, x4 .. où tous les x sont grands. N'est-ce pas juste aléatoire cependant?
The Communist Duck

Réponses:


26

Vous pouvez le résoudre de la même manière que Tetris, en créant une liste prédéfinie de résultats de dégâts et en mélangeant.

Disons que vous savez que le joueur va infliger des dégâts de 0,8 à 1,2 fois avec une distribution linéaire. Prenez la liste [0.8, 0.9, 1.0, 1.1, 1.2]. Mélangez-le au hasard , vous obtenez donc par exemple [1.2, 1.0, 0.8, 0.9, 1.1].

La première fois que le joueur inflige des dégâts, il inflige 1,2x. Puis 1x. Ensuite, etc, à 1.1x. Ce n'est que lorsque le tableau est vide que vous devez générer et mélanger un nouveau tableau.

En pratique, vous voudrez probablement faire cela sur 4+ tableaux à la fois (par exemple commencer par [0.8,0.8,0.8,0.8,0.9,0.9,0.9,0.9, ...]). Sinon, la période de la séquence est suffisamment basse pour que les joueurs puissent déterminer si leur prochain coup est "bon" ou non. (Bien que cela puisse également ajouter plus de stratégie au combat, comme dans le tableau Hoimi de Dragon Quest IX , que les gens ont compris comment sonder en regardant les nombres de soins et peaufiner jusqu'à ce que vous soyez assuré d'une goutte rare.)


3
Pour le rendre un peu plus aléatoire, vous pouvez toujours avoir la moitié de la liste sous forme de nombres aléatoires, et l'autre moitié calculée comme (2-x) pour obtenir la moyenne correcte.
Adam

2
@Adam: Cette méthode ne fonctionne vraiment que pour cet exemple particulier; si vous distribuez des pièces Tetris plutôt que des multiplicateurs de dégâts, quel est le bloc 2-S?

6
Le terme habituel pour cela est une sorte de système "aléatoire sans remplacement". C'est vraiment similaire à l'utilisation d'un jeu de cartes au lieu de dés, vraiment.
Kylotan

Encore mieux, vous pourriez faire la moitié des nombres vraiment au hasard, et seulement la moitié d'entre eux soumis à cette règle.
o0 '.

1
Il peut toujours en résulter que la distribution locale ne ressemble pas à la distribution globale, ce qui est exactement ce que la question ne veut pas. Des termes comme «vraiment aléatoire» sont de vagues pseudomathématiques; plus vous définissez les propriétés statistiques que vous voulez, plus votre intention et votre conception de jeu seront claires.

5

J'ai en fait écrit du code pour le faire . L'essentiel est d'utiliser des statistiques pour corriger les séquences malchanceuses. Pour ce faire, vous pouvez suivre le nombre de fois où l'événement s'est produit et l'utiliser pour biaiser le nombre généré par le PRNG.

Premièrement, comment pouvons-nous suivre le pourcentage d'événements? La manière naïve de le faire serait de conserver tous les nombres jamais générés en mémoire et de les faire la moyenne: ce qui fonctionnerait mais est horriblement inefficace. Après un peu de réflexion, j'ai trouvé ce qui suit (qui est essentiellement une moyenne mobile cumulative ).

Prenez les échantillons PRNG suivants (où nous procédons si l'échantillon est> = 0,5):

Values: 0.1, 0.5, 0.9, 0.4, 0.8
Events: 0  , 1  , 1  , 0  , 1
Percentage: 60%

Notez que chaque valeur contribue à 1/5 du résultat final. Regardons les choses autrement:

Values: 0.1, 0.5
Events: 0  , 1

Notez que le 0contribue à 50% de la valeur et le 1contribue à 50% de la valeur. Un peu plus loin:

Values: [0.1, 0.5], 0.9
Events: [0  , 1  ], 1

Maintenant, les premières valeurs contribuent à 66% de la valeur et les 33 derniers%. Nous pouvons essentiellement résumer cela jusqu'au processus suivant:

result = // 0 or 1 depending on the result of the event that was just generated
new_samples = samples + 1

average = (average * samples / new_samples) + (result * 1 / new_samples)
// Essentially:
average = (average * samples / new_samples) + (result / new_samples)

// You might want to limit this to, say, 100.
// Leaving it to carry on increasing can lead to unfairness
// if the game draws on forever.
samples = new_samples

Maintenant, nous devons biaiser le résultat de la valeur échantillonnée à partir du PRNG, parce que nous allons chercher un pourcentage de chance ici, les choses sont beaucoup plus faciles (contre, disons, des quantités aléatoires de dommages dans un RTS). Cela va être difficile à expliquer parce que cela vient de «me venir à l'esprit». Si la moyenne est inférieure, cela signifie que nous devons augmenter les chances que l'événement se produise et vice-versa. Donc, quelques exemples

average = 0.1
desired = 0.5
corrected_chance = 83%

average = 0.2
desired = 0.5
corrected_chance = 71%

average = 0.5
desired = 0.5
corrected_change = 50%

Maintenant, ce qui m'est venu à l'esprit, c'est que dans le premier exemple, 83% n'était que "0,5 sur 0,6" (en d'autres termes "0,5 sur 0,5 plus 0,1"). En termes d'événements aléatoires, cela signifie soit:

procced = (sample * 0.6) > 0.1
// or
procced = (sample * 0.6) <= 0.5

Donc, pour générer un événement, vous utiliseriez essentiellement le code suivant:

total = average + desired
sample = rng_sample() * total // where the RNG provides a value between 0 and 1
procced = sample <= desired

Et donc vous obtenez le code que j'ai mis dans l'essentiel. Je suis presque sûr que tout cela peut être utilisé dans le scénario de dommages aléatoires, mais je n'ai pas pris le temps de le comprendre.

Avertissement: Ce sont toutes des statistiques locales, je n'ai aucune formation dans le domaine. Mes tests unitaires réussissent cependant.


Ressemble à une erreur dans votre premier exemple, car une valeur de 0,1 et de 0,9 entraîne un événement 0. Mais vous décrivez essentiellement le maintien d'une moyenne mobile cumulative ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) et la correction basée sur cela. Un risque est que chaque résultat soit significativement inversement corrélé avec le résultat précédent, bien que cette corrélation diminue avec le temps.
Kylotan

1
Je serais tenté de modifier cela pour utiliser un système `` intégrateur qui fuit '' à la place: commencez par la moyenne initialisée à 0,5 et au lieu de compter les échantillons, choisissez une valeur constante arbitraire (par exemple 10, 20, 50 ou 100) qui n'est pas incrémentée . Ensuite, au moins la corrélation entre 2 valeurs suivantes est constante tout au long de l'utilisation du générateur. Vous pouvez également modifier la valeur constante - des valeurs plus grandes signifient une correction plus lente et un caractère aléatoire plus apparent.
Kylotan

@Kylotan merci, merci d'avoir fourni le nom. Je ne sais pas exactement ce que vous voulez dire avec votre deuxième commentaire - peut-être fournir une nouvelle réponse?
Jonathan Dickinson

C'est assez intelligent et n'a pas les limites des tableaux. Je comprends la suggestion de Kylotan, qui est d'initialiser samplesà sa valeur maximale (dans ce cas, 100) dès le départ. De cette façon, il ne faut pas 99 itérations pour que le RNG se stabilise. Quoi qu'il en soit, le seul inconvénient que je peux voir avec cette méthode est qu'elle ne garantit pas l' équité, elle garantit simplement une moyenne constante.
Utilisateur non trouvé

@jSepia - en effet, vous obtiendriez toujours des séquences d'équité / d'injustice, mais elles seraient suivies (généralement) d'une séquence équilibrée. Par exemple, dans mon test unitaire, j'ai «forcé» 100 non-procs et j'ai rencontré ~ 60 procs lorsque j'ai fait les vrais échantillons. Dans des situations sans influence (si vous regardez le code), un proc à 50% voit généralement, au pire, des exécutions de 2/3 dans les deux sens. Mais un joueur pourrait avoir une course lui permettant de vaincre l'autre joueur. Si vous voulez polariser plus fortement à la juste: total = (average / 2) + desired.
Jonathan Dickinson

3

Ce que vous demandez est en fait l'opposé de la plupart des PRNG, une distribution non linéaire. Mettez simplement une sorte de logique de rendements décroissants dans vos règles, en supposant que tout ce qui dépasse 1,0x est un "coup critique" quelconque, dites simplement qu'à chaque tour, vos chances d'obtenir un critique augmentent de X, jusqu'à ce que vous en obteniez un à quel point ils sont réinitialisés à Y. Vous effectuez ensuite deux lancers à chaque tour, un pour déterminer le niveau critique ou non, puis un autre pour la grandeur réelle.


1
C'est l'approche générale que j'adopterais, vous utilisez la distribution uniforme du RNG mais la transformez. Vous pouvez également utiliser la sortie du RNG comme entrée de votre propre distribution personnalisée qui se réajuste en fonction de l'historique récent, c'est-à-dire pour forcer la variance dans les sorties, afin qu'elle ait l'air "plus aléatoire" en termes de perception humaine.
Michael

3
En fait, je connais un MMO qui fait quelque chose comme ça, mais la chance d'un critique augmente en fait chaque fois que vous en obtenez un jusqu'à ce que vous n'en obteniez pas , puis il revient à une valeur très faible. Cela conduit à de rares séquences de crits qui sont très satisfaisantes pour le joueur.
coderanger

Cela ressemble à un bon alg, les longues périodes de sécheresse ont toujours été frustrantes, mais cela ne conduit pas à des séquences de crits fous.
Michael

2
La correction de cela ne nécessite pas de distribution non linéaire, il suffit simplement que de courts sous-ensembles séquentiels dans le temps de la distribution aient les mêmes propriétés que la distribution elle-même.

c'est ainsi que les jeux Blizzard le font, au moins depuis Warcraft 3
dreta

2

Sid Meier a fait un excellent discours sur GDC 2010 sur ce sujet et les jeux de civilisation. J'essaierai de trouver et de coller le lien plus tard. Essentiellement - le caractère aléatoire perçu n'est pas le même que le vrai caractère aléatoire. Pour que les choses se sentent bien, vous devez analyser les résultats précédents et faire attention à la psychologie des joueurs.

Évitez les séquences de malchance à tout prix (si les deux tours précédents n'ont pas été chanceux, le prochain devrait être garanti d'avoir de la chance). Le joueur doit toujours être plus chanceux que l'adversaire IA.


0

Utilisez un biais de décalage

01rb0

La distribution globale sera biaisée par la formule suivante:

rexp(b)

b1b0

Prenez ce nombre et adaptez-le de manière appropriée à la plage souhaitée.

Chaque fois qu'un joueur roule favorablement, soustrayez du biais. Chaque fois que le joueur roule défavorablement, ajoutez au biais. Le montant modifié peut être ajusté en fonction du degré (défavorable) favorable du rouleau ou peut être un montant fixe (ou une combinaison). Vous devrez ajuster des valeurs spécifiques pour s'adapter à la sensation que vous recherchez.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.