@Manga, I wrote the C++ code specially as a direct change over from the C version. Easier to follow the steps.
Simply moving code that was written to illustrate a point into a function is painting the lily.
Someone learning C or C++ for the first time will find using
srand() and
rand() a bit intimidating also. With use they become easier to remember. Same with the C++ random engines/distributions, etc. More time with C++, still not an impossible task.
If you want something easier to use, a custom and reusable C++ random number toolkit header. Adapted from a C++ working paper:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
/* A simple toolkit to help beginners using <random> library have an easier task */
// shamelessly stolen and adapted from a C++ working paper: WG21 N3551
// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf
#ifndef __RANDOM_TOOLKIT_HPP__
#define __RANDOM_TOOLKIT_HPP__
#include <chrono>
#include <random>
#include <stdexcept>
namespace rtk
{
static bool seeded = false;
inline std::default_random_engine& urng()
{
static std::default_random_engine URNG { };
return URNG;
}
inline void srand(bool FORCE_SEED = false)
{
static const std::seed_seq::result_type seeds[] { std::random_device {}(),
std::seed_seq::result_type(std::chrono::system_clock::now().time_since_epoch().count()) };
static std::seed_seq sseq(std::begin(seeds), std::end(seeds));
// static unsigned seed = static_cast<unsigned> (std::chrono::system_clock::now().time_since_epoch().count());
// the URNG can't be reseeded unless forced
if (!seeded || FORCE_SEED)
{
urng().seed(sseq);
seeded = true;
}
}
inline void srand(unsigned seed, bool FORCE_SEED = false)
{
// the URNG can't be reseeded unless forced
if (!seeded || FORCE_SEED)
{
urng().seed(seed);
seeded = true;
}
}
// two function overloads to obtain uniform distribution ints and doubles
inline int rand(int from, int to)
{
static std::uniform_int_distribution<> dist { };
if (from > to) { throw std::invalid_argument("bad int params"); }
return dist(urng(), decltype(dist)::param_type { from, to });
}
inline double rand(double from, double to)
{
static std::uniform_real_distribution<> dist { };
if (from > to) { throw std::invalid_argument("bad double params"); }
return dist(urng(), decltype(dist)::param_type { from, to });
}
// function for rolling dice, and checking if the # of pips is nonstandard
inline int roll_die(int pips)
{
//check to see if the number of die pips is less than 2
if (pips < 2)
{
return 0;
}
return rand(1, pips);
}
}
#endif
|
And some sample usages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
#include <array>
#include <iostream>
#include <numeric>
#include "random_toolkit.hpp"
int main()
{
// "create" a random engine and randomize it
rtk::srand();
using card = unsigned short;
// manufacture a deck of cards:
std::array<card, 52> deck;
// create the cards, 0 (zero) to 51
std::iota(deck.begin(), deck.end(), 0);
// lambdas to display the card in text representation:
auto rank = [] (card c) { return "AKQJT98765432"[c % 13]; };
auto suit = [] (card c) { return "SHDC"[c / 13]; };
card count { };
for (card c : deck)
{
std::cout << rank(c) << suit(c) << ' ';
count++;
if (0 == (count % 13)) { std::cout << '\n'; }
}
std::cout << '\n';
// shuffle the deck:
std::shuffle(deck.begin(), deck.end(), rtk::urng());
count = 0;
for (card c : deck)
{
std::cout << rank(c) << suit(c) << ' ';
count++;
if (0 == (count % 13)) { std::cout << '\n'; }
}
std::cout << '\n';
for (unsigned loop { }; loop < 50; loop++)
{
std::cout << rtk::roll_die(6) << ' ';
if ((loop + 1) % 20 == 0) { std::cout << '\n'; }
}
std::cout << "\n\n";
// lambda to "flip a coin," returning a text representation of coin side
auto flip_coin = [] () { return (rtk::rand(0, 1) ? "Heads" : "Tails"); };
for (size_t loop { }; loop < 25; loop++)
{
std::cout << flip_coin() << '\t';
if ((loop + 1) % 8 == 0) { std::cout << '\n'; }
}
std::cout << '\n';
std::cout << "\nLet's see if we can have a non-standard die.....\nA die with 1 side: ";
std::cout << rtk::roll_die(1) << '\n';
std::cout << "A die with zero sides: ";
std::cout << rtk::roll_die(0) << '\n';
std::cout << "A die with negative sides: ";
std::cout << rtk::roll_die(-6) << "\n\n";
// let's try to create a bad distribution
std::cout << "Creating a bad random distribution, it should be be\n";
std::cout << "encapsulated within a try/catch block. Unless you want to crash.\n";
try
{
auto test { rtk::rand(5, 2) };
}
catch (const std::exception& e)
{
std::cout << "\n>>> A standard exception was caught, with message '" << e.what() << "'\n";
}
}
|
AS KS QS JS TS 9S 8S 7S 6S 5S 4S 3S 2S
AH KH QH JH TH 9H 8H 7H 6H 5H 4H 3H 2H
AD KD QD JD TD 9D 8D 7D 6D 5D 4D 3D 2D
AC KC QC JC TC 9C 8C 7C 6C 5C 4C 3C 2C
TH 7S 2H 6D 9S JS 7C AS 2S KC 8S 6C 7D
AC 9H AD 6S 2C 8D 7H 3S 4D TS 3H 5D QD
QS 6H 5S JC JH QC 9D 4H KH TC TD AH 4C
3D 8C JD 8H KS 9C 5C 5H QH 3C 4S KD 2D
1 5 5 4 2 4 2 6 5 4 2 2 6 4 1 6 5 2 6 5
2 2 6 6 6 6 3 5 5 1 6 3 5 6 6 3 4 3 3 6
6 1 6 6 4 6 3 3 5 3
Heads Heads Tails Tails Heads Heads Heads Heads
Tails Tails Heads Tails Heads Tails Heads Heads
Heads Heads Heads Heads Heads Tails Tails Heads
Tails
Let's see if we can have a non-standard die.....
A die with 1 side: 0
A die with zero sides: 0
A die with negative sides: 0
Creating a bad random distribution, it should be be
encapsulated within a try/catch block. Unless you want to crash.
>>> A standard exception was caught, with message 'bad int params' |
The only typing now to use the toolkit is including the custom header, and calling the functions. It is lightweight with a couple of functions used to generate common random number sequences. All the "grunt" work is hidden in a reusable header file.
Now the random generator can't accidentally be seeded more than one time. It can be force seeded if needed.
I take advantage of having overloaded functions to generate integer and floating point numbers. The C random generator can't generate floating point numbers easily. If at all.
You can argue with my design choices for the toolkit. I primarily wrote it for myself. Rewriting it when I see someone else here in the forum pointing out a particular bit of code that refines and betters what I had written previously.
So it is sloppy and not consistent throughout. It works. One day I might clean it up and be more uniform about how I initialize variables and other little odds and ends. C++17 inline variables, for instance.
Adapting this from the working paper was as much a learning experience for me as it is to have a ready-to-use toolkit available. It still is a work in progress.