From 9e81d27cd13c43a7df353b6cd65c0a396eaecb55 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 30 Sep 2013 19:46:39 +0400 Subject: [PATCH] Implement the same random generator as DF uses for DFHack. --- Lua API.rst | 45 ++++++++++ library/CMakeLists.txt | 2 + library/LuaApi.cpp | 147 +++++++++++++++++++++++++++++++ library/include/modules/Random.h | 107 ++++++++++++++++++++++ library/lua/dfhack.lua | 6 ++ library/modules/Random.cpp | 147 +++++++++++++++++++++++++++++++ 6 files changed, 454 insertions(+) create mode 100644 library/include/modules/Random.h create mode 100644 library/modules/Random.cpp diff --git a/Lua API.rst b/Lua API.rst index 6d4e6b51b..42d9953e8 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -717,6 +717,51 @@ Functions: Checks if the material matches job_material_category or job_item. Accept dfhack_material_category auto-assign table. +Random number generation +------------------------ + +* ``dfhack.random.new([seed[,perturb_count]])`` + + Creates a new random number generator object. Without any + arguments, the object is initialized using current time. + Otherwise, the seed must be either a non-negative integer, + or a list of such integers. The second argument may specify + the number of additional randomization steps performed to + improve the initial state. + +* ``rng:init([seed[,perturb_count]])`` + + Re-initializes an already existing random number generator object. + +* ``rng:random([limit])`` + + Returns a random integer. If ``limit`` is specified, the value + is in the range [0, limit); otherwise it uses the whole 32-bit + unsigned integer range. + +* ``rng:drandom()`` + + Returns a random floating-point number in the range [0,1). + +* ``rng:drandom0()`` + + Returns a random floating-point number in the range (0,1). + +* ``rng:drandom1()`` + + Returns a random floating-point number in the range [0,1]. + +* ``rng:unitrandom()`` + + Returns a random floating-point number in the range [-1,1]. + +* ``rng:unitvector([size])`` + + Returns multiple values that form a random vector of length 1, + uniformly distributed over the corresponding sphere surface. + The default size is 3. + + C++ function wrappers ===================== diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 64dafd542..6df69fe27 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -120,6 +120,7 @@ include/modules/Maps.h include/modules/MapCache.h include/modules/Materials.h include/modules/Notes.h +include/modules/Random.h include/modules/Screen.h include/modules/Translation.h include/modules/Vermin.h @@ -141,6 +142,7 @@ modules/kitchen.cpp modules/Maps.cpp modules/Materials.cpp modules/Notes.cpp +modules/Random.cpp modules/Screen.cpp modules/Translation.cpp modules/Vermin.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a4ebbea5b..3b9a8d6d6 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -51,6 +51,7 @@ distribution. #include "modules/Burrows.h" #include "modules/Buildings.h" #include "modules/Constructions.h" +#include "modules/Random.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -92,6 +93,7 @@ using namespace DFHack; using namespace DFHack::LuaWrapper; using Screen::Pen; +using Random::MersenneRNG; void dfhack_printerr(lua_State *S, const std::string &str); @@ -1035,6 +1037,150 @@ static void OpenPen(lua_State *state) lua_pop(state, 1); } +/******************** + * Random generator * + ********************/ + +static int DFHACK_RANDOM_TOKEN = 0; + +static MersenneRNG *check_random_native(lua_State *L, int index) +{ + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN); + + if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2)) + luaL_argerror(L, index, "not a random generator object"); + + lua_pop(L, 2); + + return (MersenneRNG*)lua_touserdata(L, index); +} + +static int dfhack_random_init(lua_State *L) +{ + lua_settop(L, 3); + + MersenneRNG *prng = check_random_native(L, 1); + + if (lua_isnil(L, 2)) + prng->init(); + else + { + std::vector data; + int tcnt = luaL_optint(L, 3, 1); + + if (lua_isnumber(L, 2)) + data.push_back(lua_tounsigned(L, 2)); + else if (lua_istable(L, 2)) + { + int cnt = lua_rawlen(L, 2); + if (cnt <= 0) + luaL_argerror(L, 2, "empty list in dfhack.random.init"); + + for (int i = 1; i <= cnt; i++) + { + lua_rawgeti(L, 2, i); + if (!lua_isnumber(L, -1)) + luaL_argerror(L, 2, "not a number in dfhack.random.init argument"); + + data.push_back(lua_tounsigned(L, -1)); + lua_pop(L, 1); + } + } + else + luaL_argerror(L, 2, "dfhack.random.init argument not number or table"); + + prng->init(data.data(), data.size(), tcnt); + } + + lua_settop(L, 1); + return 1; +} + +static int dfhack_random_new(lua_State *L) +{ + void *pdata = lua_newuserdata(L, sizeof(MersenneRNG)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN); + lua_setmetatable(L, -2); + + new (pdata) MersenneRNG(); + + lua_insert(L, 1); + return dfhack_random_init(L); +} + +static int dfhack_random_random(lua_State *L) +{ + MersenneRNG *prng = check_random_native(L, 1); + + lua_settop(L, 2); + if (lua_gettop(L) < 2 || lua_isnil(L, 2)) + lua_pushunsigned(L, prng->random()); + else + lua_pushunsigned(L, prng->random(luaL_optunsigned(L, 2, 0))); + return 1; +} + +static int dfhack_random_drandom(lua_State *L) +{ + lua_pushnumber(L, check_random_native(L, 1)->drandom()); + return 1; +} +static int dfhack_random_drandom0(lua_State *L) +{ + lua_pushnumber(L, check_random_native(L, 1)->drandom0()); + return 1; +} +static int dfhack_random_drandom1(lua_State *L) +{ + lua_pushnumber(L, check_random_native(L, 1)->drandom1()); + return 1; +} +static int dfhack_random_unitrandom(lua_State *L) +{ + lua_pushnumber(L, check_random_native(L, 1)->unitrandom()); + return 1; +} +static int dfhack_random_unitvector(lua_State *L) +{ + MersenneRNG *prng = check_random_native(L, 1); + int size = luaL_optint(L, 2, 3); + if (size <= 0 || size > 32) + luaL_argerror(L, 2, "vector size must be positive"); + luaL_checkstack(L, size, "not enough stack in dfhack.random.unitvector"); + + std::vector buf(size); + prng->unitvector(buf.data(), size); + + for (int i = 0; i < size; i++) + lua_pushnumber(L, buf[i]); + return size; +} + +static const luaL_Reg dfhack_random_funcs[] = { + { "new", dfhack_random_new }, + { "init", dfhack_random_init }, + { "random", dfhack_random_random }, + { "drandom", dfhack_random_drandom }, + { "drandom0", dfhack_random_drandom0 }, + { "drandom1", dfhack_random_drandom1 }, + { "unitrandom", dfhack_random_unitrandom }, + { "unitvector", dfhack_random_unitvector }, + { NULL, NULL } +}; + +static void OpenRandom(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "random"); + + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_RANDOM_TOKEN); + + luaL_setfuncs(state, dfhack_random_funcs, 0); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -2023,6 +2169,7 @@ void OpenDFHackApi(lua_State *state) OpenPersistent(state); OpenMatinfo(state); OpenPen(state); + OpenRandom(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/include/modules/Random.h b/library/include/modules/Random.h new file mode 100644 index 000000000..c6f9a3f6a --- /dev/null +++ b/library/include/modules/Random.h @@ -0,0 +1,107 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once +#ifndef CL_MOD_RANDOM +#define CL_MOD_RANDOM +/** + * \defgroup grp_translation Translation: DF word tables and name translation/reading + * @ingroup grp_modules + */ + +#include "Export.h" +#include "Module.h" +#include "Types.h" + +#include "DataDefs.h" + +namespace DFHack +{ +namespace Random +{ + class DFHACK_EXPORT MersenneRNG + { + static const unsigned MT_LEN = 624; + + unsigned mt_index; + uint32_t mt_buffer[MT_LEN]; + + void twist(); + void prefill(unsigned step, int twist_cnt); + + public: + void init(const uint32_t *pseed, unsigned cnt, int twist_cnt = 1); + + void init(); // uses time + void init(uint32_t seed, int twist_cnt = 1) { init(&seed, 1, twist_cnt); } + + // [0, 2^32) + uint32_t random() { + if (mt_index >= MT_LEN) twist(); + return mt_buffer[mt_index++]; + } + // [0, limit) + uint32_t random(uint32_t limit) { + return uint32_t(uint64_t(random())*limit >> 32); + } + // (0, 1) + double drandom0() { + return (double(random())+1)/4294967297.0; + } + // [0, 1) + double drandom() { + return double(random())/4294967296.0; + } + // [0, 1] + double drandom1() { + return double(random())/4294967295.0; + } + // [-1, 1] + double unitrandom() { + return drandom1()*2.0 - 1.0; + } + + // Two exact replicas of functions in DF code + int32_t df_trandom(uint32_t max=2147483647LU); + int32_t df_loadtrandom(uint32_t max=2147483647LU); + + template + void unitvector(T *p, int size); + + template + void permute(T *p, int size) { + while(size > 1) + { + int j = random(size--); + T c = p[j]; p[j] = p[size]; p[size] = c; + } + } + }; + + extern template void MersenneRNG::unitvector(float *p, int size); + extern template void MersenneRNG::unitvector(double *p, int size); + +} +} +#endif diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 1354701ff..def48ea88 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -220,6 +220,12 @@ function dfhack.matinfo:__tostring() return "" end +dfhack.random.__index = dfhack.random + +function dfhack.random:__tostring() + return "" +end + function dfhack.maps.getSize() local map = df.global.world.map return map.x_count_block, map.y_count_block, map.z_count_block diff --git a/library/modules/Random.cpp b/library/modules/Random.cpp new file mode 100644 index 000000000..5030d1449 --- /dev/null +++ b/library/modules/Random.cpp @@ -0,0 +1,147 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include +using namespace std; + +#include "modules/Random.h" +#include "VersionInfo.h" +#include "MemAccess.h" +#include "Types.h" +#include "ModuleFactory.h" +#include "Core.h" +#include "Error.h" + +#include + +using namespace DFHack; +using namespace df::enums; + +using namespace DFHack::Random; + +//public domain RNG stuff by Michael Brundage +//modified to be compatible with the version in DF + +#define MT_IA 397 +#define MT_IB (MT_LEN - MT_IA) +#define UPPER_MASK 0x80000000 +#define LOWER_MASK 0x7FFFFFFF +#define MATRIX_A 0x9908B0DF +#define TWIST(b,i,j) ((b)[i] & UPPER_MASK) | ((b)[j] & LOWER_MASK) +#define MAGIC(s) (((s)&1)*MATRIX_A) + +void MersenneRNG::twist() +{ + uint32_t *b = mt_buffer; + uint32_t s; + unsigned i; + + i = 0; + for (; i < MT_IB; i++) { + s = TWIST(b, i, i+1); + b[i] = b[i + MT_IA] ^ (s >> 1) ^ MAGIC(s); + } + for (; i < MT_LEN-1; i++) { + s = TWIST(b, i, i+1); + b[i] = b[i - MT_IB] ^ (s >> 1) ^ MAGIC(s); + } + + s = TWIST(b, MT_LEN-1, 0); + b[MT_LEN-1] = b[MT_IA-1] ^ (s >> 1) ^ MAGIC(s); + + mt_index = 0; +} + +void MersenneRNG::prefill(unsigned step, int twist_cnt) +{ + for(unsigned i=step;i>30)) + i; + } + + mt_index = 0; + + for(int j=0;jgetTickCount(), 20); +} + +void MersenneRNG::init(const uint32_t *pseed, unsigned cnt, int twist_cnt) +{ + memcpy(mt_buffer, pseed, cnt*sizeof(uint32_t)); + + prefill(cnt, twist_cnt); +} + +int32_t MersenneRNG::df_trandom(uint32_t max) +{ + if(max<=1)return 0; + uint32_t seed=random(); + seed=seed%2147483647LU; + seed=seed/((2147483647LU/max)+1); + + return((int32_t)seed); +} + +int32_t MersenneRNG::df_loadtrandom(uint32_t max) +{ + uint32_t seed=random(); + seed=seed%max; + + return((int32_t)seed); +} + +template +void MersenneRNG::unitvector(T *p, int size) +{ + for (;;) + { + T rsqr = 0; + for (int i = 0; i < size; i++) + { + p[i] = (T)unitrandom(); + rsqr += p[i]*p[i]; + } + + if (rsqr > 0 && rsqr <= 1) + { + rsqr = std::sqrt(rsqr); + for (int i = 0; i < size; i++) + p[i] /= rsqr; + break; + } + } +} + +template void MersenneRNG::unitvector(float *p, int size); +template void MersenneRNG::unitvector(double *p, int size);