Jump to content


Agamer

Member Since 25 Mar 2009
Offline Last Active Apr 21 2013 01:32 AM

Topics I've Started

Game Maker randomness unveiled

14 May 2012 - 12:41 AM

  • Title: Game Maker randomness unveiled
  • Description: Exactly how Game Maker's randomness works
  • GM Version: GM8.0
  • Registered: unnecessary
  • File Type: -
  • File Size: -
  • File Link: -
  • Required Extensions: -
  • Required DLLs: -
Summary
This tutorial will briefly introduce readers to Game Maker randomness, and the basic provided functions. It then goes into depth to explain how Game Maker's random system works and how it can be implemented and used flexibly. Two scripts are also provided, which this tutorial thoroughly explains, namely random_step_back, and random_step_forward. They serve as demonstrations of how to take advantage of Game Maker's randomness once it is fully understood.
It is strongly suggested that readers have at least a basic understanding of binary and bitwise operators, 32-bit integers, and hexadecimal notation, as some information could be rather confusing otherwise.
Here's a good tutorial to help explain binary and bitwise operators: http://gmc.yoyogames...howtopic=473795

INTRODUCTION: Game Maker's random functions

Game Maker has always provided a system for generating random numbers. According to the help manual, these functions are as follows:

random(x) Returns a random real number between 0 and x. The number is always smaller than x.
random_range(x1,x2) Returns a random real number between x1 (inclusive) and x2 (exclusive).
irandom(x) Returns a random integer number between 0 and x (inclusive when x is an integer).
irandom_range(x1,x2) Returns a random real number between x1 (inclusive) and x2 (inclusive). Both x1 and x2 must be integer values (otherwise they are rounded down).
random_set_seed(seed) Sets the seed (an integer) that is used for the random number generation. Can be used to repeat the same random sequence. (Note though that also some actions and the system itself uses random numbers.)
random_get_seed() Returns the current seed.
randomize() Sets the seed to a random number.
choose(val1,val2,val3,...) Returns one of the arguments choosen randomly. The function can have up to 16 arguments.


Each time one of these functions is called for any particular input(s), the outcome is unpredictable. So, for instance, the first call of random(64) will be one number (with some long fractional portion as well), and the next call will be another, totally unpredictable number.

However, to be technically honest, a computer cannot generate truly random numbers. Every number generated has to be based off something. So, then, how are these numbers generated so ... randomly?


MY JOURNEY: Cracking Game Maker's random number generator.

When I was younger, and when I was using Game Maker 6.1, I made the assumption that the randomness was generated by throwing completely random things together (i.e. the previous value used in the code, user inputs at certain points, etc.) and mixing them all up with various mathematical operators. Would this work? Perhaps, but it is far off from how randomness actually works.
When Game Maker 8.0 arrived, it came with two new functions: random_set_seed(seed), and random_get_seed(). This addition made me have to abandon my whole old conception of the methods behind randomness.

After a while of inspecting more closely the random number system Game Maker provides (and especially making use of the random_set_seed(seed) and random_get_seed() functions), I was finally able to crack the system and gain full control over the functionality of it, beyond what Game Maker provides.

LOOKING DEEPER: The basic functionality of the random number generator.

  • How does the random number generator initialize?
When a game starts up, the random number generator chooses a random seed to start with. It basically calls the randomize() function. Now, how does this function choose a random number if it is the random number generator, especially at start-up? Well, technically, the number it chooses is not really random. As I've learned from other programming languages, random number generators normally take your computer's current time and use it as a seed. This assures that just about every time the game is opened, it uses a different seed. So, basically the randomize() function is somewhat like putting: random_set_seed(current_time).

  • So, then, now that it has a seed, what does it do with it?
Whenever a random function call is made (e.g. whenever you call the random(x) function), Game Maker takes this seed and manipulates it. The seed is then changed to this new number. Afterwards, the new seed is returned, formatted to fit the specific field you asked for. That is to say, seeds tend to be very large numbers (like 120487931) which aren't suitable for handling as values for most things. Therefore, when you make a call like random(10), the seed is stretched to fit into the field from 0 to 10.

That's the general way Game Maker handles random numbers.

RANDOM SEEDS: The internal seed, and scaling seeds to values.

  • So that's the process it goes through in general, but how does it actually work in full depth?
