Qu'est-ce qu'un bon générateur de nombres aléatoires à utiliser pour un jeu en C++?
Mes considérations sont les suivantes:
Rand()
dans pas mal d'endroits, donc tout autre générateur ferait mieux de justifier tous les changements qu'il nécessiterait.Je ne connais pas grand-chose à ce sujet, donc la seule alternative que j'ai pu trouver est la Mersenne Twister ; répond-il à toutes ces exigences? Y a-t-il autre chose de mieux?
Edit: Mersenne Twister semble être le choix consensuel. Mais qu'en est-il du point # 4? Est-ce vraiment mieux que Rand()
?
Edit 2: Permettez-moi d'être un peu plus clair sur le point 2: Il n'y a aucun moyen pour les joueurs de tricher en connaissant les nombres aléatoires. Période. Je le veux assez aléatoire pour que les gens (du moins ceux qui comprennent le hasard) ne puissent pas s'en plaindre, mais je ne m'inquiète pas des prédictions. C'est pourquoi je mets la vitesse au premier plan.
Edit 3: Je me penche maintenant vers les RAG Marsaglia, mais j'aimerais quand même plus d'informations. Par conséquent, je mets en place une prime.
Édition 4: Juste une note: j'ai l'intention d'accepter une réponse juste avant minuit UTC aujourd'hui (pour éviter de jouer avec la limite de représentant de quelqu'un). Alors si vous songez à répondre, n'attendez pas la dernière minute!
J'aime aussi l'apparence des générateurs XORshift de Marsaglia. Quelqu'un a-t-il des commentaires à leur sujet?
George Marsaglia a développé certains des RNG les meilleurs et les plus rapides actuellement disponibles Multiply-with-carry étant notable pour une distribution uniforme.
=== Mise à jour 2018-09-12 ===
Pour mon propre travail, j'utilise maintenant Xoshiro256 ** , qui est une sorte d'évolution/mise à jour du XorShift de Marsaglia.
Parfois les développeurs de jeux ne veulent pas de vrais aléas et un shuffle bag est plus approprié.
Si vous voulez du hasard, le twister Mersenne répond à vos exigences. Il est rapide, statistiquement aléatoire, a une longue période et il existe de nombreuses implémentations.
Edit: Rand()
est typiquement implémenté comme générateur congruentiel linéaire . Il est probablement préférable que vous fassiez un choix éclairé pour savoir si c'est assez bon pour vos besoins.
Il y a de bien meilleurs choix que Mersenne Twister de nos jours. Voici un RNG appelé WELL512, conçu par les concepteurs de Mersenne, développé 10 ans plus tard, et un meilleur choix pour les jeux. Le code est mis dans le domaine public par le Dr Chris Lomont. Il affirme que cette implémentation est 40% plus rapide que Mersenne, ne souffre pas d'une mauvaise diffusion et d'un piège lorsque l'état contient plusieurs 0 bits, et est clairement un code beaucoup plus simple. Il a une période de 2 ^ 512; un PC prend plus de 10 ^ 100 ans pour parcourir les états, il est donc assez grand.
Voici un article présentant les PRNG où j'ai trouvé l'implémentation WELL512. http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
Donc - plus rapide, plus simple, créé par les mêmes designers 10 ans plus tard, et produit de meilleurs chiffres que Mersenne. Comment pouvez-vous vous tromper? :)
MISE À JOUR (11-18-14) : Correction d'une erreur (changé 0xDA442D20UL en 0xDA442D24UL, comme décrit dans le document lié ci-dessus).
/* initialize state to random bits */
static unsigned long state[16];
/* init should also reset this to 0 */
static unsigned int index = 0;
/* return 32 bit random number */
unsigned long WELLRNG512(void)
{
unsigned long a, b, c, d;
a = state[index];
c = state[(index+13)&15];
b = a^c^(a<<16)^(c<<15);
c = state[(index+9)&15];
c ^= (c>>11);
a = state[index] = b^c;
d = a^((a<<5)&0xDA442D24UL);
index = (index + 15)&15;
a = state[index];
state[index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
return state[index];
}
Mersenne Twister est typique de l'industrie, d'autant plus qu'il se prête bien au SIMD et peut être rendu ultra rapide. Knuth est également populaire (merci, David).
Dans la plupart des applications de jeu, la vitesse est vraiment le facteur critique, car les joueurs vont se plaindre du faible débit d'images beaucoup plus qu'ils ne se plaindront du fait qu'il existe un léger biais en faveur de la génération d'un 3 chaque fois qu'il est précédé d'un 7, 2, et 9 dans cet ordre.
L'exception est bien sûr le jeu d'argent, mais là, votre autorité compétente en matière de licences définira spécifiquement les algorithmes que vous pouvez utiliser.
Achetez une webcamera bon marché, un détecteur de fumée ionisante. Démontez-les tous les deux, le détecteur de fumée contient peu de matières radioactives - une source d'ondes gamma - qui entraîneront des photons sur votre webcamera. C'est votre source de vrai hasard :)
Mersenne Twister est très bon, et c'est rapide aussi. Je l'ai utilisé dans un jeu et ce n'est pas difficile à mettre en œuvre ou à utiliser.
Le algorithme aléatoire WELL a été conçu comme une amélioration par rapport au Twister de Mersenne. Game Gems 7 a plus d'informations. dessus, si vous pouvez l'emprunter ou l'avoir.
Sur cette page BIEN à laquelle je vous ai lié, le nombre est la période de l'algorithme. Autrement dit, vous pouvez obtenir 2 ^ N - 1 nombres avant qu'il ait besoin d'être réensemencé, où N est: 512, 1024, 19937 ou 44497. Mersenne Twister a une période de N = 19937 ou 2 ^ 19937-1. voir c'est un très grand nombre :)
La seule autre chose que je peux souligner est que boost a une bibliothèque aléatoire , que vous devriez trouver utile.
En réponse à votre montage, oui, le Twister ou WELL est bien meilleur que Rand (). De plus, l'ancien truc du module nuit à la distribution des nombres. Encore plus de raisons d'utiliser boost :)
Je suis un fan de Isaac , contrairement au twister mersense, il est cryptographiquement sécurisé (vous * ne pouvez pas casser la période en observant les rouleaux)
IBAA (rc4?) est également tilisé par Blizzard pour empêcher les gens de prédire le nombre aléatoire utilisé pour les rouleaux de butin. J'imagine que quelque chose de similaire est fait avec diablo II lorsque vous jouez sur un serveur battle.net.
* ne peut pas dans un délai raisonnable (des siècles?)
Dans un jeu en temps réel, il n'y a aucun moyen pour un joueur de déterminer la différence entre un "bon" générateur et un "mauvais" générateur. Dans un jeu au tour par tour, vous avez raison - une minorité de fanatiques se plaindra. Ils vous raconteront même des histoires, avec des détails atroces, sur la façon dont vous avez ruiné leur vie avec un mauvais générateur de nombres aléatoires.
Si vous avez besoin d'un tas de nombres aléatoires authentiques (et que vous êtes un jeu en ligne), vous pouvez en obtenir sur Random.org . Utilisez-les pour les jeux au tour par tour ou comme graines pour les jeux en temps réel.
Basé sur le générateur de nombres aléatoires par Ian C. Bullard:
// utils.hpp
namespace utils {
void srand(unsigned int seed);
void srand();
unsigned int Rand();
}
// utils.cpp
#include "utils.hpp"
#include <time.h>
namespace {
static unsigned int s_Rand_high = 1;
static unsigned int s_Rand_low = 1 ^ 0x49616E42;
}
void utils::srand(unsigned int seed)
{
s_Rand_high = seed;
s_Rand_low = seed ^ 0x49616E42;
}
void utils::srand()
{
utils::srand(static_cast<unsigned int>(time(0)));
}
unsigned int utils::Rand()
{
static const int shift = sizeof(int) / 2;
s_Rand_high = (s_Rand_high >> shift) + (s_Rand_high << shift);
s_Rand_high += s_Rand_low;
s_Rand_low += s_Rand_high;
return s_Rand_high;
}
Pourquoi?
Rand()
Un critère supplémentaire à considérer est la sécurité des threads. (Et vous devriez utiliser des threads dans les environnements multicœurs d'aujourd'hui.) Le simple fait d'appeler Rand à partir de plusieurs threads peut perturber son comportement déterministe (si votre jeu en dépend). À tout le moins, je vous recommande de passer à Rand_r.
GameRand implémente l'algorithme affiché ici http://www.flipcode.com/archives/07-15-2002.shtml
C'est quelque chose que j'ai développé à l'origine à la fin des années 80. Il bat facilement Rand () en termes de qualité numérique, et comme avantage secondaire d'être l'algorithme aléatoire le plus rapide possible.
Je voterais aussi pour le Mersenne Twister. Les implémentations sont largement disponibles, elles ont une très grande période de 2 ^ 19937 -1, sont relativement rapides et réussissent la plupart des tests de hasard, y compris les tests Diehard développés par Marsaglia. Rand () et Co., étant des LCG, produisent des écarts de qualité inférieure et leurs valeurs successives peuvent être facilement déduites.
Un point à noter, cependant, est de graver correctement MT dans un état qui passe les tests de hasard. Habituellement, un LCG comme drand48 () est utilisé à cette fin.
Je dirais que le MT satisfait à toutes les exigences que vous avez définies (prouvablement), et ce serait exagéré d'aller pour quelque chose comme MWCG imo.
Selon le système d'exploitation cible, vous pourrez peut-être utiliser/dev/random. Cela ne nécessite pas vraiment d'implémentation, et sous Linux (et peut-être certains autres systèmes d'exploitation), c'est vraiment aléatoire. Les blocs de lecture jusqu'à ce que l'entropie suffisante soit disponible, vous pouvez donc vouloir lire le fichier et le stocker dans un tampon ou quelque chose en utilisant un autre thread. Si vous ne pouvez pas utiliser un appel de lecture bloquant, vous pouvez utiliser/dev/urandom. Il génère des données aléatoires presque aussi bien que/dev/random, mais il réutilise certaines données aléatoires pour donner une sortie instantanément. Ce n'est pas aussi sûr, mais cela pourrait bien fonctionner selon ce que vous prévoyez d'en faire.
Vous savez quoi? Pardonnez-moi si vous pensez que cette réponse est complètement nul ... Mais j'ai été (pour dieu ne sait quelle raison ...) en utilisant DateTime.Now.Milliseconds
comme moyen d'obtenir un nombre aléatoire. Je sais que ce n'est pas complètement aléatoire, mais il semble que ce soit ...
Je ne pouvais pas être gêné de taper autant JUSTE pour obtenir un nombre aléatoire! : P
Je le veux assez aléatoire pour que les gens (du moins ceux qui comprennent le hasard) ne puissent pas s'en plaindre, mais je ne m'inquiète pas des prédictions.
A-ha!
Voilà votre vraie exigence!
Personne ne pourrait vous reprocher d'utiliser Mersenne Twister dans cette application.
Apparemment (j'oublie où je l'ai lu, tout comme j'oublie où je lis que le curry est bon pour empêcher les altzheimas), en prenant la valeur absolue de la somme de contrôle d'un nouveau GUID est bien aléatoire). C'est un grand nombre, et vous pouvez en utiliser un module pour le réduire.
Donc, dans SQL (ma région), c'est ABS (CHECKSUM (NEWID ()))% 1000
Rob