• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Script] PRNG

Level 6
Joined
Feb 10, 2008
Messages
300
No, this isn't a number generator :p

What & why:
It is a simple hack to give mappers the ability to reproduce random numbers.

This was developed to help me with item generation on-the-fly, sort of like action-rpg's do.

Breakdown of what this script does:
  1. Get a random value, called 'game seed'.
  2. Set global seed to a fixed integer value.
  3. Get a couple of integers and store them in an array.
  4. Set global seed to 'game seed'.
  5. Provide a couple of methods to access the "random" integers.

Anyway, the script was designed to be of help with item generation, and since I only expect to generate a few hundred items every game it seems enough with 8000 "random" integers.

If someone wants more (or less) "random" integers, the script supports that too, although at a big (large array overhead?) performance hit. Though, speed shouldn't really be a main concern.

A user can also force the script to only save unique integers (i.e only save integers that aren't in the array already).

At last, here's the code:
JASS:
library PRNG initializer Init requires Table
    globals
        /**
         * PRNG -- version 1.2.1.1 -- by Tukki
         *
         *  REQUIREMENTS:
         *
         *      Table - [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
         *
         *
         *  API:
         *
         *      Note:
         *          - "random" = integer that is random, but persistent over all maps.
         *          - random = integer that is random, but not persistent.
         *
         *      GLOBAL TABLE - methods that use a global array
         *
         *          PRNG.getRandomIndex() --> index integer in range [0, GENERATOR_COUNT]
         *              - returns a random integer that can be used to get the "random" integers,
         *               for the percieved effect of randomness.
         *
         *          PRNG.getIndexInt(integer index) --> integer at 'index' position. 
         *              - returns the "random" integer mapped to 'index'.
         *              - only use natural numbers here (>=0), - will crash the game.
         *
         *      DYNAMIC TABLE - methods that fill a table with values
         *
         *          PRNG.fillTableEx(Table table, integer generatorSeed, integer generatorCount, integer generatorMinInt, integer generatorMaxInt, boolean forceUnique)
         *              - fills the provided table with random values based on the provided parameters.
         *
         *          PRNG.fillTable(Table table, integer generatorSeed, integer generatorCount)
         *              - fills the table using the provided argument and the global options.
         *              - this method calls filLTableEx(..) with the appropriate arguments.
         *
         *
         ****************************************************************************************
         *
         *  BASIC USE
         *
         *      local integer a = PRNG.getRandomIndex() -- get an index
         *      call SetRandomSeed(PRNG.getIndexInt(a)) -- update the game seed
         *      call GenerateRandomItem(a) -- do something that uses GetRandom..(), tie the index value to this item.
         *  
         *      When saving, pair the item id and the generator index.
         *
         *  When loading, to generate the same item:
         *  
         *      local integer a = myLoadedItemIndex
         *      call SetRandomSeed(a) -- update the game seed
         *      call GenerateRandomInt(a) -- should generate the same item as the one you saved.
         *
         *
         ****************************************************************************************
         *
         * Number of integers generated.
         *
         *  Since the system uses an integer array, the amount of 
         *  memory used cannot be changed. However, this value
         *  affects load time due to the amount of calls to GetRandomInt(..).
         */
        private constant integer GENERATOR_COUNT = 8190
        
        /**
         * Forces the output integers to be truly unique.
         * Requires the library Table to be present in the map.
         *
         * Note:
         *  If you have low GENERATOR_MAX_INT and GENERATOR_MIN_INT values,
         *  in combination with a high GENERATOR_COUNT value, setting this to true
         *  might crash the script, use with care. If you suspect that the script
         *  has crashed, check the PRNG.initialized variable.
         *
         *  if PRNG.initialized == true     : system is correctly initialized
         *  if PRNG.initialized == false    : system has crashed somewhere
         *
         *  Use the PRNG.lastGeneratedIntegerIndex value to find at which 
         *  index the script crashed.
         *
         *  PRNG.initialized and PRNG.lastGeneratedIntegerIndex are only available in debug mode.
         */
        private constant boolean GENERATOR_FORCE_ALL_RANDOM = false 
        
        /**
         * The maximum and minimum values of generated ints.
         *
         *  The numbers should be in range [-2147483648,2147483647]
         */
        private constant integer GENERATOR_MAX_INT = 859135902       // Expected to be positive.
        private constant integer GENERATOR_MIN_INT = -859135902      // Expected to be negative.
            
        /**
         * The static value used to generate ints.
         * Keep this constant for all your map versions,
         * otherwise you will end up with different
         * values than you expected.
         *
         *  The number itself should be in range [-2147483648,2147483647]
         */
        private constant integer GENERATOR_SEED = 90826133           
    endglobals

    private keyword ints

    struct PRNG extends array
        debug public static boolean initialized = false
        debug public static integer lastGeneratedIntegerIndex = 0
        
        // STANDARD METHODS
        static method getRandomIndex takes nothing returns integer
            return GetRandomInt(0, GENERATOR_COUNT)
        endmethod
        
        static method getIndexInt takes integer index returns integer
            return ints[index]
        endmethod
        
        // DYNAMIC TABLES
        static method fillTableEx takes Table table, integer generatorSeed, integer generatorCount, integer generatorMinInt, integer generatorMaxInt, boolean forceUnique returns nothing
            local integer gameSeed = GetRandomInt(-2147483648, 2147483647)
            
            local integer v
            local Table unique

            if (forceUnique) then
                set unique = Table.create()
            endif
            
            call SetRandomSeed(generatorSeed)
            debug set PRNG.lastGeneratedIntegerIndex = 0
            
            loop
                set generatorCount = generatorCount- 1
                if (forceUnique) then
                    loop
                        set v = GetRandomInt(generatorMinInt, generatorMaxInt)
                        exitwhen not unique.has(v)
                    endloop
                    
                    set unique[v] = 1
                    set table[generatorCount] = v
                else
                    set table[generatorCount] = GetRandomInt(generatorMinInt, generatorMaxInt)
                endif
                
                debug set PRNG.lastGeneratedIntegerIndex = generatorCount
                exitwhen generatorCount == 0
            endloop
            
            // Update global seed to avoid static seeds.
            call SetRandomSeed(gameSeed)
            
            if (forceUnique) then
                call unique.destroy()
            endif
        endmethod
        
        static method fillTable takes Table table, integer generatorSeed, integer generatorCount returns nothing
            call fillTableEx(table, generatorSeed, generatorCount, GENERATOR_MAX_INT, GENERATOR_MIN_INT, GENERATOR_FORCE_ALL_RANDOM)
        endmethod
    endstruct
    
    globals
        private integer array ints[GENERATOR_COUNT]
    endglobals
    
    private function Init takes nothing returns nothing
        local integer i = 0
        
        static if (GENERATOR_FORCE_ALL_RANDOM) then
            local integer v
            local Table unique = Table.create()
        endif

        // Get a new global seed, since we're overwriting the wc3-generated one.
        local integer gameSeed = GetRandomInt(-214748364, 214748364)
        
        call SetRandomSeed(GENERATOR_SEED)
        loop
            exitwhen i == GENERATOR_COUNT
            static if (GENERATOR_FORCE_ALL_RANDOM) then
                loop
                    set v = GetRandomInt(GENERATOR_MIN_INT, GENERATOR_MAX_INT)
                    exitwhen not unique.has(v)
                endloop
                set unique[v] = 1
                set ints[i] = v
            else
                set ints[i] = GetRandomInt(GENERATOR_MIN_INT, GENERATOR_MAX_INT)
            endif
            debug set PRNG.lastGeneratedIntegerIndex = i
            set i = i + 1
        endloop
        
        // Flag system as initialized, so user can detect that it has crashed.
        debug set PRNG.initialized = true
        
        // Update global seed to avoid static seeds.
        call SetRandomSeed(gameSeed)
        
        static if (GENERATOR_FORCE_ALL_RANDOM) then
            call unique.destroy()
        endif
    endfunction
endlibrary

Changelog:
1.0.1 - Fixed basic usage instructions.
1.1 - Added fillTable and fillTableEx methods. Removed getRandomInt method. Removed option for generating only positive/negative integers. Small speed boost when all integers must be unique.
1.2 - Fixed error that made the system select same 'game seed' every game.
1.2.1 - Improved fillTableEx method.
1.2.1.1 - Fixed fillTableEx method.

C&C :)
 
