• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Random

Status
Not open for further replies.
Level 24
Joined
Aug 1, 2013
Messages
4,657
Here is a Random struct.
The purpose of it is that you have different instances of RNG so you can have a seed for item spawn, a seed for unit spawn, a seed for terrain generation, etc without having basic attacks (with triggered critical strike) affect the roll.

For some reason call GetRandomInt(-2147483648, 2147483647) always generates "-2147483648".
Hence the MAX and MIN in this one.

If anyone has other ideas on how to make it without going through the pain of making the random number generation algorithm itself, feel free to share.

JASS:
struct Random
 
   public static constant integer MAX = 2147483646 // Yes this is Integer.MAX -1
   public static constant integer MIN = -2147483648
 
   readonly integer seed
 
   public static method create takes integer seed returns thistype
       local thistype this = .allocate()
       set .seed = seed
       return this
   endmethod
 
   public static method createByRandom takes nothing returns thistype
       return .create(GetRandomInt(.MIN, .MAX))
   endmethod
 
   public method destroy takes nothing returns nothing
       call .deallocate()
   endmethod
 
   public method setSeed takes integer seed returns nothing
       set .seed = seed
   endmethod
 
   public method getInt takes integer lowBound, integer highBound returns integer
       call SetRandomSeed(.seed)
       set .seed = GetRandomInt(.MIN, .MAX)
       return GetRandomInt(lowBound, highBound)
   endmethod
 
   public method getReal takes real lowBound, real highBound returns real
       call SetRandomSeed(.seed)
       set .seed = GetRandomInt(.MIN, .MAX)
       return GetRandomReal(lowBound, highBound)
   endmethod
 
endstruct
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Yes i think this looks reasonable.
Except for maybe setSeed. I see no reason in it.
And i guess you could reduce the get* methods to three lines but it wouldnt make much of a difference.

i will check the behaviour of GetRandomInt(min, max) and then potentially add it to my jass db.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
setSeed is possible to have... so why not have it?
Next to that, "/setSeed 12345", chat commands are pretty nice.

And yes, the getInt and getReal are pretty odd now that I actually take a look at what I actually made :D
I am too used to have seeds actually affect the results so set them after I got the results... not really necessary.

Im also thinking about adding a bug case exception.
If the seed is the same as the result of GetRandomInt(.MIN, .MAX), the seed will never change.
I assume that the chance is miniscule, but it still (probably) exists.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
You destroy the purpose of the seed by rewriting it on every call. This makes no sense whatsoever. It looks to me like you don't get what a seed is.

You cannot, in fact, do what you want to do, in Jass (without implementing your own RNG). Read a little about RNGs and how seeds are used, and you'll realize why.
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
I have, in fact, done what I want to have done.

A seed is what determines the series of random numbers.
I set the seed, get the next seed by a random number and the number after that is the one that is returned.
So in fact, I skip a seed, so if you do:
JASS:
call SetRandomSeed(123)
returnGetRandomInt(0, 10)
You get something else than this:
JASS:
call RNG.setSeed(123)
returnRNG.getInt(0, 10)
As this is the same as:
JASS:
call SetRandomSeed(123)
call GetRandomInt(0, 10)//we ignore this
return GetRandomInt(0, 10)

So yes, if you know the entire seeded numbers of WC3, then you wont like this.
If not, this is exactly the same as what the normal JASS API gives. Except I offer different instances with their own seeds.

If you wont use RNG.seed for anything, the seeds are exactly the same as in normal RNGs.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
You are supposed to set a seed once at the RNG initialization. Setting it at any other point means you reset the RNG. You keep reseting it each time you call it, and therefore you don't have "different instances with their own seeds", you have one RNG that you keep changing (and changing with supposedly random numbers too, so I don't get where you even got the notion of each "instance" owning a seed).

So, intuitively, this means your struct is as good as simply setting the seed once.

