summaryrefslogtreecommitdiff
path: root/src/z-rand.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/z-rand.cc')
-rw-r--r--src/z-rand.cc254
1 files changed, 254 insertions, 0 deletions
diff --git a/src/z-rand.cc b/src/z-rand.cc
new file mode 100644
index 00000000..a35eb08b
--- /dev/null
+++ b/src/z-rand.cc
@@ -0,0 +1,254 @@
+/* File: z-rand.c */
+
+/* Purpose: a simple random number generator -BEN- */
+
+#include "z-rand.hpp"
+
+#include <assert.h>
+#include <cstdint>
+#include <limits>
+#include <random>
+#include <sstream>
+#include <type_traits>
+
+#include "pcg_random.hpp"
+#include "seed.hpp"
+
+/**
+ * Choice of RNG; we use the "statistically most powerful" (per the
+ * documentation) RNG. The "insecure" bit just means that the RNG
+ * is definitely known to *not* be cryptographically secure.
+ */
+using rng_t = pcg64_once_insecure;
+
+/**
+ * Reseed the given RNG.
+ */
+static void reseed_rng(rng_t *rng, seed_t const &seed)
+{
+ assert(rng != nullptr);
+ // Create a seed_seq from the seed data.
+ std::uint32_t data[seed_t::n_uint32];
+ std::seed_seq seed_seq(
+ std::begin(data),
+ std::end(data)
+ );
+ // Seed the RNG.
+ rng->seed(seed_seq);
+}
+
+/**
+ * Allocate a new RNG and initialize with the given seed.
+ */
+static rng_t *new_seeded_rng(seed_t const &seed)
+{
+ rng_t *rng = new rng_t;
+ reseed_rng(rng, seed);
+ return rng;
+}
+
+/**
+ * The "quick" RNG is used for fixed-seed applications.
+ */
+static rng_t *quick_rng()
+{
+ // Note that the "quick_rng" will always be seeded explicitly
+ // whenever it's used, so we don't need to do any seeding here.
+ static rng_t *instance = new rng_t();
+ return instance;
+}
+
+/**
+ * The "complex" RNG is used when we really want non-deterministic
+ * random numbers.
+ */
+static rng_t *complex_rng()
+{
+ static rng_t *instance = new_seeded_rng(seed_t::system());
+ return instance;
+}
+
+
+/**
+ * Current RNG.
+ */
+static rng_t *current_rng = nullptr;
+
+/**
+ * Get the current RNG.
+ */
+static rng_t *get_current_rng()
+{
+ // Do we need to initialize?
+ if (current_rng == nullptr)
+ {
+ // We start with the complex RNG.
+ current_rng = complex_rng();
+ }
+
+ return current_rng;
+}
+
+void set_quick_rng(seed_t const &seed)
+{
+ reseed_rng(quick_rng(), seed);
+ current_rng = quick_rng();
+}
+
+void set_complex_rng()
+{
+ current_rng = complex_rng();
+}
+
+std::string get_complex_rng_state()
+{
+ std::stringstream s;
+ s << *complex_rng();
+ return s.str();
+}
+
+void set_complex_rng_state(std::string const &state)
+{
+ std::stringstream s(state);
+ s >> *complex_rng();
+}
+
+
+
+/*
+ * Stochastic rounding
+ */
+static double round_stochastic(double x)
+{
+ double n;
+ double f = std::modf(x, &n);
+
+ // Round up?
+ if (f > 0.5)
+ {
+ return n + 1;
+ }
+
+ // Round down?
+ if (f < 0.5)
+ {
+ return n - 1;
+ }
+
+ // Tie breaker is random; hence 'stochastic'.
+ std::uniform_int_distribution<int> distribution(0, 1);
+ if (distribution(*get_current_rng()) == 0)
+ {
+ return n - 1;
+ }
+ else
+ {
+ return n + 1;
+ }
+}
+
+
+/*
+ * Generate a random integer number of NORMAL distribution
+ */
+s16b randnor(int mean, int stand)
+{
+ // Get our own return type; we need it for limits and casting.
+ using retval_t = std::result_of<decltype(&randnor)(int, int)>::type;
+
+ // Degenerate case
+ if (stand < 1)
+ {
+ return 0;
+ }
+
+ // Sample from normal distribution
+ std::normal_distribution<double> distribution(mean, stand);
+ double x = distribution(*get_current_rng());
+
+ // Stochastic rounding to avoid rounding bias
+ double rounded_x = round_stochastic(x);
+
+ // Enforce limits of retval_t. Given that we're talking about a normal
+ // distribution, we're usually very unlikely to actually hit these (given
+ // reasonable values for 'mean' and 'stand' parameters), but in (very) rare
+ // cases it's needed to avoid undefined behavior due to the conversion
+ // we're going to do. This does introduce some (very minor) bias, but
+ // it's really unavoidable since retval_t cannot represent all possible
+ // values. We also assuming that a double can accurately represent all
+ // values in the range of retval_t.
+ double clipped_x = std::min(
+ static_cast<double>(std::numeric_limits<retval_t>::max()),
+ std::max(static_cast<double>(std::numeric_limits<retval_t>::min()),
+ rounded_x));
+
+ // Done: We just need to convert to retval_t.
+ return static_cast<retval_t>(clipped_x);
+}
+
+
+
+/*
+ * Generates damage for "2d6" style dice rolls
+ */
+s32b damroll(s16b num, s16b sides)
+{
+ int i;
+ s32b sum = 0;
+ for (i = 0; i < num; i++) sum += randint(sides);
+ return (sum);
+}
+
+
+/*
+ * Same as above, but always maximal
+ */
+s32b maxroll(s16b num, s16b sides)
+{
+ return (num * sides);
+}
+
+bool magik(s32b p) {
+ return rand_int(100) < p;
+}
+
+s32b rand_int(s32b m)
+{
+ /* Degenerate case */
+ if (m < 1)
+ {
+ return 0;
+ }
+ /* Normal case */
+ std::uniform_int_distribution<s32b> distribution(0, m - 1);
+ return distribution(*get_current_rng());
+}
+
+s32b randint(s32b m)
+{
+ /* Degenerate case */
+ if (m < 2)
+ {
+ return 1;
+ }
+ /* Normal case */
+ std::uniform_int_distribution<s32b> distribution(1, m);
+ return distribution(*get_current_rng());
+}
+
+s32b rand_range(s32b a, s32b b)
+{
+ /* Degenerate case */
+ if (b < a)
+ {
+ return a;
+ }
+ /* Normal case */
+ std::uniform_int_distribution<s32b> distribution(a, b);
+ return distribution(*get_current_rng());
+}
+
+s32b rand_spread(s32b a, s32b d)
+{
+ return rand_range(a-d, a+d);
+}