diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index e8e1b6bc8..a01a19f06 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(confirm confirm.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(createitem createitem.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp) + DFHACK_PLUGIN(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) diff --git a/plugins/cxxrandom.cpp b/plugins/cxxrandom.cpp new file mode 100644 index 000000000..517d460ea --- /dev/null +++ b/plugins/cxxrandom.cpp @@ -0,0 +1,311 @@ +/* Plugin for exporting C++11 random number functionality +*Exports functions for random number generation +*Functions: +- seedRNG(seed) +- rollInt(min, max) +- rollDouble(min, max) +- rollNormal(mean, std_deviation) +- rollBool(chance_for_true) +- resetIndexRolls(string, array_length) --String identifies the instance of SimpleNumDistribution to reset +- rollIndex(string, array_length) --String identifies the instance of SimpleNumDistribution to use + --(Shuffles a vector of indices, Next() increments through then reshuffles when end() is reached) +Author: Josh Cooper +Created: Dec. 13 2017 +Updated: Dec. 21 2017 +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Error.h" +#include "Core.h" +#include "DataFuncs.h" +#include +#include +#include +/* +typedef unsigned short uint16_t; +typedef unsigned long long uint64_t; +typedef long long int64_t; +using uint16_t = unsigned short; +using uint64_t = unsigned long long; +using int64_t = long long;*/ + +using namespace DFHack; +DFHACK_PLUGIN("cxxrandom"); +#define PLUGIN_VERSION 2.0 +color_ostream *cout = nullptr; + +//command_result cxxrandom (color_ostream &out, std::vector & parameters); +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + cout = &out; + /* + commands.push_back(PluginCommand( + "cxxrandom", "C++xx Random Numbers", cxxrandom, false, + " This plugin cannot be used on the commandline.\n" + " Import into a lua script to have access to exported RNG functions:\n" + " local rng = require('plugins.cxxrandom')\n\n" + " Exported Functions:\n" + " rng.ResetIndexRolls(string_ref, total_indices)\n" + " rng.RollIndex(string_ref, total_indices)\n" + " rng.RollInt(min, max)\n" + " rng.RollDouble(min, max)\n" + " rng.RollNormal(mean, std_deviation)\n" + " rng.RollBool(chance_of_true)\n" + " rng.BlastDistributions()\n\n" + ));*/ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + return CR_OK; +} + +#pragma region "EnginesKeeper Stuff" + +class EnginesKeeper +{ +private: + EnginesKeeper() {} + std::unordered_map m_engines; + uint16_t counter = 0; +public: + static EnginesKeeper& Instance() + { + static EnginesKeeper instance; + return instance; + } + uint16_t NewEngine( uint64_t seed ) + { + std::mt19937_64 engine( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); + m_engines[++counter] = engine; + return counter; + } + void DestroyEngine( uint16_t id ) + { + m_engines.erase( id ); + } + void NewSeed( uint16_t id, uint64_t seed ) + { + CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); + m_engines[id].seed( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); + } + std::mt19937_64& RNG( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); + return m_engines[id]; + } +}; + +#pragma endregion + +#pragma region "EngineKeeper Wrappers" + +uint16_t GenerateEngine( uint64_t seed ) +{ + return EnginesKeeper::Instance().NewEngine( seed ); +} + +void DestroyEngine( uint16_t id ) +{ + EnginesKeeper::Instance().DestroyEngine( id ); +} + +void NewSeed( uint16_t id, uint64_t seed ) +{ + EnginesKeeper::Instance().NewSeed( id, seed ); +} + +#pragma endregion + +#pragma region "std Distribution Rollers" + +int rollInt(uint16_t id, int min, int max) +{ + std::uniform_int_distribution ND(min, max); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +double rollDouble(uint16_t id, double min, double max) +{ + std::uniform_real_distribution ND(min, max); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +double rollNormal(uint16_t id, double mean, double stddev) +{ + std::normal_distribution ND(mean, stddev); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +bool rollBool(uint16_t id, float p) +{ + std::bernoulli_distribution ND(p); + return ND(EnginesKeeper::Instance().RNG(id)); +} + +#pragma endregion + +#pragma region "Number Sequence Stuff" + +class NumberSequence +{ +private: + unsigned short m_position = 0; + std::vector m_numbers; +public: + NumberSequence(){} + NumberSequence( int64_t start, int64_t end ) + { + for( int64_t i = start; i <= end; ++i ) + { + m_numbers.push_back( i ); + } + } + void Add( int64_t num ) { m_numbers.push_back( num ); } + void Reset() { m_numbers.clear(); } + int64_t Next() + { + if(m_position >= m_numbers.size()) + { + m_position = 0; + } + return m_numbers[m_position++]; + } + void Shuffle( uint16_t id ) + { + std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) ); + } + void Print() + { + char buffer1[256] = {0}; + char buffer2[256] = {0}; + for( auto v : m_numbers ) + { + sprintf( buffer2, "%s%d", buffer1, v ); + sprintf( buffer1, "%s ", buffer2 ); + } + cout->print( buffer1 ); + } +}; + +class SequenceKeeper +{ +private: + SequenceKeeper() {} + std::unordered_map m_sequences; + uint16_t counter = 0; +public: + static SequenceKeeper& Instance() + { + static SequenceKeeper instance; + return instance; + } + uint16_t MakeNumSequence( int64_t start, int64_t end ) + { + m_sequences[++counter] = NumberSequence( start, end ); + return counter; + } + uint16_t MakeNumSequence() + { + m_sequences[++counter] = NumberSequence(); + return counter; + } + void DestroySequence( uint16_t id ) + { + m_sequences.erase( id ); + } + void AddToSequence( uint16_t id, int64_t num ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + m_sequences[id].Add( num ); + } + void Shuffle( uint16_t id, uint16_t rng_id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + m_sequences[id].Shuffle( rng_id ); + } + int64_t NextInSequence( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + return m_sequences[id].Next(); + } + void PrintSequence( uint16_t id ) + { + CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); + auto seq = m_sequences[id]; + seq.Print(); + } +}; + +#pragma endregion + +#pragma region "Sequence Wrappers" + +uint16_t MakeNumSequence( int64_t start, int64_t end ) +{ + if( start == end ) + { + return SequenceKeeper::Instance().MakeNumSequence(); + } + return SequenceKeeper::Instance().MakeNumSequence( start, end ); +} + +void DestroyNumSequence( uint16_t id ) +{ + SequenceKeeper::Instance().DestroySequence( id ); +} + +void AddToSequence( uint16_t id, int64_t num ) +{ + SequenceKeeper::Instance().AddToSequence( id, num ); +} + +void ShuffleSequence( uint16_t rngID, uint16_t id ) +{ + SequenceKeeper::Instance().Shuffle( id, rngID ); +} + +int64_t NextInSequence( uint16_t id ) +{ + return SequenceKeeper::Instance().NextInSequence( id ); +} + +void DebugSequence( uint16_t id ) +{ + SequenceKeeper::Instance().PrintSequence( id ); +} + +#pragma endregion + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(GenerateEngine), + DFHACK_LUA_FUNCTION(DestroyEngine), + DFHACK_LUA_FUNCTION(NewSeed), + DFHACK_LUA_FUNCTION(rollInt), + DFHACK_LUA_FUNCTION(rollDouble), + DFHACK_LUA_FUNCTION(rollNormal), + DFHACK_LUA_FUNCTION(rollBool), + DFHACK_LUA_FUNCTION(MakeNumSequence), + DFHACK_LUA_FUNCTION(DestroyNumSequence), + DFHACK_LUA_FUNCTION(AddToSequence), + DFHACK_LUA_FUNCTION(ShuffleSequence), + DFHACK_LUA_FUNCTION(NextInSequence), + DFHACK_LUA_FUNCTION(DebugSequence), + DFHACK_LUA_END +}; + diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua new file mode 100644 index 000000000..1521c4c9e --- /dev/null +++ b/plugins/lua/cxxrandom.lua @@ -0,0 +1,214 @@ +local _ENV = mkmodule('plugins.cxxrandom') + +function MakeNewEngine(seed) + if type(seed) == 'number' then + if seed == 0 then + print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).") + end + return GenerateEngine(seed) + elseif type(seed) == 'nil' then + return GenerateEngine(0) + else + error("Argument `seed` must be a number, or nil.") + end +end + +--Class: crng +------------- +crng = {} +function crng:new(engineID, destroyEngineOnDestruction, distrib) + local o = {} + self.__index = self + local idtype = type(engineID) + local flagtype = type(destroyEngineOnDestruction) + + if idtype == 'number' then + o.rngID = engineID + elseif idtype == 'nil' then + o.rngID = GenerateEngine(0) + else + error("Invalid argument type (engineID): " .. tostring(engineID)) + end + + if flagtype ~= 'nil' and flagtype == 'boolean' then + o.destroyid = destroyEngineOnDestruction + elseif flagtype == 'nil' then + o.destroyid = true + else + error("Invalid arugment type (destroyEngineOnDestruction): " .. tostring(destroyEngineOnDestruction)) + end + + if type(distrib) ~= 'nil' then + if type(distrib) == 'table' and type(distrib.next) == 'function' then + o.distrib = distrib + o.distrib.rngID = o.rngID + else + error("Invalid distribution used as an argument. Cannot set this as the number distribution.") + end + end + setmetatable(o,self) + return o +end +--crng destructor - we may need to destroy the engine, the user may be doing it manually though +function crng:__gc() + if self.destroyid then + DestroyEngine(self.rngID) + end +end +function crng:changeSeed(seed) + if type(seed) == 'number' then + if seed == 0 then + print(":WARNING: Seeds equal to 0 are used if no seed is provided. This indicates to cxxrandom.plug.dll that the engine needs to be seeded with the current time.\nRecommendation: use a non-zero value for your seed, or don't provide a seed to use the time since epoch(1969~).") + end + return NewSeed(self.rngID, seed) + elseif type(seed) == 'nil' then + return NewSeed(self.rngID, 0) + else + error("Argument `seed` must be a number, or nil.") + end +end +function crng:setNumDistrib(distrib) + if type(distrib) == 'table' and type(distrib.next) == 'function' then + self.distrib = distrib + self.distrib.rngID = self.rngID + else + error("Invalid distribution used as an argument. Cannot set this as the number distribution.") + end +end +function crng:next() + if type(self.distrib) == 'table' and type(self.distrib.next) == 'function' then + return self.distrib:next(self.rngID) + else + error("crng object does not have a valid number distribution set") + end +end +function crng:shuffle() + if type(self.distrib) == 'table' and type(self.distrib.shuffle) == 'function' then + self.distrib:shuffle(self.rngID) + else + print(":WARNING: No self.distrib.shuffle not found.") + changeSeed(0) + end +end + +--Class: normal_distribution +---------------------------- +normal_distribution = {} +function normal_distribution:new(avg, stddev) + local o = {} + self.__index = self + if type(avg) ~= 'number' or type(stddev) ~= 'number' then + error("Invalid arguments in normal_distribution construction. Average and standard deviation must be numbers.") + end + o.average = avg + o.std_deviation = stddev + setmetatable(o,self) + return o +end +function normal_distribution:next(id) + return rollNormal(id, self.average, self.std_deviation) +end + +--Class: real_distribution +---------------------------- +real_distribution = {} +function real_distribution:new(min, max) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in real_distribution construction. min and max must be numbers.") + end + o.min = min + o.max = max + setmetatable(o,self) + return o +end +function real_distribution:next(id) + return rollDouble(id, self.min, self.max) +end + +--Class: int_distribution +---------------------------- +int_distribution = {} +function int_distribution:new(min, max) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in int_distribution construction. min and max must be numbers.") + end + o.min = min + o.max = max + setmetatable(o,self) + return o +end +function int_distribution:next(id) + return rollInt(id, self.min, self.max) +end + +--Class: bool_distribution +---------------------------- +bool_distribution = {} +function bool_distribution:new(chance) + local o = {} + self.__index = self + if type(min) ~= 'number' or type(max) ~= 'number' then + error("Invalid arguments in bool_distribution construction. min and max must be numbers.") + end + o.p = chance + setmetatable(o,self) + return o +end +function bool_distribution:next(id) + return rollBool(id, self.p) +end + +--Class: num_sequence +---------------------------- +num_sequence = {} +function num_sequence:new(a,b) + local o = {} + self.__index = self + local btype = type(b) + local atype = type(a) + if atype == 'number' and btype == 'number' then + if a == b then + print(":WARNING: You've provided two equal arguments to initialize your sequence with. This is the mechanism used to indicate to cxxrandom.plug.dll that an empty sequence is desired.\nRecommendation: provide no arguments if you wish for an empty sequence.") + end + o.seqID = MakeNumSequence(a, b) + elseif atype == 'table' then + o.seqID = MakeNumSequence(0,0) + for _,v in pairs(a) do + if type(v) ~= 'number' then + error("num_sequence can only be initialized using numbers. " .. tostring(v) .. " is not a number.") + end + AddToSequence(o.seqID, v) + end + elseif atype == "nil" and btype == "nil" then + o.seqID = MakeNumSequence(0,0) + else + error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) + end + print("seqID:"..o.seqID) + setmetatable(o,self) + return o +end +function num_sequence:__gc() + DestroyNumSequence(self.seqID) +end +function num_sequence:add(x) + if type(x) ~= 'number' then + error("Cannot add non-number to num_sequence.") + end + AddToSequence(self.seqID, x) +end +function num_sequence:next() + return NextInSequence(self.seqID) +end +function num_sequence:shuffle() + if self.rngID == 'nil' then + error("Add num_sequence object to crng as distribution, before attempting to shuffle.") + end + ShuffleSequence(self.rngID, self.seqID) +end + +return _ENV \ No newline at end of file