However, I am pretty sure (although it's been a very long time since I read quite a bit about RNGs) that when you do what you do - set the seed, get one number, set the seed, get one number, etc., it obliterates the distribution of pseudo random numbers, meaning you are only ruining the randomness of your single RNG, with no benefits whatsoever (but don't quote me on this, although I am pretty sure I remember something along these lines being said).

Now, if you want to actually have multiple instances of RNGs, then you need a mechanism to create multiple RNG objects, which of course you don't have in Jass.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
.setSeed() is indeed the same as .destroy() + .create()
But that is not the same for the .getInt() and .getReal()

I have an instance of Random and give it the seed 123.
I then call rng123.getInt(0, 10) and it gives me an int between 0 and 10.
Lets say that the first 10 results will be this:
6,8,2,0,5,1,3,7,2,3
If I then create another one with the same seed, I get the same results (as long as I use the same bounds ofc).
That is what I wanted to have.

The seeds will be changed into other random seeds, but the sequence of those random seeds will also remain the same.
And everytime I want to get a new number, I make sure that the seed is currently up to date with the seed of the current step.
In normal RNGs, the seed will be lost once you get a random number, or you have to keep track of the offset.

But in this case it doesnt matter, it works the same as multiple instances of RNGs while not having to go through the effort of making the algorithm.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
I have an instance of Random and give it the seed 123.
I then call rng123.getInt(0, 10) and it gives me an int between 0 and 10.
Lets say that the first 10 results will be this:
6,8,2,0,5,1,3,7,2,3
If I then create another one with the same seed, I get the same results (as long as I use the same bounds ofc).
That is what I wanted to have.

This is how it would work in case you only do the sensible thing of storing a seed for each RNG so called instance upon creating it:
JASS:
RNG1 = RNG.create(123)
RNG1.getInt(0, 10) // 6
RNG1.getInt(0, 10) // 8

RNG2 = RNG.create(123) // reset the real RNG of Warcraft
RNG2.getInt(0, 10) // 6

RNG1.getInt(0, 10) // 8 instead of 2 - RNG1 doesn't have "its own" seed, in the sense that it doesn't matter whatsoever because you reset the only real RNG
This shows that storing the seed is basically useless - at any point of time you have exactly one seed that is used by Warcraft, and Warcraft "tracks" this one seed. The fact that you store 2 of them in variables is meaningless.
In turn, this means that your whole struct with initialization, setSeed, getInt, and getReal, is as good as simply calling SetRandomSeed once upon map initialization and be done with it.
EXCEPT, for what I wrote about changing the seed on every call, which I am almost absolutely sure ruins the randomness of your numbers.

/Edit
I re-read what you wrote about the sequence of seeds being per-instance, and you are right. I still think this ruins the randomness of the resulting numbers, but that should be googled (or not).
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Ofcourse, it only uses a small part of the actual random number sequence, but I assume that setting the seed to literally any integer is randomly enough.

The only problem is that it uses a global sequence, a sequence of length 2^32-1 (difference between .MIN and .MAX)
Except if it would generate the same number with different seeds making some numbers (for seeds) completely unavailable.
But as that sequence is so large, I dont really care about that.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Huh?
How do you mean that?
In most cases, I want to use a specific random seed if I want "random" results, but deterministic. As in: reloading the game will exactly achieve the same roll over and over again and there is no way for the player to change the outcome. Like a quest reward or a random drop of a treasure chest or whatever.

However, what happens when I use "getInt" once is that the next roll will be deterministic, obviously, that's the point. However, if I forget to change the seed back after the roll, any attack done afterwards will progress the seed further, making the next roll non-deterministic.


In practice, this means that after I use getInt from one seed, I need to switch back to whatever was the default seed until I need another deterministic roll.
This can easily be done manually, but I think this should be part of your system.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
"However, what happens when I use "getInt" once is that the next roll will be deterministic, obviously, that's the point. However, if I forget to change the seed back after the roll, any attack done afterwards will progress the seed further, making the next roll non-deterministic."

