J'essaie de créer un jeu avec des dés et je dois y insérer des nombres aléatoires (pour simuler les côtés du dé. Je sais comment le faire entre 1 et 6). En utilisant
#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;
int main()
{
srand((unsigned)time(0));
int i;
i = (Rand()%6)+1;
cout << i << "\n";
}
ne fonctionne pas très bien, car lorsque je lance le programme plusieurs fois, voici le résultat obtenu:
6
1
1
1
1
1
2
2
2
2
5
2
Donc, je veux une commande qui générera un nombre aléatoire différent à chaque fois, pas le même 5 fois de suite. Y a-t-il une commande qui fera cela?
Le problème le plus fondamental de votre application de test est que vous appelez srand
une fois, puis appelez Rand
une fois et quittez.
Le point entier de la fonction srand
consiste à initialiser la séquence de nombres pseudo-aléatoires avec un germe aléatoire. Cela signifie que si vous passez la même valeur à srand
dans deux applications différentes (avec la même implémentation srand
/Rand
, vous obtiendrez exactement la même séquence de Rand()
valeurs lire après cela. Mais votre séquence pseudo-aléatoire ne contient qu'un élément. Votre sortie consiste en les premiers éléments de différentes séquences pseudo-aléatoires ensemencées avec une précision temporelle de 1 seconde. Alors qu'attendez-vous de voir? Lorsque vous exécutez une application à la même seconde, votre résultat est bien entendu le même (comme Martin York l'a déjà mentionné dans un commentaire à la réponse).
En fait, vous devriez appeler srand(seed)
une fois, puis appeler Rand()
plusieurs fois et analyser cette séquence - elle devrait sembler aléatoire.
L'utilisation de modulo peut introduire un biais dans les nombres aléatoires, en fonction du générateur de nombres aléatoires. Voir cette question pour plus d'informations. Bien sûr, il est parfaitement possible d'obtenir des nombres répétés dans un ordre aléatoire.
Essayez certaines fonctionnalités de C++ 11 pour une meilleure distribution:
#include <random>
#include <iostream>
int main()
{
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> dist6(1,6); // distribution in range [1, 6]
std::cout << dist6(rng) << std::endl;
}
Voir cette question/réponse pour plus d'informations sur les nombres aléatoires C++ 11. Ce qui précède n'est pas le seul moyen de faire cela, mais est un moyen.
Si vous utilisez boost libs, vous pouvez obtenir un générateur aléatoire de cette façon:
#include <iostream>
#include <string>
// Used in randomization
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
using namespace std;
using namespace boost;
int current_time_nanoseconds(){
struct timespec tm;
clock_gettime(CLOCK_REALTIME, &tm);
return tm.tv_nsec;
}
int main (int argc, char* argv[]) {
unsigned int dice_rolls = 12;
random::mt19937 rng(current_time_nanoseconds());
random::uniform_int_distribution<> six(1,6);
for(unsigned int i=0; i<dice_rolls; i++){
cout << six(rng) << endl;
}
}
Où la fonction current_time_nanoseconds()
donne l'heure actuelle en nanosecondes qui est utilisée comme une graine.
Voici une classe plus générale pour obtenir des nombres entiers et des dates aléatoires dans une plage:
#include <iostream>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "boost/date_time/posix_time/posix_time.hpp"
#include "boost/date_time/gregorian/gregorian.hpp"
using namespace std;
using namespace boost;
using namespace boost::posix_time;
using namespace boost::gregorian;
class Randomizer {
private:
static const bool debug_mode = false;
random::mt19937 rng_;
// The private constructor so that the user can not directly instantiate
Randomizer() {
if(debug_mode==true){
this->rng_ = random::mt19937();
}else{
this->rng_ = random::mt19937(current_time_nanoseconds());
}
};
int current_time_nanoseconds(){
struct timespec tm;
clock_gettime(CLOCK_REALTIME, &tm);
return tm.tv_nsec;
}
// C++ 03
// ========
// Dont forget to declare these two. You want to make sure they
// are unacceptable otherwise you may accidentally get copies of
// your singleton appearing.
Randomizer(Randomizer const&); // Don't Implement
void operator=(Randomizer const&); // Don't implement
public:
static Randomizer& get_instance(){
// The only instance of the class is created at the first call get_instance ()
// and will be destroyed only when the program exits
static Randomizer instance;
return instance;
}
bool method() { return true; };
int Rand(unsigned int floor, unsigned int ceil){
random::uniform_int_distribution<> Rand_ = random::uniform_int_distribution<> (floor,ceil);
return (Rand_(rng_));
}
// Is not considering the millisecons
time_duration Rand_time_duration(){
boost::posix_time::time_duration floor(0, 0, 0, 0);
boost::posix_time::time_duration ceil(23, 59, 59, 0);
unsigned int Rand_seconds = Rand(floor.total_seconds(), ceil.total_seconds());
return seconds(Rand_seconds);
}
date Rand_date_from_Epoch_to_now(){
date now = second_clock::local_time().date();
return Rand_date_from_Epoch_to_ceil(now);
}
date Rand_date_from_Epoch_to_ceil(date ceil_date){
date Epoch = ptime(date(1970,1,1)).date();
return Rand_date_in_interval(Epoch, ceil_date);
}
date Rand_date_in_interval(date floor_date, date ceil_date){
return Rand_ptime_in_interval(ptime(floor_date), ptime(ceil_date)).date();
}
ptime Rand_ptime_from_Epoch_to_now(){
ptime now = second_clock::local_time();
return Rand_ptime_from_Epoch_to_ceil(now);
}
ptime Rand_ptime_from_Epoch_to_ceil(ptime ceil_date){
ptime Epoch = ptime(date(1970,1,1));
return Rand_ptime_in_interval(Epoch, ceil_date);
}
ptime Rand_ptime_in_interval(ptime floor_date, ptime ceil_date){
time_duration const diff = ceil_date - floor_date;
long long gap_seconds = diff.total_seconds();
long long step_seconds = Randomizer::get_instance().Rand(0, gap_seconds);
return floor_date + seconds(step_seconds);
}
};
#include <iostream>
#include <cstdlib>
#include <ctime>
int main() {
srand(time(NULL));
int random_number = std::Rand(); // Rand() return a number between 0 and Rand_MAX
std::cout << random_number;
return 0;
}
Peut être plein Randomer
code de classe pour générer des nombres aléatoires à partir d'ici!
Si vous avez besoin de nombres aléatoires dans différentes parties du projet, vous pouvez créer une classe séparée Randomer
pour y inclure tous les éléments random
.
Quelque chose comme ca:
class Randomer {
// random seed by default
std::mt19937 gen_;
std::uniform_int_distribution<size_t> dist_;
public:
/* ... some convenient ctors ... */
Randomer(size_t min, size_t max, unsigned int seed = std::random_device{}())
: gen_{seed}, dist_{min, max} {
}
// if you want predictable numbers
void SetSeed(unsigned int seed) {
gen_.seed(seed);
}
size_t operator()() {
return dist_(gen_);
}
};
Une telle classe serait utile plus tard:
int main() {
Randomer randomer{0, 10};
std::cout << randomer() << "\n";
}
Vous pouvez vérifier ce lien à titre d'exemple, comment je l'utilise telle Randomer
classe pour générer des chaînes aléatoires. Vous pouvez également utiliser Randomer
si vous le souhaitez.
Génère un nombre aléatoire différent à chaque fois, et non le même nombre six fois de suite.
scénario d'utilisation
J'ai comparé le problème de Predictability à un sac de six morceaux de papier portant chacun une valeur comprise entre 0 et 5. Un morceau de papier est tiré du sac chaque fois qu'une nouvelle valeur est requise. Si le sac est vide, les numéros sont remis dans le sac.
... à partir de cela, je peux créer un algorithme de toutes sortes.
algorithme
Un sac est généralement un Collection
. J'ai choisi un bool[]
(autrement appelé un tableau booléen, un plan binaire ou une mappe binaire) pour prendre le rôle du sac.
La raison pour laquelle j'ai choisi un bool[]
est que l'index de chaque élément correspond déjà à la valeur de chaque morceau de papier. Si les documents demandaient quoi que ce soit d'autre, alors j'aurais utilisé un Dictionary<string, bool>
à la place. La valeur booléenne permet de savoir si le nombre a déjà été tiré ou non.
Un compteur appelé RemainingNumberCount
est initialisé à 5
et compte à rebours lorsqu'un nombre aléatoire est choisi. Cela nous évite de compter combien de feuilles de papier il nous reste chaque fois que nous souhaitons dessiner un nouveau nombre.
Pour sélectionner la valeur aléatoire suivante, j'utilise un for..loop
pour parcourir le panier d'index et un compteur pour le décompter lorsqu'un index
est false
appelé NumberOfMoves
.
NumberOfMoves
permet de choisir le prochain numéro disponible. NumberOfMoves
est d'abord défini comme une valeur aléatoire comprise entre 0
et 5
, car il existe 0..5 étapes disponibles que nous pouvons effectuer dans le sac. Lors de la prochaine itération, NumberOfMoves
est défini comme une valeur aléatoire comprise entre 0
et 4
, car nous pouvons maintenant effectuer des étapes de 0 à 4 dans le sac. Au fur et à mesure que les nombres sont utilisés, les nombres disponibles diminuent, nous utilisons donc Rand() % (RemainingNumberCount + 1)
pour calculer la valeur suivante pour NumberOfMoves
.
Lorsque le compteur NumberOfMoves
atteint zéro, le for..loop
devrait:
for..loop
.false
.for..loop
.Code
Le code de la solution ci-dessus est le suivant:
(placez les trois blocs suivants dans le fichier principal .cpp l'un après l'autre)
#include "stdafx.h"
#include <ctime>
#include <iostream>
#include <string>
class RandomBag {
public:
int Value = -1;
RandomBag() {
ResetBag();
}
void NextValue() {
int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);
int NumberOfMoves = Rand() % (RemainingNumberCount + 1);
for (int i = 0; i < BagOfNumbersLength; i++)
if (BagOfNumbers[i] == 0) {
NumberOfMoves--;
if (NumberOfMoves == -1)
{
Value = i;
BagOfNumbers[i] = 1;
break;
}
}
if (RemainingNumberCount == 0) {
RemainingNumberCount = 5;
ResetBag();
}
else
RemainingNumberCount--;
}
std::string ToString() {
return std::to_string(Value);
}
private:
bool BagOfNumbers[6];
int RemainingNumberCount;
int NumberOfMoves;
void ResetBag() {
RemainingNumberCount = 5;
NumberOfMoves = Rand() % 6;
int BagOfNumbersLength = sizeof(BagOfNumbers) / sizeof(*BagOfNumbers);
for (int i = 0; i < BagOfNumbersLength; i++)
BagOfNumbers[i] = 0;
}
};
ne classe de console
Je crée cette classe de console car elle facilite la redirection des sorties.
Ci-dessous dans le code ...
Console::WriteLine("The next value is " + randomBag.ToString());
... peut être remplacé par ...
std::cout << "The next value is " + randomBag.ToString() << std::endl;
... puis cette classe Console
peut être supprimée si vous le souhaitez.
class Console {
public:
static void WriteLine(std::string s) {
std::cout << s << std::endl;
}
};
Méthode principale
Exemple d'utilisation comme suit:
int main() {
srand((unsigned)time(0)); // Initialise random seed based on current time
RandomBag randomBag;
Console::WriteLine("First set of six...\n");
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
Console::WriteLine("\nSecond set of six...\n");
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
Console::WriteLine("\nThird set of six...\n");
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
randomBag.NextValue();
Console::WriteLine("The next value is " + randomBag.ToString());
Console::WriteLine("\nProcess complete.\n");
system("pause");
}
Exemple de sortie
Lorsque j'ai exécuté le programme, j'ai obtenu le résultat suivant:
First set of six...
The next value is 2
The next value is 3
The next value is 4
The next value is 5
The next value is 0
The next value is 1
Second set of six...
The next value is 3
The next value is 4
The next value is 2
The next value is 0
The next value is 1
The next value is 5
Third set of six...
The next value is 4
The next value is 5
The next value is 2
The next value is 0
The next value is 3
The next value is 1
Process complete.
Press any key to continue . . .
déclaration finale
Ce programme a été écrit en utilisant Visual Studio 2017, et j'ai choisi d'en faire un projet Visual C++ Windows Console Application
en utilisant .Net 4.6.1
.
Je ne fais rien de particulièrement spécial ici, donc le code devrait également fonctionner sur les versions antérieures de Visual Studio.
Voici une solution. Créez une fonction qui retourne le nombre aléatoire et placez-le en dehors de la fonction principale pour le rendre global. J'espère que cela vous aidera
#include <iostream>
#include <cstdlib>
#include <ctime>
int rollDie();
using std::cout;
int main (){
srand((unsigned)time(0));
int die1;
int die2;
for (int n=10; n>0; n--){
die1 = rollDie();
die2 = rollDie();
cout << die1 << " + " << die2 << " = " << die1 + die2 << "\n";
}
system("pause");
return 0;
}
int rollDie(){
return (Rand()%6)+1;
}
pour aléatoire chaque fichier RUN
size_t randomGenerator(size_t min, size_t max) {
std::mt19937 rng;
rng.seed(std::random_device()());
//rng.seed(std::chrono::high_resolution_clock::now().time_since_Epoch().count());
std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);
return dist(rng);
}