To understand how the random number generator works fully, first you must understand the details of the seed. Game Maker's random seed is simply an integer. It can be a number from -2147483648 to +2147483647. If you set the seed to a number with a fractional place, it will be rounded off, and if you set it to a number beyond this number range, it will loop back around the other side (much like the way the local direction variable never escapes the 0 to 360 degree range). By calling the function random_get_seed() you can get the seed at any point. Now for each seed, one and only one new seed is generated when a randomness function is called. So, for example, let's say the seed is 152. We then call random(100) and it will return 76.98. The seed then changes to a new number. To be accurate, the seed afterward is -988912903. Now let's suppose we set the seed back to 152 again at some later point (or even after reopening the game), by calling random_set_seed(152). If we call random(100) again, not only will it return 76.98 again, the seed changes to -988912903 again as well. And this happens for every seed. So, for every seed, one specific seed will always come right after it.

  • Before I continue, I must provide a basic explanation of signed and unsigned integers (if you already have an understanding of this, you can briefly skim over this section)
In computers, numbers are stored in various formats (in most type-specific programming languages, you'll hear of integers, floats, and doubles). Each type of number holds its value in the memory of a computer in a certain way. Integers (technically long integers, also called 32-bit integers, and dwords or double-words when in files) occupy 32 bits (four bytes) of space in memory. These 32 bits enable the integer to have up to 4,294,967,296 different values. Now, integers can handle this range in two ways: signed and unsigned. If an integer is unsigned it can hold a value from 0 to 4294967295, and if it's signed it can hold a value from -2147483648 to +2147483647. The numbers are limited to these ranges. So, for unsigned integers, 0 minus 1 loops over to 4294967295, and for signed, 2147483647 plus 1 becomes -2147483648. Recognize that integers can also be translated between these two types. When converting from signed to unsigned integers, instead of adding 2147483648 to the signed integer, we take the whole negative range and move it in front of the positive range. So, -2147483648 becomes +2147483648, -2147483647 becomes +2147483649, and so on until -1 becomes 4294967295. This makes it so that the range from 0 to +2147483647 is the same for both integer types, preserving these numbers. Basically we're adding 4294967296 to all the negative numbers of the signed integer. This process of transforming from a signed integer to an unsigned can also be done by using the bitwise & (and) operator. To limit the scope of this tutorial, I cannot get too into depth on how this works. Basically, though, signed numbers that are negative have a set of ones in all the largest bit-places (so, a normal signed positive number would look like "00000000010010", but a negative number would look like "11111111101001"). Therefore, when we take the signed number and bitwise & combine it with (232-1) we chop off all the 1s that make the number negative, and it then jumps to the larger field right where we need it, e.g.:

Note, (232-1) = 4294967295
-498103 (...11111111111111111111111110000110011001001001)
& 4294967295 (...00000000000011111111111111111111111111111111)

= 4294469193 (...00000000000011111111111110000110011001001001)
 				[1s chopped]

  • So, now, where does the random number come in? Where does this 76.98 come from?
As I explained earlier, the seed is scaled to fit the field the function asks for. So, random(100) asks for a field from 0 to 100 (technically, it asks for a number within the interval [0,100) ). Now first you might think the seed is simply just scaled the way it is, i.e. if the seed is -2147483648 then the random number is 0, and if it's +2147483647 then it's nearly 100 (minus some minuscule amount; it will never be exactly 100 since the random(x) function excludes the end; refer to the help documentation). However, the seed is actually taken as an unsigned integer when it's scaled. When we work with the seed using random_get_seed() and random_set_seed(seed), it's converted to a real as a signed integer (Note, Game Maker's real numbers are not integers). However, when it is used internally, it is much clearer to view it as an unsigned integer, and it is in that state when it is scaled to provide our random number (76.98 in our example). Thus, when we calculate (random_get_seed()&4294967295) which performs the chop-off (note that 4294967295 can be much more simply typed as Game Maker hexadecimal $FFFFFFFF, eight F's), we get the seed's value as it is in its internal state. Now, because the seed is internally used as an unsigned integer and can be best understood that way, from here-forth, unless specified, I'll be referring to the seed while it's in its unsigned internal state. So, once the seed is converted to an unsigned integer, then it can simply be stretched to fit into the interval asked for. So, calling random(100) means the next seed is calculated (which I'll explain shortly), and then the new seed (in its unsigned state) is divided by 4294967296, and then multiplied by 100. Since the seed is a number from 0 to 4294967295, dividing it by 4294967296 will provide a number from 0 (inclusive) to 1 (exclusive by 1 / 4294967296). Then, when it's multiplied by 100 it becomes a number from 0 to 100. So, let's take our example: the seed was -988912903, which after making unsigned becomes, 3306054393. Then dividing by 4294967296, it becomes ~0.76975, and multiplying by 100 yields76.98 (it's rounded to 2 decimal places), the number we had in the first place (note that the random_range(x1,x2) and irandom_range(x1,x2) functions simply scale the seed into the size of the range, i.e. x2-x1, and then position it properly, i.e. by adding x1; also, the irandom(x) and irandom_range(x1,x2) functions just round off the number before returning it, sort of). This works so straightly that calling random(4294967296) will always return the seed, as it is after the call.

RANDOMNESS UNVEILED: The key to randomness is 134775813.

  • So, by now, everyone must be thinking, that still doesn't explain anything about the randomness! Finally, now that the ground-work and the basic internal designs have been explained, I shall officially explain the randomness.
So, now, when any calls to the randomness functions are made, the seed is transformed. Then it's scaled and returned, but the unscaled seed becomes the new current seed. So, this transformation is the key. And basically, the transformation is just taking the seed, multiplying it by 134775813, and then adding 1, but looping it in the field from 0 to 4294967295.
So, first why does this seem to make the numbers random?
The simple fact is, since multiplication is not linear, it provides a seemingly random jump-around while not being too calculation-intensive.
  • Then, what's the randomly thrown in +1 for?
Consider the seed 0: multiply it by 134775813 and it still yields 0. Therefore, to make the 0th seed worth something, adding 1 offsets it so that the next iteration will then turn into 134775814, and become pseudo-random from there on.
  • And why the magical 134775813?
This number happens to be one of the rare special numbers that makes the random seed go through every possible number before looping back to the start. So, starting from seed 0, the algorithm described has to be run 4,294,967,296 times before coming back to seed 0. This makes it very evenly distributed. (I proved this by using basic brute force)

Now, the first thing you probably want to do is predict the next seed, and see if it came out right. Basically, you could run in debug mode and check the expression, (random_get_seed()*134775813+1)&$FFFFFFFF to get the next seed (note that I didn't first convert the seed to an unsigned integer, but it actually makes no difference because the binary data is the same; the only important part is that I chopped off the extra data after completing the calculation). Then you could execute the code, show_message(random($100000000)) to display the next seed ($100000000 is hex for 4294967296 whereas $FFFFFFFF is hex for 4294967295).
  • Did you get the same number? No?
Unfortunately, due to Game Maker's precision limits, this calculation cannot be done perfectly, but only gets very close (generally 6 or 7 off as I've found). I proved that it was just Game Maker's precision by doing the same calculation on the windows calculator in programmer mode, and I came out with identical numbers.
So, is implementing Game Maker's randomness impossible then?
Not at all. Although it can't be done as simply as x*134775813+1, it is still possible to implement. In fact I wrote a function to implement it:

random_step_forward(seed):

// takes a step forward in the randomness; returns the seed following the argument0 seed
var seed_in, key, seed_out, bit, bit_cur;
// prepare variables
seed_in = argument0;  // the input seed
key = 134775813;      // the magical randomness key
seed_out = 0; 		// the output seed
bit_cur  = 1; 		/* a number containing the current bit,
                      e.g. if "bit" is 5, this number will be in binary "100000" */
// "bit" is the bit index; we're doing a manual multiplication
for(bit = 0; bit < 32; bit += 1) // cycle through all the bits
{
   // if the input seed has the 'current bit', add the key shifted to that bit; i.e. implement binary multiplication:
  if (seed_in & bit_cur) seed_out += key;
  bit_cur = (bit_cur << 1); 		// left bit-shift the current bit
  key = ((key << 1) & $FFFFFFFF);   // left bit-shift the key value and trim off anything that exceeds the 32-bit limit
}
return (seed_out & $FFFFFFFF) + 1;  // return the new seed plus the extra 1

So, what this does is simply manually calculates a binary product, limiting it to the 32-bit field. Binary multiplication is very similar to regular multiplication in arithmetic. To calculate a binary multiplication problem, take one multiplier and list it under the other multiplier, up-shifted to every bit in the other multiplier that's a one. Then just add these together, i.e.:

10010101 x 110110010:

        10010101
        --------
   	110110010
 	110110010
   110110010
110110010
----------------
1111110010011010

Notice that under each 1 bit in the top number is the other number bit-shifted to its place

        10010101
        |--|-|-|
   	1|01|0|10
 	110|10|10
   11011|010
110110010

So, the code basically just implements this. It cycles through and checks for whatever bits are 1. Once one is, the other number (in this case the key) is added to the output shifted over to that bit-place. Thus we left bit-shift the key in each cycle so that it's already prepared to be simply added to the output. The only difference in the script's multiplication from regular binary is that we're limiting the field to 4294967296. So, we trim off the end of the key as the multiplication progresses so that it doesn't become too big and corrupt the output's precision again (since the problem is, Game Maker's number precision to the full integer only continues a little beyond 32 bits). This is called modulated multiplication. Then, at the end we trim off the excess bits in the output, and add 1 to complete the algorithm. Note again that the input wasn't trimmed. It isn't necessary, though, in this case either because all we're doing with the input is checking which of the first 32 bits are ones.

  • So, predicting the seed isn't that exciting. That is, what can we do with it when all we have to do to get the next seed is run random(1) and then call random_get_seed()?
