Implement the same random generator as DF uses for DFHack.

develop
Alexander Gavrilov 2013-09-30 19:46:39 +04:00
parent d7e35c2d23
commit 9e81d27cd1
6 changed files with 454 additions and 0 deletions

@ -717,6 +717,51 @@ Functions:
Checks if the material matches job_material_category or job_item. Checks if the material matches job_material_category or job_item.
Accept dfhack_material_category auto-assign table. 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 C++ function wrappers
===================== =====================

@ -120,6 +120,7 @@ include/modules/Maps.h
include/modules/MapCache.h include/modules/MapCache.h
include/modules/Materials.h include/modules/Materials.h
include/modules/Notes.h include/modules/Notes.h
include/modules/Random.h
include/modules/Screen.h include/modules/Screen.h
include/modules/Translation.h include/modules/Translation.h
include/modules/Vermin.h include/modules/Vermin.h
@ -141,6 +142,7 @@ modules/kitchen.cpp
modules/Maps.cpp modules/Maps.cpp
modules/Materials.cpp modules/Materials.cpp
modules/Notes.cpp modules/Notes.cpp
modules/Random.cpp
modules/Screen.cpp modules/Screen.cpp
modules/Translation.cpp modules/Translation.cpp
modules/Vermin.cpp modules/Vermin.cpp

@ -51,6 +51,7 @@ distribution.
#include "modules/Burrows.h" #include "modules/Burrows.h"
#include "modules/Buildings.h" #include "modules/Buildings.h"
#include "modules/Constructions.h" #include "modules/Constructions.h"
#include "modules/Random.h"
#include "LuaWrapper.h" #include "LuaWrapper.h"
#include "LuaTools.h" #include "LuaTools.h"
@ -92,6 +93,7 @@ using namespace DFHack;
using namespace DFHack::LuaWrapper; using namespace DFHack::LuaWrapper;
using Screen::Pen; using Screen::Pen;
using Random::MersenneRNG;
void dfhack_printerr(lua_State *S, const std::string &str); void dfhack_printerr(lua_State *S, const std::string &str);
@ -1035,6 +1037,150 @@ static void OpenPen(lua_State *state)
lua_pop(state, 1); 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<uint32_t> 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<double> 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 * * Wrappers for C++ API *
************************/ ************************/
@ -2023,6 +2169,7 @@ void OpenDFHackApi(lua_State *state)
OpenPersistent(state); OpenPersistent(state);
OpenMatinfo(state); OpenMatinfo(state);
OpenPen(state); OpenPen(state);
OpenRandom(state);
LuaWrapper::SetFunctionWrappers(state, dfhack_module); LuaWrapper::SetFunctionWrappers(state, dfhack_module);
OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "gui", dfhack_gui_module);

@ -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<class T>
void unitvector(T *p, int size);
template<class T>
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>(float *p, int size);
extern template void MersenneRNG::unitvector<double>(double *p, int size);
}
}
#endif

@ -220,6 +220,12 @@ function dfhack.matinfo:__tostring()
return "<material "..self.type..":"..self.index.." "..self:getToken()..">" return "<material "..self.type..":"..self.index.." "..self:getToken()..">"
end end
dfhack.random.__index = dfhack.random
function dfhack.random:__tostring()
return "<random generator>"
end
function dfhack.maps.getSize() function dfhack.maps.getSize()
local map = df.global.world.map local map = df.global.world.map
return map.x_count_block, map.y_count_block, map.z_count_block return map.x_count_block, map.y_count_block, map.z_count_block

@ -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 <string>
#include <vector>
#include <map>
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 <cmath>
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<MT_LEN;i++)
{
//2010: better init line from wikipedia, ultimate source unknown
mt_buffer[i]=1812433253UL * (mt_buffer[i-step] ^ (mt_buffer[i-step]>>30)) + i;
}
mt_index = 0;
for(int j=0;j<twist_cnt;j++)
twist();
}
void MersenneRNG::init()
{
init(Core::getInstance().p->getTickCount(), 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<class T>
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>(float *p, int size);
template void MersenneRNG::unitvector<double>(double *p, int size);