Last edited by a moderator:
Honestly, I like it.
I even plan to update my Stack-Library to allow the usage of your randomizer.
What I am missing is that you separate the init function and pass a table.
This way you can create a method FillTableRandomly($table, $amount, $unique, $positive, $negative).

In the end you have a good solution to provide several random tables for the user.
 
There are different and much simplier ways to create pseudo random numbers.
X2 = A * X1 (mod B)
(If you want numbers between 0 and 1 just -> X2/B)

Good choice would be A = 16807 and B = 2^31 - 1

But I guess GetRandom natives do some such like shit in background, so just changing seeds like you do can get job done, good enough as well.
 
Level 6
Joined
Feb 10, 2008
Messages
300
Yes, but this isn't a number generator, it's a script to map array indexes to pseudo random numbers :)

On the other hand, if you meant the entire GetInteger thingey then I guess that could be another possible update?
 
Level 6
Joined
Feb 10, 2008
Messages
300
Ah, then I've explained the purpose poorly!

I'm using this script for this purpose:

[Base Item]
+ 2 health
+ 2 random stats

When a player picks up that item, I get a random index via local integer index = PRNG.getRandomIndex(). Then I set the global seed, call SetRandomSeed(PRNG.getIndexInt(index)) and do my stat rolling.

The index value is then tied to that item, so when I save/load a hero, I can reproduce those rolls the item had due to the fact that PRNG.getIndexInt(index) for the same index, is the same.