Well, I suppose for starters, changing the key from 134775813 to some other number will completely change the whole system and create an entirely new random system. Of course, only a few unique numbers exist that cycle through all the seeds before returning. Nonetheless, one of the greatest aids, I think, in understanding Game Maker's random engine comes when we create the function to reverse this seed, and actually calculate the previous seed.

To reverse the seed, we simply subtract one, and then divide by 134775813. Of course, straight division by 134775813 won't yield us the previous seed, but rather some number with a fractional portion. Now the question is, if the previous number was multiplied, and then gained bits beyond the limited field, but then those bits were cut off, how can we reverse division if we lost those bits? In another way, isn't it possible that two numbers could be multiplied by 134775813, and when limited to the field 4294967296, both yield the same number? In any normal modulated multiplication, it is definitely possible that two numbers can be multiplied by another number, and when looped back, yield the same number. However, as I explained before, the random seed must cover all the 4294967296 seeds before coming back again. Therefore, in this special case, no two numbers will multiply to produce the same number. So, it is possible to reverse this exceptional modulated multiplication algorithm. Now the question is, how?

Well, simply by reversing the multiplication method used for the forward step. The following code produces the previous seed:


random_step_backward(seed):

// takes a step backward in the randomness; i.e. returns the seed that comes before the argument0 seed
var seed_in, key, seed_out, bit, bit_cur;
// prepare variables
seed_in = argument0 - 1;  // the input seed; subtract the one first
key = 134775813;          // the magical randomness key
seed_out = 0; 			// the output seed
bit_cur = 1;              /* the number containing the current bit, as before,
                          e.g. if "bit" is 5, this number will be in binary "100000" */