As long as you use a different instance of it, or use the GetRandomInt()/GetRandomReal() natives, this wont happen.
I cant set back the seed because I cant set in an offset, so if I would not change the seed "randomly" I would get the same number over and over and over again.

The outcome of the natives will not be from one seed any more, but if you dont use them directly, the seeds work fine.

Next to which, there is no "GetRandomSeed()" or any other way to load in the seed that is used.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
GetRandomInt
Code:
int result; // eax@2
  int v3; // esi@4

  if ( a1 == a2 )
  {
    result = a1;
  }
  else
  {
    if ( a1 <= a2 )
      v3 = a2 - a1 + 1;
    else
      v3 = a1 - a2 + 1;
    result = a1 + ((unsigned int)v3 * (unsigned __int64)(unsigned int)sub_6F011390(dword_6FAB73D8) >> 32);
  }
  return result;
you just causing overflow of the 32 integer and that's all. why would anyone need such range anyway?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
The ranges is not what matters really.
I tried to make the seed a random number (with all possible numbers) but it always resulted in -1*2^31 which took me a while to figure out that it just didnt work as I intended.
But it seems that it is not unexpected behavior.

Btw, how did you get the source code?
Assuming this is the actual source code and not the code of a different RNG.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
here is my attempt to make GetRandomInt readable.
Code:
#include <limits.h>
#include <stdint.h>

#include "table.c"

static int s_round();

int seed1;
int seed2;

static int rol(int value, int count){
  const unsigned int mask = (CHAR_BIT*sizeof(value)-1);
  count &= mask;
  return (value << count) | (value >>(-count) & mask);
}

static int gettable(int idx){
  //printf("tbl[%d]\n", idx);
  return *(int*)(((uint8_t*)table)+idx);
  //return table[idx];
}

int GetRandomInt3(int lo, int hi){
  if(lo == hi){
  return lo;
  }
  if(lo > hi){
  hi = lo - hi + 1;
  } else {
  hi = hi - lo + 1;
  }

  s_round(); // returns seed1
  int32_t tmp = (uint64_t)(hi) * (uint32_t)(seed1) >> 32;

  return tmp + lo;
}

int s_round(){
  int b;
  int newseed1, newseed2;

  b = (uint32_t)seed2 >> 24;
  b -= 4;
  if( b < 0){
    b += 0xBC;
  }

  newseed1 = rol(gettable(b), 1);
  newseed2 = b << 8;

  b = (uint32_t)(seed2 >> 16) & 0xff;
  b = b - 0x0C;
  if(b < 0){
    b += 0xD4;
  }

  newseed1 ^= rol(gettable(b), 2);
  newseed2 = newseed2 | b;

  b = (uint32_t)(seed2 >> 8) & 0xFF;
  b -= 0x18;
  if(b < 0){
    b += 0xEC;
  }

  newseed1 ^= rol(gettable(b), 3);
  newseed2 = (newseed2 << 8) | b;

  b = seed2 & 0xFF;
  b = b - 28;
  if(b < 0){
      b += 0xF4;
  }

  newseed1 ^= gettable(b);
  newseed2 = (newseed2 << 8) | b;

  seed1 += newseed1;
  seed2 = newseed2;

  return seed1;
}

I have no idea why it works tbh.

Also notice that SetRandomSeed does stuff to init seed1 and seed2 which i have no idea about. Also note that you need an int table[256] (or maybe char table[256]). And getting the correct values for these is also something im probably not capable of.
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
That is true.
The randomness is not really that good.
With normal seeds, there are 2^32 sequences of whatever long they are (pretty long).
What I have is one sequence of up to 2^32 length. (I can do a test to find out how long exactly.)
So yes, it is quite less random if I see it correctly.
 
Status
Not open for further replies.
Top