diff --git a/src/openrct2/core/Random.hpp b/src/openrct2/core/Random.hpp new file mode 100644 index 0000000000..70c34afe40 --- /dev/null +++ b/src/openrct2/core/Random.hpp @@ -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 +#include +#include +#include +#include +#include + +namespace Random +{ + using namespace Numerics; + + /** + * FixedSeedSequence adheres to the _Named Requirement_ `SeedSequence`. + */ + template 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::type = 0, + typename std::enable_if<(std::is_convertible::value && ...), int>::type = 0> + explicit FixedSeedSequence(T... s) + : v{ static_cast(s)... } + { + } + + template(), ++std::declval<_It&>(), void())> + explicit FixedSeedSequence(_It begin, _It end) + { + std::copy(begin, end, v.begin()); + } + + template + explicit FixedSeedSequence(std::initializer_list il) + : FixedSeedSequence(il.begin(), il.end()) + { + } + + template 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 constexpr void param(_It ob) const + { + std::copy(v.begin(), v.end(), ob); + } + + protected: + std::array v; + }; + + typedef FixedSeedSequence<2> Rct2Seed; + + /** + * RotateEngine adheres to the _Named Requirement_ `RandomNumberEngine` + * https://en.cppreference.com/w/cpp/named_req/RandomNumberEngine + */ + template class RotateEngine + { + static_assert(std::is_unsigned::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::min(); + } + + static constexpr result_type max() + { + return std::numeric_limits::max(); + } + + explicit RotateEngine(result_type seed_value = default_seed) + { + seed(seed_value); + } + + RotateEngine(RotateEngine& r) + { + s0 = r.s0; + s1 = r.s1; + } + + template::value>::type> + explicit RotateEngine(Sseq& seed_seq) + { + seed(seed_seq); + } + + void seed(result_type s = default_seed) + { + s0 = s; + } + + template typename std::enable_if::value, void>::type seed(Sseq& seed_seq) + { + std::array 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 Rct2Engine; + +} // namespace Random diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index a22858af7d..d23a648668 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -960,7 +960,7 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0) void Network::CheckDesynchronizaton() { // 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; @@ -1596,7 +1596,7 @@ void Network::Server_Send_TICK() last_tick_sent_time = ticks; std::unique_ptr 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; // 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, diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index b8e3e858a2..fe18d67081 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -2451,8 +2451,7 @@ private: { // Date and srand gScenarioTicks = _s4.ticks; - gScenarioSrand0 = _s4.random_a; - gScenarioSrand1 = _s4.random_b; + scenario_rand_seed(_s4.random_a, _s4.random_b); gDateMonthsElapsed = _s4.month; gDateMonthTicks = _s4.day; diff --git a/src/openrct2/rct2/S6Exporter.cpp b/src/openrct2/rct2/S6Exporter.cpp index 25fa6e70be..266608047b 100644 --- a/src/openrct2/rct2/S6Exporter.cpp +++ b/src/openrct2/rct2/S6Exporter.cpp @@ -188,8 +188,8 @@ void S6Exporter::Export() _s6.elapsed_months = gDateMonthsElapsed; _s6.current_day = gDateMonthTicks; _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)); diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index fad31af4ba..2015a30951 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -17,6 +17,7 @@ #include "../core/FileStream.hpp" #include "../core/IStream.hpp" #include "../core/Path.hpp" +#include "../core/Random.hpp" #include "../core/String.hpp" #include "../interface/Viewport.h" #include "../localisation/Date.h" @@ -207,8 +208,8 @@ public: gDateMonthsElapsed = _s6.elapsed_months; gDateMonthTicks = _s6.current_day; 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(); diff --git a/src/openrct2/scenario/Scenario.cpp b/src/openrct2/scenario/Scenario.cpp index eaa6bf5862..5ef2424669 100644 --- a/src/openrct2/scenario/Scenario.cpp +++ b/src/openrct2/scenario/Scenario.cpp @@ -18,6 +18,7 @@ #include "../ParkImporter.h" #include "../audio/audio.h" #include "../config/Config.h" +#include "../core/Random.hpp" #include "../interface/Viewport.h" #include "../localisation/Date.h" #include "../localisation/Localisation.h" @@ -48,6 +49,7 @@ #include "ScenarioSources.h" #include +#include const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = { 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 gScenarioTicks; -uint32_t gScenarioSrand0; -uint32_t gScenarioSrand1; +static Random::Rct2Engine rnd; uint8_t gScenarioObjectiveType; uint8_t gScenarioObjectiveYear; @@ -90,8 +91,8 @@ void scenario_begin() game_load_init(); // Set the scenario pseudo-random seeds - gScenarioSrand0 ^= platform_get_ticks(); - gScenarioSrand1 ^= platform_get_ticks(); + Random::Rct2Seed s{ 0x1234567F ^ platform_get_ticks(), 0x789FABCD ^ platform_get_ticks() }; + rnd.seed(s); gParkFlags &= ~PARK_FLAGS_NO_MONEY; if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO) @@ -476,6 +477,22 @@ static int32_t scenario_create_ducks() return 1; } +std::pair 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 @@ -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) #endif { - uint32_t originalSrand0 = gScenarioSrand0; - gScenarioSrand0 += ror32(gScenarioSrand1 ^ 0x1234567F, 7); - gScenarioSrand1 = ror32(originalSrand0, 3); - #ifdef DEBUG_DESYNC if (fp == nullptr) { @@ -527,7 +540,7 @@ uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_ } #endif - return gScenarioSrand1; + return rnd(); } #ifdef DEBUG_DESYNC diff --git a/src/openrct2/scenario/Scenario.h b/src/openrct2/scenario/Scenario.h index 0e5966794e..186e9f8c10 100644 --- a/src/openrct2/scenario/Scenario.h +++ b/src/openrct2/scenario/Scenario.h @@ -366,8 +366,6 @@ enum extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT]; extern uint32_t gScenarioTicks; -extern uint32_t gScenarioSrand0; -extern uint32_t gScenarioSrand1; extern uint8_t gScenarioObjectiveType; extern uint8_t gScenarioObjectiveYear; @@ -394,6 +392,8 @@ void load_from_sc6(const char* path); void scenario_begin(); void scenario_update(); +std::pair scenario_rand_seed(); +void scenario_rand_seed(uint32_t s0, uint32_t s1); #ifdef DEBUG_DESYNC 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) diff --git a/test/tests/Pathfinding.cpp b/test/tests/Pathfinding.cpp index 43a99654df..2bcca46322 100644 --- a/test/tests/Pathfinding.cpp +++ b/test/tests/Pathfinding.cpp @@ -41,8 +41,7 @@ public: void SetUp() override { // Use a consistent random seed in every test - gScenarioSrand0 = 0x12345678; - gScenarioSrand1 = 0x87654321; + scenario_rand_seed(0x12345678, 0x87654321); } static void TearDownTestCase()