// "bit" is the bit index; we're doing division
for(bit = 0; bit < 32; bit += 1) // cycle through bits; we start at the first again
{
  /* if the input seed has the 'current bit', remove the key shifted to that bit,
 	and assume it was part of the original multiplier: */
  if (seed_in & bit_cur) 
  {
    seed_in -= key;   	// remove the key shifted to the bit 
    seed_out |= bit_cur;  // add that bit to the output seed; uses the bitwise or (|) operator to add bits
  }
  bit_cur = (bit_cur << 1);   // left bit-shift the current bit
  f = ((f << 1) & $FFFFFFFF); // left bit-shift the key value and trim off anything that exceeds the 32-bit limit
}
return seed_out; // return the previous seed; no trimming is necessary since one bit was added at a time

What happens here is basically the reverse of the multiplication; starting with the first bit, each bit is checked in the input seed. Once a 1 is found, it is added to the output seed, and then the key bit-shifted to that bit is subtracted from the input seed. Technically, this division is backwards, but that's what keeps it from getting some fractional number for an answer, because if it were calculating forward, it would begin to affect the fractional-place bits. And this division calculator also assumes it has any carry-down bits it needs, thus making it suitable for a modulated field. Remember, though, it only works because no two numbers will multiply by one other to the same number (i.e. if a != b then a * x != b * x).

WRAP UP: Good bye! I hoped you learned something you can use!

So, that wraps up my tutorial of Game Maker's random number system.
If anyone has any questions or suggestions, or notices any bugs, just PM me, and I'll get to it asap.

And for the purposes of those who'd like to use the two previously provided functions, here they are in a much more compact form (which would not have passed for a tutorial):

random_seed_backward(seed)
// returns the random seed previous to argument0

var n,f,o,b,ni;n=argument0-1
f=134775813;o=0;b=0;ni=1
for(b=0;b<32;b+=1)
{if n&ni{n-=f;o|=ni}
ni=(ni<<1);f=((f<<1)&$FFFFFFFF)}
return o&$FFFFFFFF

random_seed_forward(seed)
// returns the random seed following argument0

var n,f,o,b,ni;n=argument0
f=134775813;o=0;b=0;ni=1
for(b=0;b<32;b+=1)
{if n&ni o+=f;ni=(ni<<1)
f=((f<<1)&$FFFFFFFF)}
return (o&$FFFFFFFF)+1

