From 84fbaa77a5889e77ff5c0fa7566f14ac4bed73b9 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 17 Feb 2021 01:54:16 +0100 Subject: [PATCH 1/3] replace rng LockGuard with polymorphic approach Signed-off-by: DNKpp --- src/algo.cpp | 19 ++-- src/algo.h | 13 ++- src/autobattle.cpp | 7 +- src/autobattle.h | 3 +- src/enemyai.cpp | 4 +- src/enemyai.h | 3 +- src/rand.cpp | 156 ++++++++++++++++----------- src/rand.h | 206 +++++++++++++++++++++++------------- tests/algo.cpp | 56 +++++----- tests/autobattle.cpp | 18 ++-- tests/enemyai.cpp | 12 +-- tests/game_player_input.cpp | 2 +- tests/rand.cpp | 133 +++++++++++------------ 13 files changed, 367 insertions(+), 265 deletions(-) diff --git a/src/algo.cpp b/src/algo.cpp index 023a2506cb..3d25d89f34 100644 --- a/src/algo.cpp +++ b/src/algo.cpp @@ -155,11 +155,11 @@ int CalcCriticalHitChance(const Game_Battler& source, const Game_Battler& target return crit_chance; } -int VarianceAdjustEffect(int base, int var) { +int VarianceAdjustEffect(int base, int var, Rand::RNG& rng) { // FIXME: RPG_RT 2k3 doesn't apply variance if negative attribute flips damage if (var > 0 && (base > 0 || Player::IsLegacy())) { int adj = std::max(1, var * base / 10); - return base + Rand::GetRandomNumber(0, adj) - adj / 2; + return base + Rand::GetRandomNumber(0, adj, rng) - adj / 2; } return base; } @@ -180,7 +180,8 @@ int CalcNormalAttackEffect(const Game_Battler& source, bool is_critical_hit, bool apply_variance, lcf::rpg::System::BattleCondition cond, - bool emulate_2k3_enemy_row_bug) + bool emulate_2k3_enemy_row_bug, + Rand::RNG& rng) { const auto atk = source.GetAtk(weapon); const auto def = target.GetDef(); @@ -209,7 +210,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, } if (apply_variance) { - dmg = VarianceAdjustEffect(dmg, 4); + dmg = VarianceAdjustEffect(dmg, 4, rng); } return dmg; @@ -218,7 +219,8 @@ int CalcNormalAttackEffect(const Game_Battler& source, int CalcSkillEffect(const Game_Battler& source, const Game_Battler& target, const lcf::rpg::Skill& skill, - bool apply_variance) { + bool apply_variance, + Rand::RNG& rng) { auto effect = skill.power; effect += skill.physical_rate * source.GetAtk() / 20; @@ -234,7 +236,7 @@ int CalcSkillEffect(const Game_Battler& source, effect = Attribute::ApplyAttributeSkillMultiplier(effect, target, skill); if (apply_variance) { - effect = VarianceAdjustEffect(effect, skill.variance); + effect = VarianceAdjustEffect(effect, skill.variance, rng); } return effect; @@ -242,13 +244,14 @@ int CalcSkillEffect(const Game_Battler& source, int CalcSelfDestructEffect(const Game_Battler& source, const Game_Battler& target, - bool apply_variance) { + bool apply_variance, + Rand::RNG& rng) { auto effect = source.GetAtk() - target.GetDef() / 2; effect = std::max(0, effect); if (apply_variance) { - effect = VarianceAdjustEffect(effect, 4); + effect = VarianceAdjustEffect(effect, 4, rng); } return effect; diff --git a/src/algo.h b/src/algo.h index cca8cf61a0..4df86534ff 100644 --- a/src/algo.h +++ b/src/algo.h @@ -23,10 +23,12 @@ #include #include #include "game_battler.h" +#include "rand.h" class Game_Actor; class Game_Enemy; + namespace Algo { /** @@ -63,7 +65,7 @@ bool IsRowAdjusted(const Game_Battler& battler, * * @return the adjusted damage amount */ -int VarianceAdjustEffect(int base, int var); +int VarianceAdjustEffect(int base, int var, Rand::RNG& generator = Rand::Service::rng()); /** * Compute the hit rate for a physical attack @@ -135,7 +137,8 @@ int CalcNormalAttackEffect(const Game_Battler& source, bool is_critical_hit, bool apply_variance, lcf::rpg::System::BattleCondition cond, - bool emulate_2k3_enemy_row_bug); + bool emulate_2k3_enemy_row_bug, + Rand::RNG& rng = Rand::Service::rng()); /** * Compute the base damage for a skill @@ -152,7 +155,8 @@ int CalcNormalAttackEffect(const Game_Battler& source, int CalcSkillEffect(const Game_Battler& source, const Game_Battler& target, const lcf::rpg::Skill& skill, - bool apply_variance); + bool apply_variance, + Rand::RNG& rng = Rand::Service::rng()); /** * Compute the base damage for self-destruct @@ -167,7 +171,8 @@ int CalcSkillEffect(const Game_Battler& source, */ int CalcSelfDestructEffect(const Game_Battler& source, const Game_Battler& target, - bool apply_variance); + bool apply_variance, + Rand::RNG& rng = Rand::Service::rng()); /** * Calculate the sp cost for a skill. diff --git a/src/autobattle.cpp b/src/autobattle.cpp index 75ee8e6ead..91d3eb7102 100644 --- a/src/autobattle.cpp +++ b/src/autobattle.cpp @@ -214,7 +214,8 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, - bool emulate_bugs) + bool emulate_bugs, + Rand::RNG& rng) { if (!target.Exists()) { return 0.0; @@ -223,7 +224,7 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, // RPG_RT BUG: Normal damage variance is not used // Note: RPG_RT does not do the "2k3_enemy_row_bug" when computing autobattle ranks. - double base_effect = Algo::CalcNormalAttackEffect(source, target, weapon, is_critical_hit, apply_variance, cond, false); + double base_effect = Algo::CalcNormalAttackEffect(source, target, weapon, is_critical_hit, apply_variance, cond, false, rng); // RPG_RT BUG: Dual Attack is ignored if (!emulate_bugs && source.HasDualAttack(weapon)) { base_effect *= 2; @@ -254,7 +255,7 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, } } if (rank > 0.0) { - rank = Rand::GetRandomNumber(0, 99) / 100.0 + rank * 1.5; + rank = Rand::GetRandomNumber(0, 99, rng) / 100.0 + rank * 1.5; } return rank; } diff --git a/src/autobattle.h b/src/autobattle.h index 4d6da8160f..abf234245b 100644 --- a/src/autobattle.h +++ b/src/autobattle.h @@ -22,6 +22,7 @@ #include #include #include +#include "rand.h" class Game_Actor; class Game_Enemy; @@ -143,7 +144,7 @@ double CalcSkillAutoBattleRank(const Game_Actor& source, const lcf::rpg::Skill& * @param apply_variance If true, apply variance to the damage * @param emulate_bugs Emulate all RPG_RT bugs for accuracy */ -double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, const Game_Battler& target, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, bool emulate_bugs); +double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, const Game_Battler& target, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, bool emulate_bugs, Rand::RNG& rng = Rand::Service::rng()); /** * Calculate the auto battle total effectiveness rank of using a normal attack. diff --git a/src/enemyai.cpp b/src/enemyai.cpp index 4e5ce03925..6ca33b9d60 100644 --- a/src/enemyai.cpp +++ b/src/enemyai.cpp @@ -300,7 +300,7 @@ static bool IsSkillEffectiveOnAnyTarget(Game_Enemy& source, int skill_id, bool e return true; } -void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs) { +void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs, Rand::RNG& rng) { const auto& actions = source.GetDbEnemy().actions; std::vector prios(actions.size(), 0); int max_prio = 0; @@ -344,7 +344,7 @@ void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs) { return; } - int which = Rand::GetRandomNumber(0, sum_prios - 1); + int which = Rand::GetRandomNumber(0, sum_prios - 1, rng); DebugLog("ENEMYAI: Enemy {}({}) sum_prios={} which={}", source.GetName(), source.GetTroopMemberId(), sum_prios, which); const lcf::rpg::EnemyAction* selected_action = nullptr; for (int i = 0; i < static_cast(actions.size()); ++i) { diff --git a/src/enemyai.h b/src/enemyai.h index bf60d7ef36..2aefe160ed 100644 --- a/src/enemyai.h +++ b/src/enemyai.h @@ -22,6 +22,7 @@ #include #include #include +#include "rand.h" class Game_Actor; class Game_Enemy; @@ -91,7 +92,7 @@ class RpgRtImproved: public AlgorithmBase { * @param emulate_bugs if true, emulate RPG_RT bugs * @post If an action was selected, the source will have a new battle algorithm attached. */ -void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs); +void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs, Rand::RNG& rng = Rand::Service::rng()); /** * Checks if the skill will be effective on a target. diff --git a/src/rand.cpp b/src/rand.cpp index df3bab956b..19c60e0043 100644 --- a/src/rand.cpp +++ b/src/rand.cpp @@ -26,20 +26,58 @@ #include #include -namespace { -Rand::RNG rng; +template +class RngWrapper : + public Rand::AbstractRngWrapper +{ +public: + explicit RngWrapper(TGenerator generator = TGenerator{}) : + AbstractRngWrapper{}, + m_Generator{ std::move(generator) } + { + } + + void seed(result_type seed) override + { + m_Generator.seed(seed); + } + + result_type operator()() override + { + return m_Generator(); + } + + std::int32_t operator()(std::int32_t from, std::int32_t to) override + { + auto ufrom = static_cast(from); + auto uto = static_cast(to); + auto urange = uto - ufrom; + auto ures = ufrom + GetRandomUnsigned(urange, *this); + return static_cast(ures); + } + +private: + TGenerator m_Generator; +}; + +Rand::RNG& Rand::Service::rng() +{ + static RngWrapper rng{}; + return rng; +} +namespace { /** Gets a random number uniformly distributed in [0, U32_MAX] */ -uint32_t GetRandomU32() { return rng(); } +uint32_t GetRandomU32(Rand::RNG& rng) { return rng(); } -int32_t rng_lock_value = 0; -bool rng_locked= false; +//int32_t rng_lock_value = 0; +//bool rng_locked= false; } /** Generate a random number in the range [0,max] */ -static uint32_t GetRandomUnsigned(uint32_t max) +static uint32_t GetRandomUnsigned(uint32_t max, Rand::RNG& rng) { - if (max == 0xffffffffull) return GetRandomU32(); + if (max == 0xffffffffull) return GetRandomU32(rng); // Rejection sampling: // 1. Divide the range of uint32 into blocks of max+1 @@ -51,85 +89,81 @@ static uint32_t GetRandomUnsigned(uint32_t max) uint32_t m = max + 1; uint32_t rem = -m % m; // = 2^32 mod m while (true) { - uint32_t n = GetRandomU32(); + uint32_t n = GetRandomU32(rng); if (n >= rem) return n % m; } } -int32_t Rand::GetRandomNumber(int32_t from, int32_t to) { +int32_t Rand::GetRandomNumber(int32_t from, int32_t to, RNG& rng) { assert(from <= to); - if (rng_locked) { - return Utils::Clamp(rng_lock_value, from, to); - } + //if (rng_locked) { + // return Utils::Clamp(rng_lock_value, from, to); + //} // Don't use uniform_int_distribution--the algorithm used isn't // portable between stdlibs. // We do from + (rand int in [0, to-from]). The miracle of two's // complement let's us do this all in unsigned and then just cast // back. - uint32_t ufrom = uint32_t(from); - uint32_t uto = uint32_t(to); - uint32_t urange = uto - ufrom; - uint32_t ures = ufrom + GetRandomUnsigned(urange); - return int32_t(ures); + return rng(from, to); } -Rand::RNG& Rand::GetRNG() { +Rand::RNG& Rand::GetRNG(RNG& rng) { return rng; } -bool Rand::ChanceOf(int32_t n, int32_t m) { +bool Rand::ChanceOf(int32_t n, int32_t m, RNG& rng) { assert(n >= 0 && m > 0); - return GetRandomNumber(1, m) <= n; + return GetRandomNumber(1, m, rng) <= n; } -bool Rand::PercentChance(float rate) { +bool Rand::PercentChance(float rate, RNG& rng) { constexpr auto scale = 0x1000000; - return GetRandomNumber(0, scale-1) < int32_t(rate * scale); + return GetRandomNumber(0, scale-1, rng) < int32_t(rate * scale); } -bool Rand::PercentChance(int rate) { - return GetRandomNumber(0, 99) < rate; +bool Rand::PercentChance(int rate, RNG& rng) { + return GetRandomNumber(0, 99, rng) < rate; } -void Rand::SeedRandomNumberGenerator(int32_t seed) { +void Rand::SeedRandomNumberGenerator(int32_t seed, RNG& rng) { rng.seed(seed); Output::Debug("Seeded the RNG with {}.", seed); } -void Rand::LockRandom(int32_t value) { - rng_locked = true; - rng_lock_value = value; -} - -void Rand::UnlockRandom() { - rng_locked = false; -} - -std::pair Rand::GetRandomLocked() { - return { rng_locked, rng_lock_value }; -} - -Rand::LockGuard::LockGuard(int32_t lock_value, bool locked) { - auto p = GetRandomLocked(); - _prev_locked = p.first; - _prev_lock_value = p.second; - _active = true; - - if (locked) { - LockRandom(lock_value); - } else { - UnlockRandom(); - } -} - -void Rand::LockGuard::Release() noexcept { - if (Enabled()) { - if (_prev_locked) { - LockRandom(_prev_lock_value); - } else { - UnlockRandom(); - } - Dismiss(); - } -} +//void Rand::LockRandom(int32_t value) { +// rng_locked = true; +// rng_lock_value = value; +//} +// +//void Rand::UnlockRandom() { +// rng_locked = false; +//} +// +//std::pair Rand::GetRandomLocked() { +// return { rng_locked, rng_lock_value }; +//} + +//Rand::LockGuard::LockGuard(int32_t lock_value, bool locked) { +// auto p = GetRandomLocked(); +// _prev_locked = p.first; +// _prev_lock_value = p.second; +// _active = true; +// +// if (locked) { +// LockRandom(lock_value); +// } else { +// UnlockRandom(); +// } +//} +// +//void Rand::LockGuard::Release() noexcept { +// if (Enabled()) { +// if (_prev_locked) { +// LockRandom(_prev_lock_value); +// } else { +// UnlockRandom(); +// } +// Dismiss(); +// } +//} diff --git a/src/rand.h b/src/rand.h index 03c7b76c35..f102cf03b4 100644 --- a/src/rand.h +++ b/src/rand.h @@ -26,12 +26,68 @@ #include "system.h" #include "string_view.h" #include "span.h" +#include "utils.h" namespace Rand { /** * The random number generator object to use */ -using RNG = std::mt19937; + +class AbstractRngWrapper +{ +public: + using result_type = std::uint32_t; + + virtual ~AbstractRngWrapper() noexcept = default; + + AbstractRngWrapper(const AbstractRngWrapper&) = delete; + AbstractRngWrapper& operator =(const AbstractRngWrapper&) = delete; + AbstractRngWrapper(AbstractRngWrapper&&) = delete; + AbstractRngWrapper& operator =(AbstractRngWrapper&&) = delete; + + virtual result_type operator()() = 0; + virtual std::int32_t operator()(std::int32_t from, std::int32_t to) = 0; + virtual void seed(result_type seed) = 0; + static result_type min() { return std::numeric_limits::min(); } + static result_type max() { return std::numeric_limits::max(); } + +protected: + AbstractRngWrapper() noexcept = default; +}; + +class ConstNumberGeneratorWrapper : + public AbstractRngWrapper +{ +public: + explicit ConstNumberGeneratorWrapper(std::int64_t value) noexcept : + m_Value{ value } + { + } + + result_type operator()() override + { + return static_cast(Utils::Clamp( m_Value, std::numeric_limits::min(), std::numeric_limits::max())); + } + + std::int32_t operator()(std::int32_t from, std::int32_t to) override + { + return Utils::Clamp(static_cast(m_Value), from, to); + } + + void seed(result_type seed) override + { + } + +private: + std::int64_t m_Value; +}; + +using RNG = AbstractRngWrapper; + +struct Service +{ + static RNG& rng(); +}; /** * Gets a random number in the inclusive range from - to. @@ -40,14 +96,14 @@ using RNG = std::mt19937; * @param to Interval end * @return Random number in inclusive interval */ -int32_t GetRandomNumber(int32_t from, int32_t to); +int32_t GetRandomNumber(int32_t from, int32_t to, RNG& generator = Service::rng()); /** * Gets the seeded Random Number Generator (RNG). * * @return the random number generator */ -RNG& GetRNG(); +RNG& GetRNG(RNG& generator = Service::rng()); /** * Has an n/m chance of returning true. If n>m, always returns true. @@ -56,7 +112,7 @@ RNG& GetRNG(); * @param m denominator of the probability (positive) * @return true with probability n/m, false with probability 1-n/m */ -bool ChanceOf(int32_t n, int32_t m); +bool ChanceOf(int32_t n, int32_t m, RNG& generator = Service::rng()); /** * Rolls a random number in [0.0f, 1.0f) returns true if it's less than rate. @@ -64,7 +120,7 @@ bool ChanceOf(int32_t n, int32_t m); * @param rate a value in [0.0f, 1.0f]. Values out of this range are clamped. * @return true with probability rate. */ -bool PercentChance(float rate); +bool PercentChance(float rate, RNG& generator = Service::rng()); /** * Rolls a random number in [0, 99] and returns true if it's less than rate. @@ -72,15 +128,15 @@ bool PercentChance(float rate); * @param rate a value in [0, 100]. Values out of this range are clamped. * @return true with probability rate. */ -bool PercentChance(int rate); -bool PercentChance(long rate); +bool PercentChance(int rate, RNG& generator = Service::rng()); +bool PercentChance(long rate, RNG& generator = Service::rng()); /** * Seeds the RNG used by GetRandomNumber and ChanceOf. * * @param seed Seed to use */ -void SeedRandomNumberGenerator(int32_t seed); +void SeedRandomNumberGenerator(int32_t seed, RNG& generator = Service::rng()); /** * Forces GetRandomNumber() and all dervative functions to return a fixed value. @@ -89,75 +145,75 @@ void SeedRandomNumberGenerator(int32_t seed); * @param lock_value the value to set. A calls to GetRandomNumber(a, b) will return clamp(lock_value, a, b) * @post All calls to GetRandomNumber(a, b) will return clamp(lock_value, a, b) */ -void LockRandom(int32_t lock_value); - -/** - * Disables locked random number and returns RNG to original state. - * @post All calls to GetRandomNumber(a, b) will return random values. - */ -void UnlockRandom(); - -/** - * Retrive whether random numbers are locked and if so, which value they are locked to. - * @return whether or not random numbers are locked and if so, to what value. - */ -std::pair GetRandomLocked(); +//void LockRandom(int32_t lock_value); +// +///** +// * Disables locked random number and returns RNG to original state. +// * @post All calls to GetRandomNumber(a, b) will return random values. +// */ +//void UnlockRandom(); +// +///** +// * Retrive whether random numbers are locked and if so, which value they are locked to. +// * @return whether or not random numbers are locked and if so, to what value. +// */ +//std::pair GetRandomLocked(); /** An RAII guard which fixes the rng while active and resets on destruction */ -class LockGuard { -public: - /** - * Store current state and set locked state - * @param lock_value The rng value to fix to - * @param locked Whether to fix or reset - */ - LockGuard(int32_t lock_value, bool locked = true); - - LockGuard(const LockGuard&) = delete; - LockGuard& operator=(const LockGuard&) = delete; - - /** Move other LockGuard to this */ - LockGuard(LockGuard&& o) noexcept; - LockGuard& operator=(LockGuard&&) = delete; - - /** Calls Release() */ - ~LockGuard(); - - /** If Enabled(), returns the rng locked state to what it was */ - void Release() noexcept; - - /** Disables the LockGuard leaving the rng state as is */ - void Dismiss(); - - /** @return whether the guard is enabled and will release on destruction */ - bool Enabled() const; -private: - int32_t _prev_lock_value = 0; - bool _prev_locked = false; - bool _active = false; -}; - -inline bool PercentChance(long rate) { - return PercentChance(static_cast(rate)); -} - -inline LockGuard::LockGuard(LockGuard&& o) noexcept { - std::swap(_prev_lock_value, o._prev_lock_value); - std::swap(_prev_locked, o._prev_locked); - std::swap(_active, o._active); -} - -inline LockGuard::~LockGuard() { - Release(); -} - -inline void LockGuard::Dismiss() { - _active = false; -} - -inline bool LockGuard::Enabled() const { - return _active; -} +//class LockGuard { +//public: +// /** +// * Store current state and set locked state +// * @param lock_value The rng value to fix to +// * @param locked Whether to fix or reset +// */ +// LockGuard(int32_t lock_value, bool locked = true); +// +// LockGuard(const LockGuard&) = delete; +// LockGuard& operator=(const LockGuard&) = delete; +// +// /** Move other LockGuard to this */ +// LockGuard(LockGuard&& o) noexcept; +// LockGuard& operator=(LockGuard&&) = delete; +// +// /** Calls Release() */ +// ~LockGuard(); +// +// /** If Enabled(), returns the rng locked state to what it was */ +// void Release() noexcept; +// +// /** Disables the LockGuard leaving the rng state as is */ +// void Dismiss(); +// +// /** @return whether the guard is enabled and will release on destruction */ +// bool Enabled() const; +//private: +// int32_t _prev_lock_value = 0; +// bool _prev_locked = false; +// bool _active = false; +//}; +// +//inline bool PercentChance(long rate) { +// return PercentChance(static_cast(rate)); +//} +// +//inline LockGuard::LockGuard(LockGuard&& o) noexcept { +// std::swap(_prev_lock_value, o._prev_lock_value); +// std::swap(_prev_locked, o._prev_locked); +// std::swap(_active, o._active); +//} +// +//inline LockGuard::~LockGuard() { +// Release(); +//} +// +//inline void LockGuard::Dismiss() { +// _active = false; +//} +// +//inline bool LockGuard::Enabled() const { +// return _active; +//} } // namespace Rand diff --git a/tests/algo.cpp b/tests/algo.cpp index 00138bb1eb..8f8ef64ff6 100644 --- a/tests/algo.cpp +++ b/tests/algo.cpp @@ -121,27 +121,27 @@ TEST_CASE("Variance") { SUBCASE(">0") { SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); for (int var = 1; var <= 10; ++var) { - REQUIRE_EQ(100 + 5 * var, Algo::VarianceAdjustEffect(100, var)); + REQUIRE_EQ(100 + 5 * var, Algo::VarianceAdjustEffect(100, var, gen)); } } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); for (int var = 1; var <= 10; ++var) { - REQUIRE_EQ(100 - 5 * var, Algo::VarianceAdjustEffect(100, var)); + REQUIRE_EQ(100 - 5 * var, Algo::VarianceAdjustEffect(100, var, gen)); } } } SUBCASE("one") { SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - REQUIRE_EQ(2, Algo::VarianceAdjustEffect(1, 10)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + REQUIRE_EQ(2, Algo::VarianceAdjustEffect(1, 10, gen)); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - REQUIRE_EQ(1, Algo::VarianceAdjustEffect(1, 10)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + REQUIRE_EQ(1, Algo::VarianceAdjustEffect(1, 10, gen)); } } @@ -159,14 +159,14 @@ TEST_CASE("Variance") { REQUIRE(Player::IsLegacy()); SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); - REQUIRE_EQ(1, Algo::VarianceAdjustEffect(0, 1)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0, gen)); + REQUIRE_EQ(1, Algo::VarianceAdjustEffect(0, 1, gen)); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 1)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0, gen)); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 1, gen)); } } } @@ -909,12 +909,12 @@ TEST_CASE("NormalAttackVariance") { REQUIRE_EQ(target.GetDef(), 90); SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - REQUIRE_EQ(46, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + REQUIRE_EQ(46, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false, gen)); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - REQUIRE_EQ(31, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + REQUIRE_EQ(31, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false, gen)); } } } @@ -928,14 +928,14 @@ static void testSkillVar(Game_Battler& source, Game_Battler& target, int var, in skill2->scope = lcf::rpg::Skill::Scope_ally; SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - REQUIRE_EQ(dmg_high, Algo::CalcSkillEffect(source, target, *skill1, true)); - REQUIRE_EQ(heal_high, Algo::CalcSkillEffect(source, target, *skill2, true)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + REQUIRE_EQ(dmg_high, Algo::CalcSkillEffect(source, target, *skill1, true, gen)); + REQUIRE_EQ(heal_high, Algo::CalcSkillEffect(source, target, *skill2, true, gen)); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - REQUIRE_EQ(dmg_low, Algo::CalcSkillEffect(source, target, *skill1, true)); - REQUIRE_EQ(heal_low, Algo::CalcSkillEffect(source, target, *skill2, true)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + REQUIRE_EQ(dmg_low, Algo::CalcSkillEffect(source, target, *skill1, true, gen)); + REQUIRE_EQ(heal_low, Algo::CalcSkillEffect(source, target, *skill2, true, gen)); } } @@ -965,12 +965,12 @@ TEST_CASE("SelfDestructVariance") { auto source = MakeStatEnemy(1, 150, 0, 0); SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - REQUIRE_EQ(120, Algo::CalcSelfDestructEffect(source, target, true)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + REQUIRE_EQ(120, Algo::CalcSelfDestructEffect(source, target, true, gen)); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - REQUIRE_EQ(80, Algo::CalcSelfDestructEffect(source, target, true)); + Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + REQUIRE_EQ(80, Algo::CalcSelfDestructEffect(source, target, true, gen)); } } diff --git a/tests/autobattle.cpp b/tests/autobattle.cpp index 2b8ebb5a35..4cf27b28a6 100644 --- a/tests/autobattle.cpp +++ b/tests/autobattle.cpp @@ -28,11 +28,11 @@ decltype(auto) MakeActor(int id, int hp, int sp, int atk, int def, int spi, int TEST_SUITE_BEGIN("Autobattle"); -static void testNormalAttack(const Game_Actor& source, const Game_Battler& target, double v0, double v1, double v2, double v3) { - REQUIRE(doctest::Approx(v0) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, true)); - REQUIRE(doctest::Approx(v1) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, false)); - REQUIRE(doctest::Approx(v2) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, true)); - REQUIRE(doctest::Approx(v3) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, false)); +static void testNormalAttack(const Game_Actor& source, const Game_Battler& target, double v0, double v1, double v2, double v3, Rand::RNG& rng) { + REQUIRE(doctest::Approx(v0) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, true, rng)); + REQUIRE(doctest::Approx(v1) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, false, rng)); + REQUIRE(doctest::Approx(v2) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, true, rng)); + REQUIRE(doctest::Approx(v3) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, false, rng)); } TEST_CASE("NormalAttackTargetRank") { @@ -46,12 +46,12 @@ TEST_CASE("NormalAttackTargetRank") { REQUIRE_EQ(target.GetDef(), 90); SUBCASE("max") { - Rand::LockGuard lk(INT32_MAX); - testNormalAttack(source, target, 1.131, 1.131, 1.158, 1.158); + Rand::ConstNumberGeneratorWrapper rng(INT32_MAX); + testNormalAttack(source, target, 1.131, 1.131, 1.158, 1.158, rng); } SUBCASE("min") { - Rand::LockGuard lk(INT32_MIN); - testNormalAttack(source, target, 0.141, 0.141, 0.114, 0.114); + Rand::ConstNumberGeneratorWrapper rng(INT32_MIN); + testNormalAttack(source, target, 0.141, 0.141, 0.114, 0.114, rng); } } } diff --git a/tests/enemyai.cpp b/tests/enemyai.cpp index 1466593f3d..5fbcffc319 100644 --- a/tests/enemyai.cpp +++ b/tests/enemyai.cpp @@ -244,16 +244,16 @@ static void testActionType(int start, int end, Game_BattleAlgorithm::Type type_c bool bugs = true; CAPTURE(bugs); - Rand::LockGuard lk(rng); + Rand::ConstNumberGeneratorWrapper gen(rng); bugs = true; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); REQUIRE(source.GetBattleAlgorithm()); REQUIRE_EQ(static_cast(type_compat), static_cast(source.GetBattleAlgorithm()->GetType())); bugs = false; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); REQUIRE(source.GetBattleAlgorithm()); REQUIRE_EQ(static_cast(type_improved), static_cast(source.GetBattleAlgorithm()->GetType())); } @@ -265,15 +265,15 @@ static void testActionNullptr(int start, int end, Game_Enemy& source) { bool bugs = true; CAPTURE(bugs); - Rand::LockGuard lk(rng); + Rand::ConstNumberGeneratorWrapper gen(rng); bugs = true; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); REQUIRE_FALSE(source.GetBattleAlgorithm()); bugs = false; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); REQUIRE_FALSE(source.GetBattleAlgorithm()); } } diff --git a/tests/game_player_input.cpp b/tests/game_player_input.cpp index 6f2cb420cd..0f2179b35d 100644 --- a/tests/game_player_input.cpp +++ b/tests/game_player_input.cpp @@ -67,7 +67,7 @@ static void testMove(bool success, int input_dir, int dir, int x, int y, int dx, const auto ty = y + dy; // Force RNG to always fail to generate a random encounter - Rand::LockGuard lk(INT32_MAX); + //Rand::LockGuard lk(INT32_MAX); ForceUpdate(ch); if (success) { int enc_steps = (cheat && debug) ? 0 : 100; diff --git a/tests/rand.cpp b/tests/rand.cpp index 361d672252..71575d9211 100644 --- a/tests/rand.cpp +++ b/tests/rand.cpp @@ -19,75 +19,76 @@ TEST_CASE("GetRandomNumber") { testGetRandomNumber(-5, -2); } -TEST_CASE("Lock") { - REQUIRE_FALSE(Rand::GetRandomLocked().first); - - Rand::LockRandom(55); - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, 55); - - Rand::UnlockRandom(); - - REQUIRE_FALSE(Rand::GetRandomLocked().first); -} - -TEST_CASE("LockGuardNest") { - REQUIRE_FALSE(Rand::GetRandomLocked().first); - { - Rand::LockGuard fg(32); - REQUIRE(fg.Enabled()); - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, 32); - - { - Rand::LockGuard fg(0, false); - REQUIRE(fg.Enabled()); - - REQUIRE_FALSE(Rand::GetRandomLocked().first); - } - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, 32); - } - REQUIRE_FALSE(Rand::GetRandomLocked().first); -} - -TEST_CASE("LockGuardRelease") { - REQUIRE_FALSE(Rand::GetRandomLocked().first); - - Rand::LockGuard fg(-16); - REQUIRE(fg.Enabled()); - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, -16); - - fg.Release(); - - REQUIRE_FALSE(Rand::GetRandomLocked().first); - REQUIRE_FALSE(fg.Enabled()); -} - -TEST_CASE("LockGuardDismiss") { - Rand::LockGuard fg(INT32_MAX); - REQUIRE(fg.Enabled()); - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); - - fg.Dismiss(); - - REQUIRE(Rand::GetRandomLocked().first); - REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); - - Rand::UnlockRandom(); -} +//TEST_CASE("Lock") { +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +// +// Rand::LockRandom(55); +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, 55); +// +// Rand::UnlockRandom(); +// +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +//} + +//TEST_CASE("LockGuardNest") { +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +// { +// Rand::LockGuard fg(32); +// REQUIRE(fg.Enabled()); +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, 32); +// +// { +// Rand::LockGuard fg(0, false); +// REQUIRE(fg.Enabled()); +// +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +// } +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, 32); +// } +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +//} +// +//TEST_CASE("LockGuardRelease") { +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +// +// Rand::LockGuard fg(-16); +// REQUIRE(fg.Enabled()); +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, -16); +// +// fg.Release(); +// +// REQUIRE_FALSE(Rand::GetRandomLocked().first); +// REQUIRE_FALSE(fg.Enabled()); +//} +// +//TEST_CASE("LockGuardDismiss") { +// Rand::LockGuard fg(INT32_MAX); +// REQUIRE(fg.Enabled()); +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); +// +// fg.Dismiss(); +// +// REQUIRE(Rand::GetRandomLocked().first); +// REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); +// +// Rand::UnlockRandom(); +//} static void testGetRandomNumberFixed(int32_t a, int32_t b, int32_t fix, int32_t result) { - Rand::LockGuard fg(fix); + //Rand::LockGuard fg(fix); for (int i = 0; i < 10; ++i) { - auto x = Rand::GetRandomNumber(a, b); + Rand::ConstNumberGeneratorWrapper rng(fix); + auto x = GetRandomNumber(a, b, rng); REQUIRE_EQ(x, result); } } From 5ad570437bb8494e716474cf1e6ba24658698d89 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 17 Feb 2021 13:13:35 +0100 Subject: [PATCH 2/3] make RNG ScopedRNGExchange-able and replace previous RNG injection system Signed-off-by: DNKpp --- src/algo.cpp | 19 ++-- src/algo.h | 13 +-- src/autobattle.cpp | 7 +- src/autobattle.h | 3 +- src/enemyai.cpp | 4 +- src/enemyai.h | 3 +- src/rand.cpp | 180 ++++++++++++++---------------- src/rand.h | 213 +++++++++++++++--------------------- tests/algo.cpp | 56 +++++----- tests/autobattle.cpp | 18 +-- tests/enemyai.cpp | 12 +- tests/game_player_input.cpp | 2 +- tests/rand.cpp | 5 +- 13 files changed, 235 insertions(+), 300 deletions(-) diff --git a/src/algo.cpp b/src/algo.cpp index 3d25d89f34..023a2506cb 100644 --- a/src/algo.cpp +++ b/src/algo.cpp @@ -155,11 +155,11 @@ int CalcCriticalHitChance(const Game_Battler& source, const Game_Battler& target return crit_chance; } -int VarianceAdjustEffect(int base, int var, Rand::RNG& rng) { +int VarianceAdjustEffect(int base, int var) { // FIXME: RPG_RT 2k3 doesn't apply variance if negative attribute flips damage if (var > 0 && (base > 0 || Player::IsLegacy())) { int adj = std::max(1, var * base / 10); - return base + Rand::GetRandomNumber(0, adj, rng) - adj / 2; + return base + Rand::GetRandomNumber(0, adj) - adj / 2; } return base; } @@ -180,8 +180,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, bool is_critical_hit, bool apply_variance, lcf::rpg::System::BattleCondition cond, - bool emulate_2k3_enemy_row_bug, - Rand::RNG& rng) + bool emulate_2k3_enemy_row_bug) { const auto atk = source.GetAtk(weapon); const auto def = target.GetDef(); @@ -210,7 +209,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, } if (apply_variance) { - dmg = VarianceAdjustEffect(dmg, 4, rng); + dmg = VarianceAdjustEffect(dmg, 4); } return dmg; @@ -219,8 +218,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, int CalcSkillEffect(const Game_Battler& source, const Game_Battler& target, const lcf::rpg::Skill& skill, - bool apply_variance, - Rand::RNG& rng) { + bool apply_variance) { auto effect = skill.power; effect += skill.physical_rate * source.GetAtk() / 20; @@ -236,7 +234,7 @@ int CalcSkillEffect(const Game_Battler& source, effect = Attribute::ApplyAttributeSkillMultiplier(effect, target, skill); if (apply_variance) { - effect = VarianceAdjustEffect(effect, skill.variance, rng); + effect = VarianceAdjustEffect(effect, skill.variance); } return effect; @@ -244,14 +242,13 @@ int CalcSkillEffect(const Game_Battler& source, int CalcSelfDestructEffect(const Game_Battler& source, const Game_Battler& target, - bool apply_variance, - Rand::RNG& rng) { + bool apply_variance) { auto effect = source.GetAtk() - target.GetDef() / 2; effect = std::max(0, effect); if (apply_variance) { - effect = VarianceAdjustEffect(effect, 4, rng); + effect = VarianceAdjustEffect(effect, 4); } return effect; diff --git a/src/algo.h b/src/algo.h index 4df86534ff..cca8cf61a0 100644 --- a/src/algo.h +++ b/src/algo.h @@ -23,12 +23,10 @@ #include #include #include "game_battler.h" -#include "rand.h" class Game_Actor; class Game_Enemy; - namespace Algo { /** @@ -65,7 +63,7 @@ bool IsRowAdjusted(const Game_Battler& battler, * * @return the adjusted damage amount */ -int VarianceAdjustEffect(int base, int var, Rand::RNG& generator = Rand::Service::rng()); +int VarianceAdjustEffect(int base, int var); /** * Compute the hit rate for a physical attack @@ -137,8 +135,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, bool is_critical_hit, bool apply_variance, lcf::rpg::System::BattleCondition cond, - bool emulate_2k3_enemy_row_bug, - Rand::RNG& rng = Rand::Service::rng()); + bool emulate_2k3_enemy_row_bug); /** * Compute the base damage for a skill @@ -155,8 +152,7 @@ int CalcNormalAttackEffect(const Game_Battler& source, int CalcSkillEffect(const Game_Battler& source, const Game_Battler& target, const lcf::rpg::Skill& skill, - bool apply_variance, - Rand::RNG& rng = Rand::Service::rng()); + bool apply_variance); /** * Compute the base damage for self-destruct @@ -171,8 +167,7 @@ int CalcSkillEffect(const Game_Battler& source, */ int CalcSelfDestructEffect(const Game_Battler& source, const Game_Battler& target, - bool apply_variance, - Rand::RNG& rng = Rand::Service::rng()); + bool apply_variance); /** * Calculate the sp cost for a skill. diff --git a/src/autobattle.cpp b/src/autobattle.cpp index 91d3eb7102..75ee8e6ead 100644 --- a/src/autobattle.cpp +++ b/src/autobattle.cpp @@ -214,8 +214,7 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, - bool emulate_bugs, - Rand::RNG& rng) + bool emulate_bugs) { if (!target.Exists()) { return 0.0; @@ -224,7 +223,7 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, // RPG_RT BUG: Normal damage variance is not used // Note: RPG_RT does not do the "2k3_enemy_row_bug" when computing autobattle ranks. - double base_effect = Algo::CalcNormalAttackEffect(source, target, weapon, is_critical_hit, apply_variance, cond, false, rng); + double base_effect = Algo::CalcNormalAttackEffect(source, target, weapon, is_critical_hit, apply_variance, cond, false); // RPG_RT BUG: Dual Attack is ignored if (!emulate_bugs && source.HasDualAttack(weapon)) { base_effect *= 2; @@ -255,7 +254,7 @@ double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, } } if (rank > 0.0) { - rank = Rand::GetRandomNumber(0, 99, rng) / 100.0 + rank * 1.5; + rank = Rand::GetRandomNumber(0, 99) / 100.0 + rank * 1.5; } return rank; } diff --git a/src/autobattle.h b/src/autobattle.h index abf234245b..4d6da8160f 100644 --- a/src/autobattle.h +++ b/src/autobattle.h @@ -22,7 +22,6 @@ #include #include #include -#include "rand.h" class Game_Actor; class Game_Enemy; @@ -144,7 +143,7 @@ double CalcSkillAutoBattleRank(const Game_Actor& source, const lcf::rpg::Skill& * @param apply_variance If true, apply variance to the damage * @param emulate_bugs Emulate all RPG_RT bugs for accuracy */ -double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, const Game_Battler& target, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, bool emulate_bugs, Rand::RNG& rng = Rand::Service::rng()); +double CalcNormalAttackAutoBattleTargetRank(const Game_Actor& source, const Game_Battler& target, Game_Battler::Weapon weapon, lcf::rpg::System::BattleCondition cond, bool apply_variance, bool emulate_bugs); /** * Calculate the auto battle total effectiveness rank of using a normal attack. diff --git a/src/enemyai.cpp b/src/enemyai.cpp index 6ca33b9d60..4e5ce03925 100644 --- a/src/enemyai.cpp +++ b/src/enemyai.cpp @@ -300,7 +300,7 @@ static bool IsSkillEffectiveOnAnyTarget(Game_Enemy& source, int skill_id, bool e return true; } -void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs, Rand::RNG& rng) { +void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs) { const auto& actions = source.GetDbEnemy().actions; std::vector prios(actions.size(), 0); int max_prio = 0; @@ -344,7 +344,7 @@ void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs, Rand: return; } - int which = Rand::GetRandomNumber(0, sum_prios - 1, rng); + int which = Rand::GetRandomNumber(0, sum_prios - 1); DebugLog("ENEMYAI: Enemy {}({}) sum_prios={} which={}", source.GetName(), source.GetTroopMemberId(), sum_prios, which); const lcf::rpg::EnemyAction* selected_action = nullptr; for (int i = 0; i < static_cast(actions.size()); ++i) { diff --git a/src/enemyai.h b/src/enemyai.h index 2aefe160ed..bf60d7ef36 100644 --- a/src/enemyai.h +++ b/src/enemyai.h @@ -22,7 +22,6 @@ #include #include #include -#include "rand.h" class Game_Actor; class Game_Enemy; @@ -92,7 +91,7 @@ class RpgRtImproved: public AlgorithmBase { * @param emulate_bugs if true, emulate RPG_RT bugs * @post If an action was selected, the source will have a new battle algorithm attached. */ -void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs, Rand::RNG& rng = Rand::Service::rng()); +void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs); /** * Checks if the skill will be effective on a target. diff --git a/src/rand.cpp b/src/rand.cpp index 19c60e0043..43a63cdb35 100644 --- a/src/rand.cpp +++ b/src/rand.cpp @@ -19,40 +19,97 @@ #include "rand.h" #include "utils.h" #include "output.h" -#include "compiler.h" #include #include -#include #include #include +namespace Rand +{ + AbstractRNGWrapper::result_type SequencedRNGWrapper::operator()() + { + return pickNext(); + } + + AbstractRNGWrapper::result_type SequencedRNGWrapper::distribute(std::uint32_t max) + { + return Utils::Clamp(pickNext(), 0u, max); + } + + std::int32_t SequencedRNGWrapper::distribute(std::int32_t from, std::int32_t to) + { + return Utils::Clamp(static_cast(pickNext()), from, to); + } + + void SequencedRNGWrapper::seed(Seed_t seed) + { + } + + AbstractRNGWrapper::result_type SequencedRNGWrapper::pickNext() + { + auto value = m_Sequence[m_NextIndex]; + m_NextIndex = ++m_NextIndex % std::size(m_Sequence); + return value; + } +} + template -class RngWrapper : - public Rand::AbstractRngWrapper +class RNGWrapper : + public Rand::AbstractRNGWrapper { public: - explicit RngWrapper(TGenerator generator = TGenerator{}) : - AbstractRngWrapper{}, + explicit RNGWrapper(TGenerator generator = TGenerator{}) : + AbstractRNGWrapper{}, m_Generator{ std::move(generator) } { } - void seed(result_type seed) override + void seed(Rand::Seed_t seed) override { m_Generator.seed(seed); } + /** Gets a random number uniformly distributed in [0, U32_MAX] */ result_type operator()() override { return m_Generator(); } - std::int32_t operator()(std::int32_t from, std::int32_t to) override + /** Generate a random number in the range [0,max] */ + result_type distribute(std::uint32_t max) override + { + if (max == 0xffffffffull) + { + return m_Generator(); + } + + // Rejection sampling: + // 1. Divide the range of uint32 into blocks of max+1 + // numbers each, with rem numbers left over. + // 2. Generate a random u32. If it belongs to a block, + // mod it into the range [0,max] and accept it. + // 3. If it fell into the range of rem leftover numbers, + // reject it and go back to step 2. + std::uint32_t m = max + 1; + std::uint32_t rem = -m % m; // = 2^32 mod m + while (true) { + auto n = m_Generator(); + if (n >= rem) + return n % m; + } + } + + std::int32_t distribute(std::int32_t from, std::int32_t to) override { + // Don't use uniform_int_distribution--the algorithm used isn't + // portable between stdlibs. + // We do from + (rand int in [0, to-from]). The miracle of two's + // complement let's us do this all in unsigned and then just cast + // back. auto ufrom = static_cast(from); auto uto = static_cast(to); auto urange = uto - ufrom; - auto ures = ufrom + GetRandomUnsigned(urange, *this); + auto ures = ufrom + distribute(urange); return static_cast(ures); } @@ -60,110 +117,41 @@ class RngWrapper : TGenerator m_Generator; }; -Rand::RNG& Rand::Service::rng() -{ - static RngWrapper rng{}; - return rng; -} - namespace { -/** Gets a random number uniformly distributed in [0, U32_MAX] */ -uint32_t GetRandomU32(Rand::RNG& rng) { return rng(); } - -//int32_t rng_lock_value = 0; -//bool rng_locked= false; + Rand::RngPtr rng = std::make_unique>(); } -/** Generate a random number in the range [0,max] */ -static uint32_t GetRandomUnsigned(uint32_t max, Rand::RNG& rng) +Rand::RngPtr Rand::ExchangeRNG(RngPtr newRng) { - if (max == 0xffffffffull) return GetRandomU32(rng); - - // Rejection sampling: - // 1. Divide the range of uint32 into blocks of max+1 - // numbers each, with rem numbers left over. - // 2. Generate a random u32. If it belongs to a block, - // mod it into the range [0,max] and accept it. - // 3. If it fell into the range of rem leftover numbers, - // reject it and go back to step 2. - uint32_t m = max + 1; - uint32_t rem = -m % m; // = 2^32 mod m - while (true) { - uint32_t n = GetRandomU32(rng); - if (n >= rem) - return n % m; - } + assert(newRng && "rng must not be nullptr"); + return std::exchange(rng, std::move(newRng)); } -int32_t Rand::GetRandomNumber(int32_t from, int32_t to, RNG& rng) { - assert(from <= to); - //if (rng_locked) { - // return Utils::Clamp(rng_lock_value, from, to); - //} - // Don't use uniform_int_distribution--the algorithm used isn't - // portable between stdlibs. - // We do from + (rand int in [0, to-from]). The miracle of two's - // complement let's us do this all in unsigned and then just cast - // back. - return rng(from, to); +std::int32_t Rand::GetRandomNumber(std::int32_t from, std::int32_t to) { + assert(from <= to && "from must be less-equal than to"); + return GetRNG().distribute(from, to); } -Rand::RNG& Rand::GetRNG(RNG& rng) { - return rng; +Rand::RNG& Rand::GetRNG() { + assert(rng && "rng is empty"); + return *rng; } -bool Rand::ChanceOf(int32_t n, int32_t m, RNG& rng) { +bool Rand::ChanceOf(std::int32_t n, std::int32_t m) { assert(n >= 0 && m > 0); - return GetRandomNumber(1, m, rng) <= n; + return GetRandomNumber(1, m) <= n; } -bool Rand::PercentChance(float rate, RNG& rng) { +bool Rand::PercentChance(float rate) { constexpr auto scale = 0x1000000; - return GetRandomNumber(0, scale-1, rng) < int32_t(rate * scale); + return GetRandomNumber(0, scale-1) < static_cast(rate * scale); } -bool Rand::PercentChance(int rate, RNG& rng) { - return GetRandomNumber(0, 99, rng) < rate; +bool Rand::PercentChance(int rate) { + return GetRandomNumber(0, 99) < rate; } -void Rand::SeedRandomNumberGenerator(int32_t seed, RNG& rng) { - rng.seed(seed); +void Rand::SeedRandomNumberGenerator(Seed_t seed) { + GetRNG().seed(seed); Output::Debug("Seeded the RNG with {}.", seed); } - -//void Rand::LockRandom(int32_t value) { -// rng_locked = true; -// rng_lock_value = value; -//} -// -//void Rand::UnlockRandom() { -// rng_locked = false; -//} -// -//std::pair Rand::GetRandomLocked() { -// return { rng_locked, rng_lock_value }; -//} - -//Rand::LockGuard::LockGuard(int32_t lock_value, bool locked) { -// auto p = GetRandomLocked(); -// _prev_locked = p.first; -// _prev_lock_value = p.second; -// _active = true; -// -// if (locked) { -// LockRandom(lock_value); -// } else { -// UnlockRandom(); -// } -//} -// -//void Rand::LockGuard::Release() noexcept { -// if (Enabled()) { -// if (_prev_locked) { -// LockRandom(_prev_lock_value); -// } else { -// UnlockRandom(); -// } -// Dismiss(); -// } -//} diff --git a/src/rand.h b/src/rand.h index f102cf03b4..95b92706ca 100644 --- a/src/rand.h +++ b/src/rand.h @@ -19,76 +19,73 @@ #define EP_RANDOM_H #include -#include -#include #include #include -#include "system.h" #include "string_view.h" #include "span.h" -#include "utils.h" namespace Rand { -/** - * The random number generator object to use - */ -class AbstractRngWrapper +using Seed_t = std::uint32_t; + +class AbstractRNGWrapper { public: using result_type = std::uint32_t; - virtual ~AbstractRngWrapper() noexcept = default; + virtual ~AbstractRNGWrapper() noexcept = default; + + AbstractRNGWrapper(const AbstractRNGWrapper&) = delete; + AbstractRNGWrapper& operator =(const AbstractRNGWrapper&) = delete; + AbstractRNGWrapper(AbstractRNGWrapper&&) = delete; + AbstractRNGWrapper& operator =(AbstractRNGWrapper&&) = delete; - AbstractRngWrapper(const AbstractRngWrapper&) = delete; - AbstractRngWrapper& operator =(const AbstractRngWrapper&) = delete; - AbstractRngWrapper(AbstractRngWrapper&&) = delete; - AbstractRngWrapper& operator =(AbstractRngWrapper&&) = delete; - virtual result_type operator()() = 0; - virtual std::int32_t operator()(std::int32_t from, std::int32_t to) = 0; - virtual void seed(result_type seed) = 0; + virtual result_type distribute(std::uint32_t max) = 0; + virtual std::int32_t distribute(std::int32_t from, std::int32_t to) = 0; + virtual void seed(Seed_t seed) = 0; + static result_type min() { return std::numeric_limits::min(); } static result_type max() { return std::numeric_limits::max(); } protected: - AbstractRngWrapper() noexcept = default; + AbstractRNGWrapper() noexcept = default; }; -class ConstNumberGeneratorWrapper : - public AbstractRngWrapper +class SequencedRNGWrapper : + public AbstractRNGWrapper { public: - explicit ConstNumberGeneratorWrapper(std::int64_t value) noexcept : - m_Value{ value } - { - } - - result_type operator()() override - { - return static_cast(Utils::Clamp( m_Value, std::numeric_limits::min(), std::numeric_limits::max())); - } - - std::int32_t operator()(std::int32_t from, std::int32_t to) override - { - return Utils::Clamp(static_cast(m_Value), from, to); - } - - void seed(result_type seed) override + template , result_type>>> + explicit SequencedRNGWrapper(TArgs ... args) noexcept : + AbstractRNGWrapper{}, + m_Sequence{ static_cast(args)... } { + assert(!std::empty(m_Sequence) && "Empty sequence is not allowed."); } + result_type operator()() override; + + result_type distribute(std::uint32_t max) override; + std::int32_t distribute(std::int32_t from, std::int32_t to) override; + + void seed(Seed_t seed) override; + private: - std::int64_t m_Value; -}; - -using RNG = AbstractRngWrapper; + std::size_t m_NextIndex = 0; + std::vector m_Sequence; -struct Service -{ - static RNG& rng(); + result_type pickNext(); }; + /** + * The random number generator object to use + */ +using RNG = AbstractRNGWrapper; +using RngPtr = std::unique_ptr; + +RngPtr ExchangeRNG(RngPtr newRng); + /** * Gets a random number in the inclusive range from - to. * @@ -96,14 +93,14 @@ struct Service * @param to Interval end * @return Random number in inclusive interval */ -int32_t GetRandomNumber(int32_t from, int32_t to, RNG& generator = Service::rng()); +std::int32_t GetRandomNumber(std::int32_t from, std::int32_t to); /** * Gets the seeded Random Number Generator (RNG). * * @return the random number generator */ -RNG& GetRNG(RNG& generator = Service::rng()); +RNG& GetRNG(); /** * Has an n/m chance of returning true. If n>m, always returns true. @@ -112,7 +109,7 @@ RNG& GetRNG(RNG& generator = Service::rng()); * @param m denominator of the probability (positive) * @return true with probability n/m, false with probability 1-n/m */ -bool ChanceOf(int32_t n, int32_t m, RNG& generator = Service::rng()); +bool ChanceOf(std::int32_t n, std::int32_t m); /** * Rolls a random number in [0.0f, 1.0f) returns true if it's less than rate. @@ -120,7 +117,7 @@ bool ChanceOf(int32_t n, int32_t m, RNG& generator = Service::rng()); * @param rate a value in [0.0f, 1.0f]. Values out of this range are clamped. * @return true with probability rate. */ -bool PercentChance(float rate, RNG& generator = Service::rng()); +bool PercentChance(float rate); /** * Rolls a random number in [0, 99] and returns true if it's less than rate. @@ -128,94 +125,56 @@ bool PercentChance(float rate, RNG& generator = Service::rng()); * @param rate a value in [0, 100]. Values out of this range are clamped. * @return true with probability rate. */ -bool PercentChance(int rate, RNG& generator = Service::rng()); -bool PercentChance(long rate, RNG& generator = Service::rng()); +bool PercentChance(int rate); +bool PercentChance(long rate); /** * Seeds the RNG used by GetRandomNumber and ChanceOf. * * @param seed Seed to use */ -void SeedRandomNumberGenerator(int32_t seed, RNG& generator = Service::rng()); - -/** - * Forces GetRandomNumber() and all dervative functions to return a fixed value. - * Useful for testing. - * - * @param lock_value the value to set. A calls to GetRandomNumber(a, b) will return clamp(lock_value, a, b) - * @post All calls to GetRandomNumber(a, b) will return clamp(lock_value, a, b) - */ -//void LockRandom(int32_t lock_value); -// -///** -// * Disables locked random number and returns RNG to original state. -// * @post All calls to GetRandomNumber(a, b) will return random values. -// */ -//void UnlockRandom(); -// -///** -// * Retrive whether random numbers are locked and if so, which value they are locked to. -// * @return whether or not random numbers are locked and if so, to what value. -// */ -//std::pair GetRandomLocked(); - -/** An RAII guard which fixes the rng while active and resets on destruction */ -//class LockGuard { -//public: -// /** -// * Store current state and set locked state -// * @param lock_value The rng value to fix to -// * @param locked Whether to fix or reset -// */ -// LockGuard(int32_t lock_value, bool locked = true); -// -// LockGuard(const LockGuard&) = delete; -// LockGuard& operator=(const LockGuard&) = delete; -// -// /** Move other LockGuard to this */ -// LockGuard(LockGuard&& o) noexcept; -// LockGuard& operator=(LockGuard&&) = delete; -// -// /** Calls Release() */ -// ~LockGuard(); -// -// /** If Enabled(), returns the rng locked state to what it was */ -// void Release() noexcept; -// -// /** Disables the LockGuard leaving the rng state as is */ -// void Dismiss(); -// -// /** @return whether the guard is enabled and will release on destruction */ -// bool Enabled() const; -//private: -// int32_t _prev_lock_value = 0; -// bool _prev_locked = false; -// bool _active = false; -//}; -// -//inline bool PercentChance(long rate) { -// return PercentChance(static_cast(rate)); -//} -// -//inline LockGuard::LockGuard(LockGuard&& o) noexcept { -// std::swap(_prev_lock_value, o._prev_lock_value); -// std::swap(_prev_locked, o._prev_locked); -// std::swap(_active, o._active); -//} -// -//inline LockGuard::~LockGuard() { -// Release(); -//} -// -//inline void LockGuard::Dismiss() { -// _active = false; -//} -// -//inline bool LockGuard::Enabled() const { -// return _active; -//} +void SeedRandomNumberGenerator(Seed_t seed); +namespace test +{ + template >> + class ScopedRNGExchange + { + public: + template >> + explicit ScopedRNGExchange(TArgs&&... args) : + m_PreviousRNG{ Rand::ExchangeRNG(std::make_unique(std::forward(args)...)) } + { + } + + ~ScopedRNGExchange() noexcept + { + if (m_PreviousRNG) + { + ExchangeRNG(std::move(m_PreviousRNG)); + } + } + + ScopedRNGExchange(const ScopedRNGExchange&) = delete; + ScopedRNGExchange& operator =(const ScopedRNGExchange&) = delete; + + ScopedRNGExchange(ScopedRNGExchange&&) noexcept = default; + ScopedRNGExchange& operator =(ScopedRNGExchange&&) noexcept = default; + + private: + RngPtr m_PreviousRNG; + }; + + template + std::enable_if_t< + std::is_base_of_v && std::is_constructible_v, + ScopedRNGExchange // actual return type + > + makeScopedRNGExchange(TArgs&&... args) + { + return ScopedRNGExchange{ std::forward(args)... }; + } +} } // namespace Rand - #endif diff --git a/tests/algo.cpp b/tests/algo.cpp index 8f8ef64ff6..855782f7d0 100644 --- a/tests/algo.cpp +++ b/tests/algo.cpp @@ -121,27 +121,27 @@ TEST_CASE("Variance") { SUBCASE(">0") { SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); for (int var = 1; var <= 10; ++var) { - REQUIRE_EQ(100 + 5 * var, Algo::VarianceAdjustEffect(100, var, gen)); + REQUIRE_EQ(100 + 5 * var, Algo::VarianceAdjustEffect(100, var)); } } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); for (int var = 1; var <= 10; ++var) { - REQUIRE_EQ(100 - 5 * var, Algo::VarianceAdjustEffect(100, var, gen)); + REQUIRE_EQ(100 - 5 * var, Algo::VarianceAdjustEffect(100, var)); } } } SUBCASE("one") { SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); - REQUIRE_EQ(2, Algo::VarianceAdjustEffect(1, 10, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); + REQUIRE_EQ(2, Algo::VarianceAdjustEffect(1, 10)); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); - REQUIRE_EQ(1, Algo::VarianceAdjustEffect(1, 10, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); + REQUIRE_EQ(1, Algo::VarianceAdjustEffect(1, 10)); } } @@ -159,14 +159,14 @@ TEST_CASE("Variance") { REQUIRE(Player::IsLegacy()); SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0, gen)); - REQUIRE_EQ(1, Algo::VarianceAdjustEffect(0, 1, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); + REQUIRE_EQ(1, Algo::VarianceAdjustEffect(0, 1)); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0, gen)); - REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 1, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 0)); + REQUIRE_EQ(0, Algo::VarianceAdjustEffect(0, 1)); } } } @@ -909,12 +909,12 @@ TEST_CASE("NormalAttackVariance") { REQUIRE_EQ(target.GetDef(), 90); SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); - REQUIRE_EQ(46, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); + REQUIRE_EQ(46, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); - REQUIRE_EQ(31, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); + REQUIRE_EQ(31, Algo::CalcNormalAttackEffect(source, target, Game_Battler::WeaponAll, false, true, lcf::rpg::System::BattleCondition_none, false)); } } } @@ -928,14 +928,14 @@ static void testSkillVar(Game_Battler& source, Game_Battler& target, int var, in skill2->scope = lcf::rpg::Skill::Scope_ally; SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); - REQUIRE_EQ(dmg_high, Algo::CalcSkillEffect(source, target, *skill1, true, gen)); - REQUIRE_EQ(heal_high, Algo::CalcSkillEffect(source, target, *skill2, true, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); + REQUIRE_EQ(dmg_high, Algo::CalcSkillEffect(source, target, *skill1, true)); + REQUIRE_EQ(heal_high, Algo::CalcSkillEffect(source, target, *skill2, true)); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); - REQUIRE_EQ(dmg_low, Algo::CalcSkillEffect(source, target, *skill1, true, gen)); - REQUIRE_EQ(heal_low, Algo::CalcSkillEffect(source, target, *skill2, true, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); + REQUIRE_EQ(dmg_low, Algo::CalcSkillEffect(source, target, *skill1, true)); + REQUIRE_EQ(heal_low, Algo::CalcSkillEffect(source, target, *skill2, true)); } } @@ -965,12 +965,12 @@ TEST_CASE("SelfDestructVariance") { auto source = MakeStatEnemy(1, 150, 0, 0); SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MAX); - REQUIRE_EQ(120, Algo::CalcSelfDestructEffect(source, target, true, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); + REQUIRE_EQ(120, Algo::CalcSelfDestructEffect(source, target, true)); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper gen(INT32_MIN); - REQUIRE_EQ(80, Algo::CalcSelfDestructEffect(source, target, true, gen)); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::min()); + REQUIRE_EQ(80, Algo::CalcSelfDestructEffect(source, target, true)); } } diff --git a/tests/autobattle.cpp b/tests/autobattle.cpp index 4cf27b28a6..7d64ca67c5 100644 --- a/tests/autobattle.cpp +++ b/tests/autobattle.cpp @@ -28,11 +28,11 @@ decltype(auto) MakeActor(int id, int hp, int sp, int atk, int def, int spi, int TEST_SUITE_BEGIN("Autobattle"); -static void testNormalAttack(const Game_Actor& source, const Game_Battler& target, double v0, double v1, double v2, double v3, Rand::RNG& rng) { - REQUIRE(doctest::Approx(v0) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, true, rng)); - REQUIRE(doctest::Approx(v1) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, false, rng)); - REQUIRE(doctest::Approx(v2) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, true, rng)); - REQUIRE(doctest::Approx(v3) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, false, rng)); +static void testNormalAttack(const Game_Actor& source, const Game_Battler& target, double v0, double v1, double v2, double v3) { + REQUIRE(doctest::Approx(v0) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, true)); + REQUIRE(doctest::Approx(v1) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, false, false)); + REQUIRE(doctest::Approx(v2) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, true)); + REQUIRE(doctest::Approx(v3) == AutoBattle::CalcNormalAttackAutoBattleTargetRank(source, target, Game_Battler::WeaponAll, lcf::rpg::System::BattleCondition_none, true, false)); } TEST_CASE("NormalAttackTargetRank") { @@ -46,12 +46,12 @@ TEST_CASE("NormalAttackTargetRank") { REQUIRE_EQ(target.GetDef(), 90); SUBCASE("max") { - Rand::ConstNumberGeneratorWrapper rng(INT32_MAX); - testNormalAttack(source, target, 1.131, 1.131, 1.158, 1.158, rng); + auto scoped = Rand::test::makeScopedRNGExchange(static_cast(std::numeric_limits::max())); + testNormalAttack(source, target, 1.131, 1.131, 1.158, 1.158); } SUBCASE("min") { - Rand::ConstNumberGeneratorWrapper rng(INT32_MIN); - testNormalAttack(source, target, 0.141, 0.141, 0.114, 0.114, rng); + auto scoped = Rand::test::makeScopedRNGExchange(static_cast(std::numeric_limits::min())); + testNormalAttack(source, target, 0.141, 0.141, 0.114, 0.114); } } } diff --git a/tests/enemyai.cpp b/tests/enemyai.cpp index 5fbcffc319..5bc1f07e0e 100644 --- a/tests/enemyai.cpp +++ b/tests/enemyai.cpp @@ -244,16 +244,16 @@ static void testActionType(int start, int end, Game_BattleAlgorithm::Type type_c bool bugs = true; CAPTURE(bugs); - Rand::ConstNumberGeneratorWrapper gen(rng); + auto scoped = Rand::test::makeScopedRNGExchange(rng); bugs = true; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); REQUIRE(source.GetBattleAlgorithm()); REQUIRE_EQ(static_cast(type_compat), static_cast(source.GetBattleAlgorithm()->GetType())); bugs = false; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); REQUIRE(source.GetBattleAlgorithm()); REQUIRE_EQ(static_cast(type_improved), static_cast(source.GetBattleAlgorithm()->GetType())); } @@ -265,15 +265,15 @@ static void testActionNullptr(int start, int end, Game_Enemy& source) { bool bugs = true; CAPTURE(bugs); - Rand::ConstNumberGeneratorWrapper gen(rng); + auto scoped = Rand::test::makeScopedRNGExchange(rng); bugs = true; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); REQUIRE_FALSE(source.GetBattleAlgorithm()); bugs = false; source.SetBattleAlgorithm(nullptr); - EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs, gen); + EnemyAi::SelectEnemyAiActionRpgRtCompat(source, bugs); REQUIRE_FALSE(source.GetBattleAlgorithm()); } } diff --git a/tests/game_player_input.cpp b/tests/game_player_input.cpp index 0f2179b35d..6dc121bdae 100644 --- a/tests/game_player_input.cpp +++ b/tests/game_player_input.cpp @@ -67,7 +67,7 @@ static void testMove(bool success, int input_dir, int dir, int x, int y, int dx, const auto ty = y + dy; // Force RNG to always fail to generate a random encounter - //Rand::LockGuard lk(INT32_MAX); + auto scoped = Rand::test::makeScopedRNGExchange(std::numeric_limits::max()); ForceUpdate(ch); if (success) { int enc_steps = (cheat && debug) ? 0 : 100; diff --git a/tests/rand.cpp b/tests/rand.cpp index 71575d9211..de2bca03b3 100644 --- a/tests/rand.cpp +++ b/tests/rand.cpp @@ -85,10 +85,9 @@ TEST_CASE("GetRandomNumber") { //} static void testGetRandomNumberFixed(int32_t a, int32_t b, int32_t fix, int32_t result) { - //Rand::LockGuard fg(fix); + auto scoped = Rand::test::makeScopedRNGExchange(static_cast(fix)); for (int i = 0; i < 10; ++i) { - Rand::ConstNumberGeneratorWrapper rng(fix); - auto x = GetRandomNumber(a, b, rng); + auto x = Rand::GetRandomNumber(a, b); REQUIRE_EQ(x, result); } } From 35bd5211b82697b37d204e5d9f56795a38a115d6 Mon Sep 17 00:00:00 2001 From: DNKpp Date: Wed, 17 Feb 2021 14:25:48 +0100 Subject: [PATCH 3/3] add test cases for new rng interface Signed-off-by: DNKpp --- src/rand.h | 11 +++-- tests/rand.cpp | 127 +++++++++++++++++++++++-------------------------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/src/rand.h b/src/rand.h index 95b92706ca..3e9f5bcbdc 100644 --- a/src/rand.h +++ b/src/rand.h @@ -56,13 +56,18 @@ class SequencedRNGWrapper : public AbstractRNGWrapper { public: - template , result_type>>> - explicit SequencedRNGWrapper(TArgs ... args) noexcept : + explicit SequencedRNGWrapper(std::vector seq) noexcept : AbstractRNGWrapper{}, - m_Sequence{ static_cast(args)... } + m_Sequence{ std::move(seq) } { assert(!std::empty(m_Sequence) && "Empty sequence is not allowed."); } + + template , result_type>>> + explicit SequencedRNGWrapper(TArgs ... args) noexcept : + SequencedRNGWrapper{ std::vector{ static_cast(args)... } } + { + } result_type operator()() override; diff --git a/tests/rand.cpp b/tests/rand.cpp index de2bca03b3..4200f40c1a 100644 --- a/tests/rand.cpp +++ b/tests/rand.cpp @@ -19,73 +19,66 @@ TEST_CASE("GetRandomNumber") { testGetRandomNumber(-5, -2); } -//TEST_CASE("Lock") { -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -// -// Rand::LockRandom(55); -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, 55); -// -// Rand::UnlockRandom(); -// -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -//} - -//TEST_CASE("LockGuardNest") { -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -// { -// Rand::LockGuard fg(32); -// REQUIRE(fg.Enabled()); -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, 32); -// -// { -// Rand::LockGuard fg(0, false); -// REQUIRE(fg.Enabled()); -// -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -// } -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, 32); -// } -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -//} -// -//TEST_CASE("LockGuardRelease") { -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -// -// Rand::LockGuard fg(-16); -// REQUIRE(fg.Enabled()); -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, -16); -// -// fg.Release(); -// -// REQUIRE_FALSE(Rand::GetRandomLocked().first); -// REQUIRE_FALSE(fg.Enabled()); -//} -// -//TEST_CASE("LockGuardDismiss") { -// Rand::LockGuard fg(INT32_MAX); -// REQUIRE(fg.Enabled()); -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); -// -// fg.Dismiss(); -// -// REQUIRE(Rand::GetRandomLocked().first); -// REQUIRE_EQ(Rand::GetRandomLocked().second, INT32_MAX); -// -// Rand::UnlockRandom(); -//} +static void testSequenceGenerator(const std::vector& seq, Rand::SequencedRNGWrapper& rng, std::size_t iterations = 1) +{ + for (std::size_t i = 0; i < iterations; ++i) + { + for (auto value : seq) + { + REQUIRE(rng() == value); + } + } +} + +TEST_CASE("Single-Element Sequence") { + + std::vector seq{ 42 }; + Rand::SequencedRNGWrapper seqRNG(seq); + + testSequenceGenerator(seq, seqRNG, 3); +} + +TEST_CASE("Multi-Element Sequence") { + + std::vector seq{ 42, 1337, 42, 3, 1, 3 }; + Rand::SequencedRNGWrapper seqRNG(seq); + + testSequenceGenerator(seq, seqRNG, 3); +} + +TEST_CASE("GetRNG") { + + REQUIRE(&Rand::GetRNG() != nullptr); +} + +TEST_CASE("ExchangeRNG") { + + auto* preRNGPtr = &Rand::GetRNG(); + std::vector seq{ 42, 1337, 42, 3, 1, 3 }; + auto seqRNG = std::make_unique(seq); + auto* seqRNGPtr = seqRNG.get(); + auto prevRNG = Rand::ExchangeRNG(std::move(seqRNG)); + + REQUIRE(preRNGPtr == prevRNG.get()); + + REQUIRE(seqRNGPtr == &Rand::GetRNG()); +} + +TEST_CASE("ScopedRNGExchange") { + + auto* preRNGPtr = &Rand::GetRNG(); + + { + auto scoped = Rand::test::makeScopedRNGExchange(1); + + REQUIRE(preRNGPtr != &Rand::GetRNG()); + } + + REQUIRE(preRNGPtr == &Rand::GetRNG()); +} static void testGetRandomNumberFixed(int32_t a, int32_t b, int32_t fix, int32_t result) { - auto scoped = Rand::test::makeScopedRNGExchange(static_cast(fix)); + auto scoped = Rand::test::makeScopedRNGExchange(fix); for (int i = 0; i < 10; ++i) { auto x = Rand::GetRandomNumber(a, b); REQUIRE_EQ(x, result); @@ -96,8 +89,8 @@ TEST_CASE("GetRandomNumberFixed") { testGetRandomNumberFixed(-10, 12, 5, 5); testGetRandomNumberFixed(-10, 12, 12, 12); testGetRandomNumberFixed(-10, 12, 55, 12); - testGetRandomNumberFixed(-10, 12, INT32_MIN, -10); - testGetRandomNumberFixed(-10, 12, INT32_MAX, 12); + testGetRandomNumberFixed(-10, 12, std::numeric_limits::min(), -10); + testGetRandomNumberFixed(-10, 12, std::numeric_limits::max(), 12); } TEST_SUITE_END();