ge211  2021.5.1
A student game engine
ge211_random.hxx
1 #pragma once
2 
3 #include "ge211_error.hxx"
4 #include "ge211_if_cpp.hxx"
5 #include "ge211_forward.hxx"
6 #include "ge211_type_traits.hxx"
7 
8 #include <cmath>
9 #include <cstdint>
10 #include <limits>
11 #include <memory>
12 #include <random>
13 #include <type_traits>
14 
15 namespace ge211 {
16 namespace detail {
17 namespace random {
18 
19 using Generator = std::mt19937_64;
20 
21 Generator
22 construct_generator();
23 
24 template <typename T>
25 T
26 bound_between(T value, T lo, T hi)
27 {
28  return std::max(lo, std::min(hi, value));
29 }
30 
31 template <typename RESULT_TYPE>
32 struct Random_engine
33 {
34  using result_type = RESULT_TYPE;
35 
36  virtual result_type next() = 0;
37 
38  virtual result_type next_between(result_type, result_type) = 0;
39 
40  virtual ~Random_engine() = default;
41 };
42 
43 template <typename RESULT_TYPE, typename ENABLE = void>
44 struct Distribution
45 {
46  struct
47  {
48  RESULT_TYPE contents;
49  } error = "ge211::Random_source<RESULT_TYPE> requires "
50  "RESULT_TYPE to be a built-in arithmetic type like int "
51  "or double.";
52 };
53 
54 template <typename RESULT_TYPE>
55 class Distribution<
56  RESULT_TYPE,
57  std::enable_if_t<Is_Integral<RESULT_TYPE>>
58 >
59 {
60 public:
61  using result_type = RESULT_TYPE;
62 
63 private:
65  impl_type impl_;
66 
67 public:
68  Distribution(result_type lo, result_type hi)
69  : impl_{lo, hi}
70  { }
71 
72  explicit Distribution(result_type end)
73  : impl_{0, end - 1}
74  { }
75 
76  result_type operator()(Generator &gen)
77  {
78  return impl_(gen);
79  }
80 };
81 
82 
83 template <typename RESULT_TYPE>
84 class Distribution<
85  RESULT_TYPE,
86  std::enable_if_t<Is_Floating_Point<RESULT_TYPE>>>
87 {
88 public:
89  using result_type = RESULT_TYPE;
90 
91 private:
93  impl_type impl_;
94 
95 public:
96  Distribution(result_type lo, result_type hi)
97  : impl_{lo, hi}
98  { }
99 
100  result_type operator()(Generator &gen)
101  {
102  return impl_(gen);
103  }
104 };
105 
106 template <typename RESULT_TYPE>
107 class Pseudo_random_engine
108  : public Random_engine<RESULT_TYPE>
109 {
110 public:
111  using result_type = RESULT_TYPE;
112 
113  template <typename... Args>
114  Pseudo_random_engine(Args&&... args);
115 
116  result_type next() override;
117 
118  result_type next_between(result_type, result_type) override;
119 
120 private:
121  Distribution<result_type> distribution_;
122  Generator generator_;
123 };
124 
125 template <>
126 class Pseudo_random_engine<bool>
127  : public Random_engine<bool>
128 {
129 public:
130  explicit Pseudo_random_engine(double p_true);
131 
132  bool next() override;
133 
134  bool next_between(bool, bool) override;
135 
136 private:
137  double probability_;
138  Distribution<double> distribution_;
139  Generator generator_;
140 };
141 
142 template <typename RESULT_TYPE>
143 class Stub_random_engine
144  : public Random_engine<RESULT_TYPE>
145 {
146 public:
147  using result_type = RESULT_TYPE;
148  using container_type = std::vector<RESULT_TYPE>;
149  using iterator_type = typename container_type::const_iterator;
150 
151  // PRECONDITION: !container.empty()
152  Stub_random_engine(container_type&& container);
153 
154  result_type next() override;
155 
156  result_type next_between(result_type, result_type) override;
157 
158 private:
159  container_type container_;
160  iterator_type next_;
161 };
162 
163 } // end namespace random
164 } // end namespace detail
165 
169 { };
170 
181 constexpr unbounded_type const unbounded { };
182 
214 template <typename RESULT_TYPE>
216 {
217 public:
219  using result_type = RESULT_TYPE;
220 
243  IF_COMPILER(DECLARE_IF(!Is_Same<result_type, bool>))
245 
246 
256  IF_COMPILER(DECLARE_IF(
257  Is_Integral<result_type> &&
258  !Is_Same<result_type, bool>))
259  explicit Random_source(result_type limit);
260 
261 
287  IF_COMPILER(DECLARE_IF(Is_Same<result_type, bool>))
288  explicit Random_source(double p_true);
289 
290 
313  IF_COMPILER(DECLARE_IF(!Is_Same<result_type, bool>))
314  explicit Random_source(unbounded_type);
315 
316 
328  {
329  return engine_->next();
330  }
331 
358  {
359  return next();
360  }
361 
362 
383  IF_COMPILER(DECLARE_IF(!Is_Same<result_type, bool>))
385  {
386  return engine_->next_between(lo, hi);
387  }
388 
396  IF_COMPILER(DECLARE_IF(!Is_Same<result_type, bool>))
398  {
399  return next_between(lo, hi);
400  }
401 
453 
454 
462  void stub_with(std::vector<result_type> values);
463 
464 
471  void stub_with(result_type value);
472 
473 
477  IF_COMPILER(DECLARE_IF(!Is_Same<result_type, bool>))
478  static result_type bound_between(
479  result_type value, result_type lo, result_type hi);
480 
483  Random_source(Random_source&& other) = delete;
484 
488 
489 private:
490  using Engine = detail::random::Random_engine<result_type>;
491  using Prng = detail::random::Pseudo_random_engine<result_type>;
492  using Stub = detail::random::Stub_random_engine<result_type>;
493 
494  std::unique_ptr<Engine> engine_;
495 };
496 
497 
498 //
499 // IMPLEMENTATIONS
500 //
501 
502 namespace detail {
503 namespace random {
504 
505 using std::begin;
506 using std::end;
507 
508 template <typename RESULT_TYPE>
509 template <typename... Args>
510 Pseudo_random_engine<RESULT_TYPE>::Pseudo_random_engine(Args&&... args)
511  : distribution_{std::forward<Args>(args)...},
512  generator_{construct_generator()}
513 { }
514 
515 template <typename RESULT_TYPE>
516 RESULT_TYPE
517 Pseudo_random_engine<RESULT_TYPE>::next()
518 {
519  return distribution_(generator_);
520 }
521 
522 template <typename RESULT_TYPE>
523 RESULT_TYPE
524 Pseudo_random_engine<RESULT_TYPE>::next_between(result_type lo, result_type hi)
525 {
526  return Distribution<result_type>{lo, hi}(generator_);
527 }
528 
529 template <typename RESULT_TYPE>
530 Stub_random_engine<RESULT_TYPE>::Stub_random_engine(container_type&& container)
531  : container_(std::move(container)),
532  next_(begin(container_))
533 {
534  if (next_ == end(container_)) {
536  "Random_source: cannot stub with empty container"};
537  }
538 }
539 
540 template <typename RESULT_TYPE>
541 RESULT_TYPE
542 Stub_random_engine<RESULT_TYPE>::next()
543 {
544  result_type result = *next_++;
545  if (next_ == end(container_)) next_ = begin(container_);
546  return result;
547 }
548 
549 template <typename RESULT_TYPE>
550 RESULT_TYPE
551 Stub_random_engine<RESULT_TYPE>::next_between(result_type lo, result_type hi)
552 {
553  return bound_between<result_type>(next(), lo, hi);
554 }
555 
556 } // end namespace random
557 } // end namespace detail
558 
559 template <typename RESULT_TYPE>
560 IF_COMPILER(DEFINE_IF)
562  : engine_{std::make_unique<Prng>(lo, hi)}
563 { }
564 
565 template <typename RESULT_TYPE>
566 IF_COMPILER(DEFINE_IF)
568  : engine_{std::make_unique<Prng>(limit)}
569 { }
570 
571 template <typename RESULT_TYPE>
572 IF_COMPILER(DEFINE_IF)
573 Random_source<RESULT_TYPE>::Random_source(double p_true)
574  : engine_{std::make_unique<Prng>(p_true)}
575 { }
576 
577 template <typename RESULT_TYPE>
578 IF_COMPILER(DEFINE_IF)
581 { }
582 
583 template <typename RESULT_TYPE>
584 void
586 {
587  engine_ = std::make_unique<Stub>(std::move(values));
588 }
589 
590 template <typename RESULT_TYPE>
591 void
593 {
594  stub_with(std::vector<result_type>(values));
595 }
596 
597 template <typename RESULT_TYPE>
598 void
600 {
601  stub_with({value});
602 }
603 
604 template <typename RESULT_TYPE>
605 IF_COMPILER(DEFINE_IF)
606 RESULT_TYPE
608  result_type value,
609  result_type lo,
610  result_type hi)
611 {
612  return detail::random::bound_between<result_type>(value, lo, hi);
613 }
614 
615 } // end namespace ge211
std::uniform_int_distribution
ge211::Random_source::result_type
RESULT_TYPE result_type
The type of value generated by this Random_source.
Definition: ge211_random.hxx:219
ge211::unbounded
constexpr unbounded_type const unbounded
A tag value for passing to the constructor Random_source::Random_source(unbounded_type) in order to d...
Definition: ge211_random.hxx:181
std::vector
ge211::Random_source::bound_between
static result_type bound_between(result_type value, result_type lo, result_type hi)
Given a randomly-generated result_type value, bounds it between lo and hi (inclusive) by adjusting va...
Definition: ge211_random.hxx:607
ge211::unbounded_type
The type of special tag value unbounded.
Definition: ge211_random.hxx:169
ge211::Random_source::Random_source
Random_source(result_type lo, result_type hi)
Constructs a random source that generates values between lo and hi, inclusive.
Definition: ge211_random.hxx:561
std::uniform_real_distribution
std::mt19937_64
ge211
The game engine namespace.
Definition: ge211.hxx:4
ge211::Random_source::next_between
result_type next_between(result_type lo, result_type hi)
Returns a random value between lo and hi.
Definition: ge211_random.hxx:384
ge211::exceptions::Client_logic_error
An exception that indicates that a logic error was performed by the client.
Definition: ge211_error.hxx:48
ge211::Random_source::Random_source
Random_source(Random_source &&other)=delete
Random_sources cannot be move-constructed because they cannot be moved or copied.
ge211::Random_source
A generic class for generating pseudorandom numbers in uniform distribution over a specified range.
Definition: ge211_random.hxx:216
ge211::Random_source::operator=
Random_source & operator=(Random_source &&other)=delete
Random_sources cannot be move-assigned because they cannot be moved or copied.
std::end
T end(T... args)
std::max
T max(T... args)
std::unique_ptr< Engine >
ge211::Random_source::stub_with
void stub_with(std::initializer_list< result_type > values)
Configures this Random_source to return a predetermined sequence of values.
Definition: ge211_random.hxx:592
ge211::Random_source::operator()
result_type operator()()
Returns the next random value from this source.
Definition: ge211_random.hxx:357
std::next
T next(T... args)
ge211::Random_source::next
result_type next()
Returns the next random value from this source.
Definition: ge211_random.hxx:327
std::initializer_list