And then here are functions that actually set Game Maker's seed forward and backward:

random_step_backward()

// takes a step back in the randomness

var n,f,o,b,ni;n=random_get_seed()-1
f=134775813;o=0;b=0;ni=1
for(b=0;b<32;b+=1)
{if n&ni{n-=f;o|=ni}
ni=(ni<<1);f=((f<<1)&$FFFFFFFF)}
random_set_seed(o&$FFFFFFFF)

random_step_forward()

// takes a step forward in the randomness
var n,f,o,b,ni;n=random_get_seed()
f=134775813;o=0;b=0;ni=1
for(b=0;b<32;b+=1)
{if n&ni o+=f;ni=(ni<<1)
f=((f<<1)&$FFFFFFFF)}
random_set_seed((o&$FFFFFFFF)+1)

And just for the record,
the random seed right before seed 0 is 649090867.

- Agamer

Free-rotating arbitrary dimensions

10 April 2012 - 11:04 PM

Warning: Do not try to answer this question if you do not have a strong mind-conceptual space. Posted Image

So, here's the background:
I am making a vector graphics editor system that runs with arbitrary dimensions (i.e. the user could set it up to have two-dimensional points, ten-dimensional points, or even a thousand-dimensional points). The point system operates off of a group of points representing the origin. There's a central origin point, representing the absolute zero in all dimensions, and then there are other points, representing the base-vectors for each dimension (i.e. positive one unit along each axis). The view is merely a transformation of all points, including the origin points, such that the points have their original coordinates as combinations of the transformed origin points. Also, the view only sees the first two dimensions; the other dimensions are conceivably all depth dimensions (sort of...).
Here's the beta version if you need to get an idea of what it is more exactly.

Here's the catch:
Now, I need to allow the user to manipulate this x-dimensional view-space. I have a few keys already grounded: shift-click-and-drag translates the view, control-shift-click-and-drag rotates the view around the center. These are the two-dimensional view transformations. I have two more keys available: shift-rightclick-and-drag and control-shift-rightclick-and-drag. Here's the question: How can I use these keys to rotate the x-dimensional space by the origin points so that the user can view any and every angle of the origin by rotating to it?

My guess:
I thought I might use shift-rightclick-and-drag to rotate 3-dimensional space and use control-shift-rightclick-and-drag to transform in such a way as to "open up" the other dimensions.

Does anyone have any ideas?

Deleting Directories

02 March 2012 - 10:28 PM

This handy, yet very simple GEX file can delete directories completely.

Posted ImagePlease Note!Posted Image
This is to be used to your own risk! This will delete entire directories!
There is the possibility that this might not work on all operating systems.

(This might not work for x32 bit OS, but i haven't tried it)

Download Link: DirectoryDelete.zip

Cactus; looks a little strange

27 October 2011 - 10:41 PM

So, I whipped together a little program that creates sprites from scripts; i.e., a script draws stuff onto a surface. After I put it together, I've been adding plenty of groups of scripts to generate images. Some of them aren't that bad really, and after creating and adjusting the script, I get as many images as I want.
However, I made one to generate random cacti from a top view:
Posted Image
It just doesn't look right... I don't really know what to do to sharpen it up. Suggestions?

Generic GUI Component Package

17 October 2011 - 10:32 PM

I have put together an extremely useful package of Game Maker resources (that's a .gmres file).
It's a package of objects that can be used to create a generic GUI interface, similar to Windows components.
So for those trying to make some image editor or model designer or something, and are in need of some GUI components, this package includes the following GUI components:
  • Multi-tab field
  • Label (check-box and option connectable)
  • Button
  • Check-box
  • Option
  • Scroll-bar
  • Item-list
  • Tree-view (full folder collapsing operational)
  • Drag-bar (can be used for resizing)
  • Frame (fully editable window; maximum capabilities of the frame are available in v4.04)
  • Text-box (full mouse-drag and cursor capabilities)
Use the hundreds of scripts designed to properly change the component properties, and allow working with the components to be both professional and easy. Please report any bugs.

The components 3.21 package has the basic operating components which can be imported into basic programs. But for more complex programs, where multiple frames are necessary, use components 4.04 which supports containers.
Components package v3.21
Components package v4.04
Here's a picture!
If you've already downloaded versions earlier than 3.21, definitely get v3.21 or 4.04. Major bug fixes and updates were applied to both the components and their function scripts since v3.0.

Look forward to the upcoming features:

Spoiler