Random Numbers in ActionScript
Generating (Pseudo-)Random NumbersThe Math object in ActionScript contains a number of methods that perform mathematical operations. Among these is random(), which generates an apparently random number between 0 and 1. To generate a random-number value and assign it to a variable, use a statement like this:
someVariable = Math.random();
Note that the M in Math must always be capitalized. Names of predefined objects always use capitals.
Technically speaking the output of Math.random() is only pseudo-random, because the technique used to generate its values will display a detectable pattern if it is repeated enough times. Unless you have the patience to scrutinize hundreds of thousands of repetitions, you probably won't notice.
Below are five movies showing Math.random() in action. In each movie a script displays randomly generated numbers or words.
The script is installed within a Movie Clip called scripts, which appears in the upper right corner of the movie window. This element may look like a button but it's just a convenient place to park the scripts. In a real project we'd probably make this Movie Clip invisible.
Some scripters prefer to place general scripts in the first frame of a layer devoted exclusively to scripting. This is an even simpler solution.
Finally, in ActionScript 3.0, we can place common scripts within a document class associated with the main timeline. This is the best solution of all, and we'll come to it eventually.
Also on the right side you'll find a button marked Repeat. This button invokes a function defined within the Scripts Movie Clip. This allows you to re-run the demonstration function as many times as you like.
The first demonstration movie below shows the results of Math.random() invoked four times.
By themselves, long decimal numbers between 0 and 1 are not much help in controlling Flash movies. To select from among a range of elements, you'll probably want to match the output of Math.random() against a series of tests, as in this example:
if(randyItem == 0) loadMovie("firstCourse.swf",0);
if(randyItem == 1) loadMovie("secondCourse.swf",0);
if(randyItem == 2) loadMovie("thirdCourse.swf", 0);
if(randyItem == 3) loadMovie("fourthCourse.swf", 0);
if(randyItem == 4) loadMovie("fifthCourse.swf", 0);
These five lines allow you to choose a new movie at random and load it over the present movie.
As you can see, the if tests here assume we're dealing with integers. Two steps are required to get from the raw output of Math.random() to useful integer values.
First, multiply the output of Math.random() by the number of items from which you wish to choose. In the example above we'd multiple by 5:
randy = Math.random()*5;
Here's what this change does to our initial example:
As you can see, when you multiply an integer like 5 by a messy decimal number like 0.427009978920274, the result is itself a messy decimal (2.13504989460137, to be exact).
Suppose we need a simple integer. To clean up the results, we apply another method of the Math object called floor(). This method rounds the decimal down to the nearest integer. In the case above, it yields 2. The statement looks like this:
randy = Math.floor(Math.random()*5);
Be careful with those embedded parentheses; you have to get them just right. The variable name randy is one to which we've grown attached over the years, but it's entirely arbitrary.
Here are four more iterations of Math.random(), this time multiplied by 5 and rounded down with Math.floor():
If you study the output above, you'll see that the random values may include 0 but never include 5. That's because we're always rounding down. If we wanted to include 5 and exclude 0, we'd use the Math.ceil() function instead of Math.floor(). If we wanted to use floor, to generate a range from 0 to 5, inclusive, we would use 6 as our multiplier term.
Which function you choose depends on what you want to do with the random value. Suppose you want to extract a randomly-chosen value from a specially constructed object called an Array. At heart, Arrays are simply ordered lists, though their numbering systems differ from everyday practice in one respect: they always start at 0.
Arrays are numbered from 0 to one less than their total length--from 0 to 14 for a fifteen-element array. Thus the numbers generated by a random function rounded down with Math.floor() will always correspond to indices of the array. If we write an array containing fifteen words, we can now use Math.floor(Math.random()*15)) to generate a random word list:
Constraining random values
If you re-run the example above a few times, you'll notice that it often contains duplicate items, and that these duplicates may occur one after another. This happens because we're choosing from a relatively small range of options, and because nothing prevents the script from generating the same random integer twice in succession. (Technically speaking, the problem lies with the Math.floor() operation, which takes nearly unique decimals and rounds down to a much smaller range of integers.)
Suppose we want to prevent any value from being chosen twice in succession. To achieve this goal, we first need to track random values, so that we can compare any value generated on a subsequent iteration of the program to the value generated on the previous iteration. Thus along with randy we also use the variable randyOld to store a previous value for comparison.
Now things get a little strange. To eliminate repetitions, we need to check every random number against its predecessor. However, the first time we run the script, there is no predecessor -- that is, unless we assign one in the Constructor of whatever class we are running. Thus we might have, in our variable declaration section, the following:
public var randy:Number = 0; public var randyOld:Number = 0;
We initialize to zero here, but that's an arbitrary choice. As long as both variables are set to the same value, you can use any number you like.
Armed with this recording system, we can write a random number generator that screens out repetitions. It might go something like this:
public function randomOut():void
{
public var randyRange:Number = 10;
private var rangeA:Number;
private var rangeB:Number;
randy = Math.floor(Math.random()*randyRange);
if(randy == randyOld)
{
rangeA = randyRange-randy;
rangeB = randy-1
if((rangeA > rangeB) || (rangeA == rangeB))
{
randy = Math.ceil(Math.random()*rangeA)+randy;
}
else{
randy = Math.floor(Math.random()*rangeB);
}
}
randyOld = randy;
}
Given a specified range (which could have been assigned in the Constructor, by the way), this script generates a random number on that range and then checks for repetition. If the value it just generated, randy, matches its previous output, randyOld, then it assigns values to two variables, rangeA and rangeB. The first is the number of values greater than the current random value, while the second is the number of values lower than the current random.
Next we ask which range is larger, or if the two are equal. If one range is larger than the other, we want to select our new random value from the larger range, to maximize possibilities. We do that by adding randy (the current random value) back to the range, then using the ceil or ceiling function instead of floor so that the new numbers selected will always be greater than the current random, which we want to avoid.
If both ranges are the same size, we arbitrarily choose the range of higher numbers. If the lower range is larger, we use it.
Finally, we assign to the record variable randyOld the new value of randy we just generated, so that we can test the next random candidate against it.
A simpler method for constraining randoms
If you think about it, you'll see that the method we just described can be extended to multiple degrees of constraint, by adding more tracking variables. Thus you could write a randomizer that would generate unique choices for two, three, or as many iterations as you like.
However, things get complicated pretty quickly when you introduce so many variables. If you're content to restrict your random constraint to one level -- that is, to rule out ever picking the same number twice in succession -- then there's a simpler method.
public function randomOut():void
{
public var randyRange:Number = 10;
while(randy == randyOld)
{
randy = Math.floor(Math.random()*randyRange);
}
randyOld = randy;
}
Much simpler, no? This method simply says, if the current random value and the previous random value are the same, pick again. Assuming the two variables are initialized to the same value, the condition will always be met the first time the script runs; and since we set the two variables equal to each other once we've found a non-repeating value, the condition is always met subsequently.
This method works quite well, especially if you set the range of selection (randyRange in this case) to some reasonably large number. Technically speaking, though, this method could throw the Flash Player into a prolonged loop, if for some reason it happens to pick the same number several million times in succession. Not very likely, so entirely safe for our purposes; but the simpler algorithm described above is not suitable for large-scale applications, for instance, those involving thousands of simultaneous users. If you're building on that scale, use the more complicated version.
|
|