Alien Swarm: Reactive Drop

Alien Swarm: Reactive Drop

Not enough ratings
High Quality and Fast Random Number Generator for Modding and Mapping
By 法克*尤尔*马泽尔
This guide introduces the well-known Mersenne Twister 19937 random number generator to VScripts for Modding. Implemention of MT19937 RNG in Squirrel code is provided. Code is simple and easy to use in your Add-ons / Maps.
   
Award
Favorite
Favorited
Unfavorite
Introduction
Random Number Generators (RNGs) are infrastructure of many games and mods. However, the RandomInt() / RandomFloat() and such provided by Source Engine have their issues: they have bad distribution and potentially have bias on some numbers in the range you specifies. The reason for this is not the topic here. But a brief description is that the engine (probably) uses rand() in <random.h> of C, which is OK for FPS games but not good enough in terms of randomness.

What is Mersenne Twister 19937
Mersenne Twister 19937 is a widely-used pseudo random number generator (PRNG). It is known for its long period of 2^19937-1, high-quality randomness, and efficiency. MT19937 is implemented in many programming languages, including C++. Unfortunately, the game does not provide such an interface to access MT19937. Therefore, we have to implement it using Squirrel.

Advantages of MT19937
  • Extremely Long Period: A period of 2^19937-1 ensures virtually no repetition in practical applications.
  • High Quality of Randomness: Produces well-distributed random numbers, passing rigorous randomness tests like Diehard and TestU01.
  • Fast and Efficient: Despite its complex algorithm, it is optimized for speed and suitable for performance-critical applications.
  • Cross-Platform Consistency: Generates consistent results across platforms when initialized with the same seed.
  • Flexibility: Supports integration with various probability distributions (e.g., uniform, normal).
Implementing MT19937 in Your Add-on / Map VScript
You can put the following code in your *.nut file. it works fine in global scope. Note: DO NOT use variables and functions starting with a '_'.

Source Code
/*------------------------- // High-quality random number generator based on Mersenne Twister 19937 (MT19937) algorithm, // supporting unbiased uniform and normal distributions. // For Squirrel Vscripts of Source Engine games --------------------------*/ //Global variables used by MT19937. DO NOT touch these two variables _mtArray <- array(624); // State array _mtIndex <- 0; /*------------------------- //Followings are all global functions. DO NOT call functions starting with a '_'. --------------------------*/ //Initialize MT19937 engine with the seed. Seed should be an integer. //If you feed MT19937 with the same seed, you will get the same sequence of numbers. //It is advised to call this function only once in your vscpript function RngSetSeed(seed) { _mtArray[0] = seed; for (local i = 1; i < 624; i++) { _mtArray[ i ] = (0x6c078965 * (_mtArray[ i - 1 ] ^ (_mtArray[ i - 1 ] >>> 30)) + i) & 0xffffffff; } _mtIndex = 624; } // Extracts a random number function _RngExtractNumber() { if (_mtIndex >= 624) { _RngTwist(); // Perform a twist operation when index is greater than 624 } local y = _mtArray[_mtIndex++]; y = y ^ (y >>> 11); y = y ^ ((y << 7) & 0x9d2c5680); y = y ^ ((y << 15) & 0xefc60000); y = y ^ (y >>> 18); return y & 0xffffffff; } // Twists the state array to generate new random numbers function _RngTwist() { for (local i = 0; i < 624; i++) { local y = (_mtArray[ i ] & 0x80000000) + (_mtArray[(i + 1) % 624] & 0x7fffffff); _mtArray[ i ] = _mtArray[(i + 397) % 624] ^ (y >>> 1); if (y % 2 != 0) { _mtArray[ i ] = _mtArray[ i ] ^ 0x9908b0df; } } _mtIndex = 0; } // Discards the sign bit to produce a random integer in [0, 0x7fffffff] function _RngRandom() { return _RngExtractNumber() & 0x7fffffff; } // Returns a uniformly distributed random integer in the range [min, max] function RandIntUniformDistribution(min, max) { local range = max - min + 1; local num; // Set the upper limit of random range local securemax = (0x7fffffff - (0x7fffffff % range)); do { num = _RngRandom(); } while (num >= securemax); // Reject numbers that would cause bias return min + (num % range); // Return a uniformly distributed value in [min, max] } // Returns a uniformly distributed float in the range [min, max] // Due to the limited precision of floating-point numbers, two or more adjacent random integers // may produce the same floating-point number after computation. // Therefore, this method cannot generate truly uniformly distributed decimals, especially at the range endpoints. function RandFloatUniformDistribution(min, max) { // Generate a uniformly distributed random decimal in the range [0, 1] local randomFloat = _RngRandom().tofloat() / 0x7fffffff; return min + (randomFloat * (max - min)); // Scale the range to the target range } // Returns a random number following a normal distribution (Gaussian distribution) // mean: The mean of the normal distribution // std_dev: The standard deviation of the normal distribution function RandFloatNormalDistribution(mean, std_dev) { local u1 = _RngRandom().tofloat() / 0x7fffffff; // Uniform float in [0,1] local u2 = _RngRandom().tofloat() / 0x7fffffff; // Uniform float in [0,1] // Box-Muller transform to obtain standard normal distribution // sqrt(), log(), and cos() are math functions. // The code works in AS:RD. If it doesn't work for your source engine game add-on, // try to find the corresponding functions for your game. local z0 = sqrt(-2 * log(u1)) * cos(2 * 3.14159 * u2); // Transform Z0 to have the desired mean and standard deviation return mean + z0 * std_dev; // Transform to the target distribution }

Example Usage

function OnGameplayStart() { RngSetSeed(GetLocalTime()); //This will provide a random seed to the MT19937 engine. //RngSetSeed(12345);// Or you can set the seed for reproducibility for debugging purpos. // Generate a random integer in the range [1, 100] local randInt = RandIntUniformDistribution(1, 100); printl("Random Integer in [1, 100]: " + randInt); // Generate a random float in the range [0.0, 1.0] local randFloat = RandFloatUniformDistribution(0.0, 1.0); printl("Random Float in [0.0, 1.0]: " + randFloat); // Generate a random float in a normal distribution with mean = 10 and std_dev = 2 local randNormal = RandFloatNormalDistribution(10, 2); printl("Random Float with Normal Distribution (mean=10, std_dev=2): " + randNormal); } // Return the current time in seconds function GetLocalTime() { local localTime = {}; LocalTime(localTime); return localTime.dayofyear * 86400 + localTime.hour * 3600 + localTime.minute * 60 + localTime.second; }

Output Example

Random Integer in [1, 100]: 42 Random Float in [0.0, 1.0]: 0.718928 Random Float with Normal Distribution (mean=10, std_dev=2): 8.634879