mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-23 15:52:55 +01:00
Refactor random engine
Introduce RotateEngine and Rct2Engine, FixedSeedSequence and Rct2Seed. Adhere respectively to requirements `RandomNumberEngine` and `SeedSequence`. Can be used with C++11 adaptors and distributions in <random>.
This commit is contained in:
175
src/openrct2/core/Random.hpp
Normal file
175
src/openrct2/core/Random.hpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Copyright (c) 2014-2019 OpenRCT2 developers
|
||||||
|
*
|
||||||
|
* For a complete list of all authors, please refer to contributors.md
|
||||||
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||||
|
*
|
||||||
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Numerics.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <random>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace Random
|
||||||
|
{
|
||||||
|
using namespace Numerics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FixedSeedSequence adheres to the _Named Requirement_ `SeedSequence`.
|
||||||
|
*/
|
||||||
|
template<size_t _N = 0> class FixedSeedSequence
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef uint32_t result_type;
|
||||||
|
|
||||||
|
static constexpr size_t N = _N;
|
||||||
|
static constexpr result_type default_seed = 0x1234567F;
|
||||||
|
|
||||||
|
explicit FixedSeedSequence()
|
||||||
|
{
|
||||||
|
std::fill(v.begin(), v.end(), default_seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename... T, typename std::enable_if<sizeof...(T) == N, int>::type = 0,
|
||||||
|
typename std::enable_if<(std::is_convertible<T, result_type>::value && ...), int>::type = 0>
|
||||||
|
explicit FixedSeedSequence(T... s)
|
||||||
|
: v{ static_cast<result_type>(s)... }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename _It, typename = decltype(*std::declval<_It&>(), ++std::declval<_It&>(), void())>
|
||||||
|
explicit FixedSeedSequence(_It begin, _It end)
|
||||||
|
{
|
||||||
|
std::copy(begin, end, v.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
explicit FixedSeedSequence(std::initializer_list<T> il)
|
||||||
|
: FixedSeedSequence(il.begin(), il.end())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename _It> void generate(_It begin, _It end)
|
||||||
|
{
|
||||||
|
std::copy_n(v.begin(), std::min((size_t)(end - begin), N), begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t size() const
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename _It> constexpr void param(_It ob) const
|
||||||
|
{
|
||||||
|
std::copy(v.begin(), v.end(), ob);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::array<result_type, N> v;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef FixedSeedSequence<2> Rct2Seed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RotateEngine adheres to the _Named Requirement_ `RandomNumberEngine`
|
||||||
|
* https://en.cppreference.com/w/cpp/named_req/RandomNumberEngine
|
||||||
|
*/
|
||||||
|
template<typename UIntType, UIntType __x, size_t __r1, size_t __r2> class RotateEngine
|
||||||
|
{
|
||||||
|
static_assert(std::is_unsigned<UIntType>::value, "Type must be unsigned integral.");
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef UIntType result_type;
|
||||||
|
static constexpr result_type x = __x;
|
||||||
|
static constexpr size_t r1 = __r1;
|
||||||
|
static constexpr size_t r2 = __r2;
|
||||||
|
static constexpr result_type default_seed = 1;
|
||||||
|
|
||||||
|
static constexpr result_type min()
|
||||||
|
{
|
||||||
|
return std::numeric_limits<result_type>::min();
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr result_type max()
|
||||||
|
{
|
||||||
|
return std::numeric_limits<result_type>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit RotateEngine(result_type seed_value = default_seed)
|
||||||
|
{
|
||||||
|
seed(seed_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
RotateEngine(RotateEngine& r)
|
||||||
|
{
|
||||||
|
s0 = r.s0;
|
||||||
|
s1 = r.s1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Sseq, typename = typename std::enable_if<!std::is_same<Sseq, RotateEngine>::value>::type>
|
||||||
|
explicit RotateEngine(Sseq& seed_seq)
|
||||||
|
{
|
||||||
|
seed(seed_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void seed(result_type s = default_seed)
|
||||||
|
{
|
||||||
|
s0 = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Sseq> typename std::enable_if<std::is_class<Sseq>::value, void>::type seed(Sseq& seed_seq)
|
||||||
|
{
|
||||||
|
std::array<result_type, 2> s;
|
||||||
|
seed_seq.generate(s.begin(), s.end());
|
||||||
|
s0 = s[0];
|
||||||
|
s1 = s[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void discard(size_t n)
|
||||||
|
{
|
||||||
|
for (; n > 0; n--)
|
||||||
|
(*this)();
|
||||||
|
}
|
||||||
|
result_type operator()()
|
||||||
|
{
|
||||||
|
auto s0z = s0;
|
||||||
|
s0 += ror(s1 ^ x, r1);
|
||||||
|
s1 = ror(s0z, r2);
|
||||||
|
return s1;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator==(const RotateEngine& lhs, const RotateEngine& rhs)
|
||||||
|
{
|
||||||
|
return lhs.s0 == rhs.s0 && lhs.s1 == rhs.s1;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const RotateEngine& e)
|
||||||
|
{
|
||||||
|
os << e.s0 << ' ' << e.s1;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend std::istream& operator>>(std::istream& is, RotateEngine& e)
|
||||||
|
{
|
||||||
|
is >> e.s0;
|
||||||
|
is >> e.s1;
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
result_type s0;
|
||||||
|
result_type s1;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef RotateEngine<uint32_t, 0x1234567F, 7, 3> Rct2Engine;
|
||||||
|
|
||||||
|
} // namespace Random
|
||||||
@@ -960,7 +960,7 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0)
|
|||||||
void Network::CheckDesynchronizaton()
|
void Network::CheckDesynchronizaton()
|
||||||
{
|
{
|
||||||
// Check synchronisation
|
// Check synchronisation
|
||||||
if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, gScenarioSrand0))
|
if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, scenario_rand_seed().first))
|
||||||
{
|
{
|
||||||
_desynchronised = true;
|
_desynchronised = true;
|
||||||
|
|
||||||
@@ -1596,7 +1596,7 @@ void Network::Server_Send_TICK()
|
|||||||
last_tick_sent_time = ticks;
|
last_tick_sent_time = ticks;
|
||||||
|
|
||||||
std::unique_ptr<NetworkPacket> packet(NetworkPacket::Allocate());
|
std::unique_ptr<NetworkPacket> packet(NetworkPacket::Allocate());
|
||||||
*packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << gScenarioSrand0;
|
*packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << scenario_rand_seed().first;
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
// Simple counter which limits how often a sprite checksum gets sent.
|
// Simple counter which limits how often a sprite checksum gets sent.
|
||||||
// This can get somewhat expensive, so we don't want to push it every tick in release,
|
// This can get somewhat expensive, so we don't want to push it every tick in release,
|
||||||
|
|||||||
@@ -2451,8 +2451,7 @@ private:
|
|||||||
{
|
{
|
||||||
// Date and srand
|
// Date and srand
|
||||||
gScenarioTicks = _s4.ticks;
|
gScenarioTicks = _s4.ticks;
|
||||||
gScenarioSrand0 = _s4.random_a;
|
scenario_rand_seed(_s4.random_a, _s4.random_b);
|
||||||
gScenarioSrand1 = _s4.random_b;
|
|
||||||
gDateMonthsElapsed = _s4.month;
|
gDateMonthsElapsed = _s4.month;
|
||||||
gDateMonthTicks = _s4.day;
|
gDateMonthTicks = _s4.day;
|
||||||
|
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ void S6Exporter::Export()
|
|||||||
_s6.elapsed_months = gDateMonthsElapsed;
|
_s6.elapsed_months = gDateMonthsElapsed;
|
||||||
_s6.current_day = gDateMonthTicks;
|
_s6.current_day = gDateMonthTicks;
|
||||||
_s6.scenario_ticks = gScenarioTicks;
|
_s6.scenario_ticks = gScenarioTicks;
|
||||||
_s6.scenario_srand_0 = gScenarioSrand0;
|
|
||||||
_s6.scenario_srand_1 = gScenarioSrand1;
|
std::tie(_s6.scenario_srand_0, _s6.scenario_srand_1) = scenario_rand_seed();
|
||||||
|
|
||||||
std::memcpy(_s6.tile_elements, gTileElements, sizeof(_s6.tile_elements));
|
std::memcpy(_s6.tile_elements, gTileElements, sizeof(_s6.tile_elements));
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "../core/FileStream.hpp"
|
#include "../core/FileStream.hpp"
|
||||||
#include "../core/IStream.hpp"
|
#include "../core/IStream.hpp"
|
||||||
#include "../core/Path.hpp"
|
#include "../core/Path.hpp"
|
||||||
|
#include "../core/Random.hpp"
|
||||||
#include "../core/String.hpp"
|
#include "../core/String.hpp"
|
||||||
#include "../interface/Viewport.h"
|
#include "../interface/Viewport.h"
|
||||||
#include "../localisation/Date.h"
|
#include "../localisation/Date.h"
|
||||||
@@ -207,8 +208,8 @@ public:
|
|||||||
gDateMonthsElapsed = _s6.elapsed_months;
|
gDateMonthsElapsed = _s6.elapsed_months;
|
||||||
gDateMonthTicks = _s6.current_day;
|
gDateMonthTicks = _s6.current_day;
|
||||||
gScenarioTicks = _s6.scenario_ticks;
|
gScenarioTicks = _s6.scenario_ticks;
|
||||||
gScenarioSrand0 = _s6.scenario_srand_0;
|
|
||||||
gScenarioSrand1 = _s6.scenario_srand_1;
|
scenario_rand_seed(_s6.scenario_srand_0, _s6.scenario_srand_1);
|
||||||
|
|
||||||
ImportTileElements();
|
ImportTileElements();
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "../ParkImporter.h"
|
#include "../ParkImporter.h"
|
||||||
#include "../audio/audio.h"
|
#include "../audio/audio.h"
|
||||||
#include "../config/Config.h"
|
#include "../config/Config.h"
|
||||||
|
#include "../core/Random.hpp"
|
||||||
#include "../interface/Viewport.h"
|
#include "../interface/Viewport.h"
|
||||||
#include "../localisation/Date.h"
|
#include "../localisation/Date.h"
|
||||||
#include "../localisation/Localisation.h"
|
#include "../localisation/Localisation.h"
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
#include "ScenarioSources.h"
|
#include "ScenarioSources.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = {
|
const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = {
|
||||||
STR_BEGINNER_PARKS, STR_CHALLENGING_PARKS, STR_EXPERT_PARKS, STR_REAL_PARKS, STR_OTHER_PARKS,
|
STR_BEGINNER_PARKS, STR_CHALLENGING_PARKS, STR_EXPERT_PARKS, STR_REAL_PARKS, STR_OTHER_PARKS,
|
||||||
@@ -66,8 +68,7 @@ uint16_t gSavedAge;
|
|||||||
uint32_t gLastAutoSaveUpdate = 0;
|
uint32_t gLastAutoSaveUpdate = 0;
|
||||||
|
|
||||||
uint32_t gScenarioTicks;
|
uint32_t gScenarioTicks;
|
||||||
uint32_t gScenarioSrand0;
|
static Random::Rct2Engine rnd;
|
||||||
uint32_t gScenarioSrand1;
|
|
||||||
|
|
||||||
uint8_t gScenarioObjectiveType;
|
uint8_t gScenarioObjectiveType;
|
||||||
uint8_t gScenarioObjectiveYear;
|
uint8_t gScenarioObjectiveYear;
|
||||||
@@ -90,8 +91,8 @@ void scenario_begin()
|
|||||||
game_load_init();
|
game_load_init();
|
||||||
|
|
||||||
// Set the scenario pseudo-random seeds
|
// Set the scenario pseudo-random seeds
|
||||||
gScenarioSrand0 ^= platform_get_ticks();
|
Random::Rct2Seed s{ 0x1234567F ^ platform_get_ticks(), 0x789FABCD ^ platform_get_ticks() };
|
||||||
gScenarioSrand1 ^= platform_get_ticks();
|
rnd.seed(s);
|
||||||
|
|
||||||
gParkFlags &= ~PARK_FLAGS_NO_MONEY;
|
gParkFlags &= ~PARK_FLAGS_NO_MONEY;
|
||||||
if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO)
|
if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO)
|
||||||
@@ -476,6 +477,22 @@ static int32_t scenario_create_ducks()
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> scenario_rand_seed()
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << rnd;
|
||||||
|
uint32_t s0, s1;
|
||||||
|
ss >> s0;
|
||||||
|
ss >> s1;
|
||||||
|
return { s0, s1 };
|
||||||
|
};
|
||||||
|
|
||||||
|
void scenario_rand_seed(uint32_t s0, uint32_t s1)
|
||||||
|
{
|
||||||
|
Random::Rct2Seed s{ s0, s1 };
|
||||||
|
rnd.seed(s);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* rct2: 0x006E37D2
|
* rct2: 0x006E37D2
|
||||||
@@ -491,10 +508,6 @@ static const char* realm = "LC";
|
|||||||
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data)
|
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
uint32_t originalSrand0 = gScenarioSrand0;
|
|
||||||
gScenarioSrand0 += ror32(gScenarioSrand1 ^ 0x1234567F, 7);
|
|
||||||
gScenarioSrand1 = ror32(originalSrand0, 3);
|
|
||||||
|
|
||||||
#ifdef DEBUG_DESYNC
|
#ifdef DEBUG_DESYNC
|
||||||
if (fp == nullptr)
|
if (fp == nullptr)
|
||||||
{
|
{
|
||||||
@@ -527,7 +540,7 @@ uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return gScenarioSrand1;
|
return rnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_DESYNC
|
#ifdef DEBUG_DESYNC
|
||||||
|
|||||||
@@ -366,8 +366,6 @@ enum
|
|||||||
extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT];
|
extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT];
|
||||||
|
|
||||||
extern uint32_t gScenarioTicks;
|
extern uint32_t gScenarioTicks;
|
||||||
extern uint32_t gScenarioSrand0;
|
|
||||||
extern uint32_t gScenarioSrand1;
|
|
||||||
|
|
||||||
extern uint8_t gScenarioObjectiveType;
|
extern uint8_t gScenarioObjectiveType;
|
||||||
extern uint8_t gScenarioObjectiveYear;
|
extern uint8_t gScenarioObjectiveYear;
|
||||||
@@ -394,6 +392,8 @@ void load_from_sc6(const char* path);
|
|||||||
void scenario_begin();
|
void scenario_begin();
|
||||||
void scenario_update();
|
void scenario_update();
|
||||||
|
|
||||||
|
std::pair<uint32_t, uint32_t> scenario_rand_seed();
|
||||||
|
void scenario_rand_seed(uint32_t s0, uint32_t s1);
|
||||||
#ifdef DEBUG_DESYNC
|
#ifdef DEBUG_DESYNC
|
||||||
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data);
|
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data);
|
||||||
# define scenario_rand() dbg_scenario_rand(__FILE__, __FUNCTION__, __LINE__, NULL)
|
# define scenario_rand() dbg_scenario_rand(__FILE__, __FUNCTION__, __LINE__, NULL)
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ public:
|
|||||||
void SetUp() override
|
void SetUp() override
|
||||||
{
|
{
|
||||||
// Use a consistent random seed in every test
|
// Use a consistent random seed in every test
|
||||||
gScenarioSrand0 = 0x12345678;
|
scenario_rand_seed(0x12345678, 0x87654321);
|
||||||
gScenarioSrand1 = 0x87654321;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TearDownTestCase()
|
static void TearDownTestCase()
|
||||||
|
|||||||
Reference in New Issue
Block a user