On another note:
I could update this with a random number generator thingey instead of using an integer array. Any thoughts?
 
Level 7
Joined
Jan 28, 2012
Messages
266
this is a very good idea.
@-kobas- the whole purpose of this system is for save/load codes in maps that have randomly generated items/ whatever.
what it would do is allow you to create an item with a complicated list of bonuses into a relatively small number of symbols(like the level of the item, and the seed used to generate it).

Edit: the fill table method suggested by anachron would be useful.
 
Last edited:
Level 6
Joined
Feb 10, 2008
Messages
300
A quick example:

JASS:
    private function TestSeed takes nothing returns nothing
        local integer a
        local integer b

        local integer a1
        local integer b1

        call SetRandomSeed(5) // set seed to fixed value
        set a = GetRandomInt(0, 100) 
        set a1 = GetRandomInt(0, 5) 

        // important part:
        // After the SetRandomSeed(5), all GetRandom..-functions must mirror. Like:
        call SetRandomSeed(5) 
        set b = GetRandomInt(0, 100) // Mirror the above GetRandomInt call (a)
        set b1 = GetRandomInt(0, 8) // Doesn't mirror the corresponding GetRandomInt call (a1)

        if (a == b) then
          call BJDebugMsg("It works!")
        endif

        if (a1 == b1) then
          call BJDebugMsg("You are lucky!")
        endif
    endfunction

This function will output "It works!" every time it is executed.
If you are lucky, it will also output "You are lucky!" every time it is executed,
if you however isn't that lucky, it will never output "You are lucky!".

(Note: The "You are lucky!" text is only be shown if GetRandomInt(0, 8) == GetRandomInt(0, 5)
, which is possible, but not something you should expect.)

---

As Ender writes, it is possible to do complex rolling and reproduce that by just saving one number.
In my item system, I do about 50 GetRandom... calls per item generated,
something that would increase save/load code length by a lot of extra characters per item.

---

A note for potential users, it is probably a good idea to save the 'index' (as I show in the instructs), instead of the actual seed.

PRNG.getRandomIndex() is in range [0, GENERATOR_COUNT]
PRNG.getIndexInt(integer index) is in range [GENERATOR_MIN_INT, GENERATOR_MAX_INT]

---

You can do a couple of cool things with this, so I'll probably update the script with that table function.

EDIT:

Updated the script.
Version is now 1.1

Instead of the system doing the positive/negative only handling, that is now controlled via the GENERATOR_MAX_INT and GENERATOR_MIN_INT globals.
Added two new methods:
JASS:
static method PRNG.fillTable takes Table table, integer generatorSeed, integer generatorCount returns nothing

static method PRNG.fillTableEx takes Table table, integer generatorSeed, integer generatorCount, integer generatorMinInt, integer generatorMaxInt, boolean forceUnique returns nothing
Also updated the script to use a local variable to cache values while forceUnique/GENERATOR_FORCE_ALL_RANDOM is true.
 
Last edited:
Level 6
Joined
Feb 10, 2008
Messages
300
It would be clean if I could pass an integer array instead of a Table instance :)

From my perspective, an integer array fits the purpose perfectly, while a Table is just overkill, if you're generating less than 8191 values that is.
If you want to generate more than 8190, a Table would be the best solution.
 
Level 6
Joined
Feb 10, 2008
Messages
300
Table is now required.

Don't misunderstand me, I don't think using fillTable for the global table is a bad idea, it's just that I think an integer array is better.

If one more person agrees that a Table would be the best option, I'll happily change it :)

EDIT:

Important update!
Fixed critical error when selecting new game seed value.
 
Last edited:
Top