diff --git a/include/chaiscript/extras/random.hpp b/include/chaiscript/extras/random.hpp new file mode 100644 index 0000000..5c52ea8 --- /dev/null +++ b/include/chaiscript/extras/random.hpp @@ -0,0 +1,67 @@ +#ifndef CHAISCRIPT_EXTRAS_RANDOM_HPP_ +#define CHAISCRIPT_EXTRAS_RANDOM_HPP_ + +#include +#include + +#include + +namespace chaiscript { + namespace extras { + namespace random { + class MT19937_Engine { + public: + MT19937_Engine() + : m_engine(std::random_device{}()) + { + } + + explicit MT19937_Engine(unsigned int t_seed) + : m_engine(t_seed) + { + } + + MT19937_Engine(const MT19937_Engine &) = default; + MT19937_Engine &operator=(const MT19937_Engine &) = default; + + void seed(unsigned int t_seed) { + m_engine.seed(t_seed); + } + + int random_int(int t_min, int t_max) { + std::uniform_int_distribution dist(t_min, t_max); + return dist(m_engine); + } + + double random_float(double t_min, double t_max) { + std::uniform_real_distribution dist(t_min, t_max); + return dist(m_engine); + } + + private: + std::mt19937 m_engine; + }; + + ModulePtr bootstrap(ModulePtr m = std::make_shared()) + { + m->add(user_type(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); + m->add(constructor(), "MT19937_Engine"); + + m->add(fun(&MT19937_Engine::seed), "seed"); + m->add(fun(&MT19937_Engine::random_int), "random_int"); + m->add(fun(&MT19937_Engine::random_float), "random_float"); + + m->add(fun([](MT19937_Engine &t_lhs, const MT19937_Engine &t_rhs) -> MT19937_Engine & { + t_lhs = t_rhs; + return t_lhs; + }), "="); + + return m; + } + } + } +} + +#endif /* CHAISCRIPT_EXTRAS_RANDOM_HPP_ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8416adb..a86db28 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,3 +50,9 @@ add_executable(string_methods_test string_methods.cpp) target_link_libraries(string_methods_test ${LIBS}) target_include_directories(string_methods_test PUBLIC "${chaiscript_SOURCE_DIR}/include") ADD_CATCH_TESTS(string_methods_test) + +# Random +add_executable(random_test random.cpp) +target_link_libraries(random_test ${LIBS}) +target_include_directories(random_test PUBLIC "${chaiscript_SOURCE_DIR}/include") +ADD_CATCH_TESTS(random_test) diff --git a/tests/math.cpp b/tests/math.cpp index 63eafcd..7efebf0 100644 --- a/tests/math.cpp +++ b/tests/math.cpp @@ -1,4 +1,5 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#define CATCH_CONFIG_NO_POSIX_SIGNALS //#include #include "catch.hpp" diff --git a/tests/random.cpp b/tests/random.cpp new file mode 100644 index 0000000..dfb355c --- /dev/null +++ b/tests/random.cpp @@ -0,0 +1,75 @@ +#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_NO_POSIX_SIGNALS +#include "catch.hpp" + +#include +#include +#include "../include/chaiscript/extras/random.hpp" + +TEST_CASE( "MT19937_Engine type is usable in ChaiScript", "[random]" ) { + auto randomlib = chaiscript::extras::random::bootstrap(); + + auto stdlib = chaiscript::Std_Lib::library(); + chaiscript::ChaiScript chai(stdlib); + chai.add(randomlib); + + SECTION("default constructor creates a valid engine") { + const auto result = chai.eval("var rng = MT19937_Engine(); rng.random_int(0, 100)"); + CHECK(result >= 0); + CHECK(result <= 100); + } + + SECTION("seeded constructor creates a valid engine") { + const auto result = chai.eval("var rng = MT19937_Engine(42u); rng.random_int(0, 100)"); + CHECK(result >= 0); + CHECK(result <= 100); + } + + SECTION("seed produces deterministic sequences") { + chai.eval("var rng = MT19937_Engine()"); + chai.eval("rng.seed(42u)"); + const auto first = chai.eval("rng.random_int(0, 1000)"); + chai.eval("rng.seed(42u)"); + const auto second = chai.eval("rng.random_int(0, 1000)"); + CHECK(first == second); + } + + SECTION("copy constructor copies engine state") { + chai.eval("var rng = MT19937_Engine(99u)"); + chai.eval("var rng2 = MT19937_Engine(rng)"); + const auto a = chai.eval("rng.random_int(0, 1000)"); + const auto b = chai.eval("rng2.random_int(0, 1000)"); + CHECK(a == b); + } + + SECTION("assignment copies engine state") { + chai.eval("var rng = MT19937_Engine(77u)"); + chai.eval("var rng2 = MT19937_Engine()"); + chai.eval("rng2 = rng"); + const auto a = chai.eval("rng.random_int(0, 1000)"); + const auto b = chai.eval("rng2.random_int(0, 1000)"); + CHECK(a == b); + } + + SECTION("random_int returns values in range") { + chai.eval("var rng = MT19937_Engine(42u)"); + const auto result = chai.eval("rng.random_int(5, 10)"); + CHECK(result >= 5); + CHECK(result <= 10); + } + + SECTION("random_float returns values in range") { + chai.eval("var rng = MT19937_Engine(42u)"); + const auto result = chai.eval("rng.random_float(0.0, 1.0)"); + CHECK(result >= 0.0); + CHECK(result < 1.0); + } + + SECTION("different seeds produce different sequences") { + chai.eval("var rng1 = MT19937_Engine(1u)"); + chai.eval("var rng2 = MT19937_Engine(2u)"); + const auto a = chai.eval("rng1.random_int(0, 1000000)"); + const auto b = chai.eval("rng2.random_int(0, 1000000)"); + CHECK(a != b); + } +} diff --git a/tests/string_methods.cpp b/tests/string_methods.cpp index 07efc3f..57ee709 100644 --- a/tests/string_methods.cpp +++ b/tests/string_methods.cpp @@ -1,4 +1,5 @@ #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file +#define CATCH_CONFIG_NO_POSIX_SIGNALS #include #include "catch.hpp"