Chapter 4. Numbers and Math
Introduction
Although numbers aren’t always in the spotlight, don’t overlook their power and importance in your code. Numbers come in all shapes and sizes—from binary to decimal to hexadecimal. Each type of representation has its own particular niche in which it is most valuable. For example, hexadecimal numbers are often used to represent RGB color values because they make it easy to discern each of the three color components. (See Recipe 4.2 to learn how to convert between different number bases.)
Closely related to numbers is the subject of mathematics. Without mathematical operations, your Flash movies would be rather dull. Simple operations such as addition and subtraction are essential to even the most basic ActionScript applications, and more advanced math, such as random number generation and trigonometric calculations, is equally essential to advanced applications.
ActionScript 3.0 has three basic numeric types: number, int, and uint. number is for any floating-point numbers, whereas int and uint are for integers (whole numbers). The distinction between int and uint is that int is the set of negative and non-negative integers, while uint is the set of non-negative integers (unsigned integers).
Representing Numbers in Different Bases
Solution
Hexadecimal literals start with 0x
(where the first character is a zero, not
an “oh”), and octal literals start with 0
(again, zero, not “oh”). Binary numbers
can’t be represented directly, but you can either specify their octal
or hexadecimal equivalent or use the
parseInt() function to convert a string to
a number.
Discussion
You can represent numbers in ActionScript using whichever format
is most convenient, such as decimal or hexadecimal notation. For
example, if you set the value of the Sprite.rotation
property, it is most convenient to use a decimal number:
rectangleSprite.rotation = 180;
On the other hand, hexadecimal numbers are useful for specifying
RGB colors. For example, you can set the rgb
value for a ColorTransform object in hexadecimal
notation (in this example, 0xF612AB
is a hex number representing a shade of pink):
var pink:ColorTransform = new ColorTransform(); pink.rgb = 0xF612AB;
Any numeric literal starting with 0X
or 0x
(where the first character is a zero, not an “oh”) is presumed to be a
hexadecimal number (i.e.,
hex or base-16). Allowable digits
in a hexadecimal number are 0 through 9 and A through F (upper- and
lowercase letters are equivalent, meaning 0xFF
is the same as 0xff
).
Any numeric literal starting with 0
(again, zero not “oh”), but not 0x
or 0X
,
is presumed to be an octal number
(i.e., base-8). Allowable digits in an octal number are 0 through 7;
for example, 0777
is an octal
number. Most developers don’t ever use octal numbers in ActionScript.
For most developers it’s simply far more convenient to represent most
numbers as decimal numbers (base-10), except color values for which it
is generally more convenient to use hexadecimal representation. There
aren’t many common examples for which octal representation is more
convenient than decimal or hexadecimal.
The only digits allowed in binary
numbers (i.e., base-2) are 0 and 1. Although you can’t
specify a binary number directly, you can specify its hexadecimal
equivalent. Four binary digits (bits) are equivalent to a single hex digit.
For example, 1111
in binary is
equivalent to F
in hex (15 in
decimal). The number 11111111
in
binary is equivalent to FF
in hex
(255 in decimal). Binary numbers (or rather their hexadecimal
equivalents) are most commonly used with ActionScript’s bitwise operators ( &
, |
, ^
, >>
, <<
, and >>>
).
See Also
Converting Between Different Number Systems
Problem
You want to convert a number between different bases (such as decimal, binary, hexadecimal, etc.).
Solution
Use the parseInt() function with the radix parameter (the radix is the number’s base) to convert a string to a decimal representation. Use the toString() method of a Number, uint, or int object with the radix parameter to convert a decimal number to a string representation of the value in another base.
Discussion
No matter how you set a number value in ActionScript, the result is always retrieved as a decimal (base-10) number:
// Create a Color
object
var pink:ColorTransform = new ColorTransform();
// Set the RGB value as a hexadecimal
pink.rgb = 0xF612AB;
// This displays the value as decimal: 16126635
trace(pink.rgb);
However, if you want to output a value in a different base, you
can use toString(
radix
) for a Number, uint, or int object to convert any number value to a
string representing that number in the specified base.
These two examples convert numeric literals to uint objects and output the string representations in base-2 (binary) and base-16 (hexadecimal) format.
// Theradix
is 2, so output as binary trace(new uint(51).toString(2)); // Displays: 110011 // Theradix
is 16, so output as hex trace(new uint(25).toString(16)); // Displays: 19
When using the toString() method with a variable that contains a numeric literal value, Flash automatically creates a new Number, uint, or int object before calling the toString() method. Although it’s not typically the best practice, it is not technically wrong, and in most applications the differences are negligible. This example assigns a primitive number to a variable and calls the toString() method to output the value in hexadecimal:
var quantity:Number = 164; trace(quantity.toString(16)); // Displays: a4
Tip
The results from these examples are not numeric literals, but rather strings, such as 110011, 19, and A4.
The following example sets the RGB value of a ColorTransform object, calls toString() on the result to display the value as a hexadecimal (as it had been input, although the alpha digits are converted to lowercase, and the result is a string, not a number):
// Create a Color
object
var pink:Color = new ColorTransform();
// Set the RGB value as a hexadecimal
pink.rgb = 0xF612AB;
trace(pink.rgb.toString(16)); // Displays: f612ab
The valid range for the radix
parameter of the toString()
method is from 2 to 36. If you call toString() with no
radix
parameter or an invalid value,
decimal format (base-10) is assumed.
You can achieve the inverse of the toString() process using the parseInt() function with the
radix
parameter. The parseInt() function takes a string value
and returns a number. This is useful if you want to work with base
inputs other than 10.
These examples parse the numbers from the string in base-2 (binary), base-16 (hexadecimal), and base-10, respectively (note that the result is always a decimal):
trace(parseInt("110011", 2)); // Displays: 51 trace(parseInt("19", 16)); // Displays: 25 trace(parseInt("17", 10)); // Displays: 17
If omitted, the radix
is assumed to
be 10, unless the string starts with 0x
, 0X
,
or 0
, in which case hexadecimal or
octal is assumed:
trace(parseInt("0x12")); // Theradix
is implicitly 16. Displays: 18 trace(parseInt("017")); // Theradix
is implicitly 8. Displays: 15
An explicit radix
overrides an
implicit one. In the next example, the result is 0, not 12. When the
number is treated base-10, conversion stops when a non-numeric
character—the x—is
encountered:
// The number is treated as a decimal, not a hexadecimal number trace(parseInt("0x12", 10)); // Displays: 0 (not 12 or 18)
Here, although the leading zero doesn’t prevent the remainder digits from being interpreted, it is treated as a decimal number, not an octal number:
// The number is treated as a decimal, not an octal number trace(parseInt("017", 10)); // Displays: 17 (not 15)
Don’t forget to include 0
,
0x
, or an explicit radix. The
following interprets the string as a decimal and returns NaN
(not a number) because “A” can’t be
converted to an integer:
trace(parseInt("A9FC9C")); // NaN
Rounding Numbers
Problem
You want to round a number to the nearest integer, decimal place, or interval (such as to the nearest multiple of five).
Solution
Use Math.round() to round a number to the nearest integer. Use Math.floor() and Math.ceil() to round a number down or up. Use a custom NumberUtilities.round() method to round a number to a specified number of decimal places or to a specified multiple.
Discussion
There are numerous reasons to round numbers. For example, when displaying the results of a calculation, you might display only the intended precision. Because all arithmetic in ActionScript is performed with floating-point numbers, some calculations result in unexpected floating-point numbers that must be rounded. For example, the result of a calculation may be 3.9999999 in practice, even though it should be 4.0 in theory.
The Math.round() method returns the nearest integer value of any parameter passed to it:
trace(Math.round(204.499)); // Displays: 204 trace(Math.round(401.5)); // Displays: 402
The Math.floor() method rounds down, and the Math.ceil() method rounds up:
trace(Math.floor(204.99)); // Displays: 204 trace(Math.ceil(401.01)); // Displays: 402
To round a number to the nearest decimal place:
Decide the number of decimal places to which you want the number rounded. For example, if you want to round 90.337 to 90.34, then you want to round to two decimal places, which means you want to round to the nearest .01.
Divide the input value by the number chosen in Step 1 (in this case, .01).
Use Math.round() to round the calculated value from Step 2 to the nearest integer.
Multiple the result of Step 3 by the same value that you used to divide in Step 2.
For example, to round 90.337 to two decimal places, you could use:
trace (Math.round(90.337 / .01) * .01); // Displays: 9.34
You can use the identical math to round a number to the nearest multiple of an integer.
For example, this rounds 92.5 to the nearest multiple of 5:
trace (Math.round(92.5 / 5) * 5); // Displays: 95
As another example, this rounds 92.5 to the nearest multiple of 10:
trace (Math.round(92.5 / 10) * 10); // Displays: 90
In practice you are likely to find it is much simpler to use a custom NumberUtilities.round() method that encapsulates this functionality. The custom method takes two parameters:
The NumberUtilities class is in the ascb.util package, so the first thing you’ll want to add to any file that uses the class is an import statement. Here is an example of how to use the NumberUtilities.round() method (the following code assumes that you’ve imported ascb.util.NumberUtilities):
trace(NumberUtilities.round(Math.PI)); // Displays: 3 trace(NumberUtilities.round(Math.PI, .01)); // Displays: 3.14 trace(NumberUtilities.round(Math.PI, .0001)); // Displays: 3.1416 trace(NumberUtilities.round(123.456, 1)); // Displays: 123 trace(NumberUtilities.round(123.456, 6)); // Displays: 126 trace(NumberUtilities.round(123.456, .01)); // Displays: 123.46
Inserting Leading or Trailing Zeros or Spaces
Discussion
You might need to format numbers with leading or trailing zeros or spaces for display purposes, such as when displaying times or dates. For example, you would want to format 6 hours and 3 minutes as 6:03 or 06:03, not 6:3. Additionally, sometimes you’ll want to apply leading and/or trailing spaces to align the columns of several numbers; for example:
123456789 1234567 12345
Although you can certainly work out the algorithms on your own to add leading or trailing characters, you’ll likely find working with a NumberFormat object much faster, simpler, and more flexible. The NumberFormat class is a custom class included with the downloads for this book at http://www.rightactionscript.com/ascb. That class is part of the ascb.util package, so the first thing you’ll want to do is make sure you have an import statement:
import ascb.util.NumberFormat;
Next you need to determine the mask that you’ll use to format
the number. The mask can consist of zeros (0
), pound signs (#
),
dots (.
), and
commas (,
); any other
characters are disregarded.
- Zeros (
0
) Placeholders that are either filled with the corresponding digit or a zero.
- Pound signs (
#
) Placeholders that are either filled with the corresponding digit or a space.
- Dots (
.
) Decimal point placeholders; they can be replaced by the localized decimal point symbol.
- Commas (
,
) Placeholders for grouping symbols; they are replaced by the localized grouping symbol.
To better understand this, it can be helpful to take a look at some examples; consider the following mask:
##,###.0000
When the preceding mask is used with the numbers 1.2345, 12.345, 123.45, 1234.5, and 12345, the results are as follows (assuming that the localized settings apply commas as grouping symbols and dots as decimal points):
1.2345 12.3450 123.4500 1,234.5000 12,345.0000
You can set the mask for a NumberFormat object in several ways. You can specify the mask as a parameter when you construct the object, as follows:
var styler:NumberFormat = new NumberFormat("##,###.0000");
Additionally, you can use the mask property of the object to change the mask at any point:
styler.mask = "##.00";
Tip
The mask property is a read-write property, so you can also retrieve the current mask string by reading the value from the property.
Once a mask has been applied to a NumberFormat object, you can format any number value by calling the format() method and passing the number as a parameter:
trace(styler.format(12345);
The following code is a complete, working example that illustrates the features of the NumberFormat class that have been discussed so far:
var styler:NumberFormat = new NumberFormat("#,###,###,###"); trace(styler.format(1)); trace(styler.format(12)); trace(styler.format(123)); trace(styler.format(1234)); styler.mask = "#,###,###,###.0000"; trace(styler.format(12345)); trace(styler.format(123456)); trace(styler.format(1234567)); trace(styler.format(12345678)); trace(styler.format(123456789));
The output from the preceding example is as follows (assuming U.S.-style localization settings):
1 12 123 1,234 12,345.0000 123,456.0000 1,234,567.0000 12,345,678.0000 123,456,789.0000
By default, NumberFormat objects attempt to automatically localize the return values. If the Flash Player is running on a U.S. English operating system, the NumberFormat class uses commas as grouping symbols and dots as decimal points. On the other hand, if the computer is running a French operating system, the symbols are reversed; dots for grouping symbols and commas for decimal points. There are several reasons why you may opt to override the automatic localization, including:
You want the numbers to be formatted in a standard way regardless of the operating system on which the Flash application is run.
Automatic localization doesn’t work properly. This may occur in some situations for at least two reasons:
The Locale class (the class used by NumberFormat to determine the correct localization settings) may not include some languages/countries.
The Flash Player does not report very specific settings. It only reports the language code. Because of that, it may be difficult—to nearly impossible—to correctly calculate the locale to use.
There are a variety of ways you can override the automatic localization settings:
Pass a Locale object to the format() method as a second parameter. The format() method then uses the settings from that Locale object instead of the automatic settings. This option works well when you want to apply different custom localization settings each time you call the format() method. You can create a Locale object by using the constructor. With no parameters, the Locale object uses automatic localization detection, so you’ll want to pass it one or two parameters. The first parameter is the language code (e.g., en). The second parameter is the country code (e.g., US), also called the variant. You should only specify the variant if there are different regions that use the same language, but use different formatting. For example, the language code es (Spanish) could potentially apply to many countries, including Mexico (MX) and Spain (ES)—both of which use different symbols to format numbers.
Set the Locale.slanguage and/or Locale.svariant properties to set the localization properties globally. You don’t need to specify any additional parameters when calling format() with this option. Simply assign values to the static properties, Locale.slanguage and/or Locale.svariant; those settings affect any subsequent calls to format().
Use a symbols object as the second parameter when calling format(). The symbols object should have two properties: group and decimal. The values for those properties allow you to define the symbols to use when formatting the number. This option is best when the Locale object does not have settings for the locale that you want and/or when you want to use custom formatting symbols.
Tip
The Locale class is in the ascb.util package, so be sure to import that class if you want to use it.
The following example illustrates some of the ways you can override the automatic localization settings:
var styler:NumberFormat = new NumberFormat("#,###,###,###.00"); Locale.slanguage = "fr"; trace(styler.format(1234)); trace(styler.format(12345, {group: ",", decimal: "."})); trace(styler.format(123456)); Locale.slanguage = "en"; trace(styler.format(1234567)); trace(styler.format(12345678, new Locale("es", "ES"))); trace(styler.format(123456789, {group: "|", decimal: ","}));
The preceding code displays the following:
1.234,00 12,345.00 123.456,00 1,234,567.00 12.345.678,00 123|456|789,00
Formatting Numbers for Display Without a Mask
Discussion
Recipe 4.4 discusses complex ways to format numbers as strings, including using masks and applying leading and trailing zeros and spaces. Sometimes, however, you just want to format a number without those complexities. The NumberFormat class provides that simplicity as well. If no mask is applied to a NumberFormat object, then the format() method applies basic, localized formatting to a number, as shown in the following example:
var styler:NumberFormat = new NumberFormat(); trace(styler.format(12.3)); trace(styler.format(123.4)); trace(styler.format(1234.5)); trace(styler.format(12345.6));
Notice that a mask wasn’t applied to the NumberFormat object at any point. Assuming U.S.-style formatting, the preceding code outputs the following:
12.3 123.4 1,234.5 12,345.6
As with the other use of the format() method (discussed in Recipe 4.4), this usage attempts to use automatic localization detection. However, the same issues may be applicable. You may prefer to override the automatic localization settings, and you can accomplish that by using the same techniques discussed in Recipe 4.4, as illustrated with the following example:
var styler:NumberFormat = new NumberFormat(); Locale.slanguage = "fr"; trace(styler.format(1234, new Locale("en"))); trace(styler.format(12345, {group: ":", decimal: "|"})); trace(styler.format(123456));
The output from the preceding code is as follows:
1,234 12:345 123.456
See Also
Recipes 4.3 and 4.4 can be used to ensure a
certain number of digits are displayed past the decimal point. Then
aligning numbers is simply a matter of setting the text field’s format
to right justification using the TextFormat
.align
property. Also refer to
Recipe 4.6.
Formatting Currency Amounts
Discussion
Unlike some other languages, such as ColdFusion, ActionScript does not have a built-in function for formatting numbers as currency amounts. However, the custom NumberFormat class includes a currencyFormat() method that takes care of basic currency formatting for you.
The currencyFormat() method requires at least one parameter; the number you want to format as currency. The following example illustrates the simplest use of currencyFormat():
var styler:NumberFormat = new NumberFormat(); trace(styler.currencyFormat(123456));
Assuming that the preceding code is run on a U.S. English computer, the output is as follows:
$123,456.00
As with the format() method of the NumberFormat class discussed in Recipes 4.4 and 4.5, the currencyFormat() method uses automatic localization detection settings. Therefore, if the preceding code is run on a computer in Spain running a Spanish operating system, the output is as follows:
123.456,00
However, the Locale class (which is responsible for determining the locale from where the application is being run) may not be able to correctly detect the locale. Furthermore, you may simply want to override automatic localization so you get a consistent value regardless of where the application is run. There are several ways you can override the automatic localization detection; the same ways that you can override the localization settings when using the format() method:
Use a Locale object as the second parameter when calling currencyFormat().
Assign global values to the Locale.slanguage and/or Locale.svariant properties.
Use a symbols object as the second parameter when calling currencyFormat().
The symbols object for currencyFormat() is slightly more complex
than the symbols object for the format() object. If you use a symbols
object with currencyFormat(),
you should include the following four properties: group
, decimal
, currency
, and before
. The group
and decimal
properties act just as with the
format() method. The currency
property should have a value of the
currency symbol you want to use. The before
property is a Boolean value in which
true
means the currency symbol
should appear before the numbers, and false
means the symbol should appear after
the numbers.
The following is an example of different ways of overriding the localization settings with currencyFormat():
var styler:NumberFormat = new NumberFormat(); trace(styler.currencyFormat(123456)); Locale.slanguage = "nl"; trace(styler.currencyFormat(123456)); trace(styler.currencyFormat(123456, new Locale("sv"))); trace(styler.currencyFormat(123456, {group: ",", decimal: ".", currency: "@", before: false}));
The preceding code outputs the following results:
$123,456.00 123.456,00 123,456.00kr 123,456.00@
Generating a Random Number
Solution
Use Math.random() to generate a random number between 0 and .999999. Optionally, use the NumberUtilities.random() method to generate a random number within a specific range.
Discussion
You can use the Math.random() method to generate a random floating-point number from 0 to 0.999999999. In most cases, however, programs call for a random integer, not a random floating-point number. Furthermore, you may want a random value within a specific range. If you do want a random floating-point number, you’ll need to specify its precision (the number of decimal places).
The simplest way to generate random numbers within a range and to a specified precision is to use the custom NumberUtilities.random() method. This method accepts up to three parameters, described as follows:
minimum
The smallest value in the range specified as a Number.
maximum
The largest value in the range specified as a Number.
roundToInterval
The optional interval to use for rounding. If omitted, numbers are rounded to the nearest integer. You can specify integer intervals to round to integer multiples. You can also specify numbers smaller than 1 to round to numbers with decimal places.
Tip
The NumberUtilities class
is in the ascb.util package, so be sure to include an
import
statement.
The following example illustrates some uses of the round() method:
// Generate a random integer from 0 to 100. trace(NumberUtilities.random(0, 100)); // Generate a random multiple of 5 from 0 to 100. trace(NumberUtilities.random(0, 100, 5)); // Generate a random number from -10 to 10, rounded to the // nearest tenth. trace(NumberUtilities.random(-10, 10, .1)); // Generate a random number from -1 to 1, rounded to the // nearest five-hundredth. trace(NumberUtilities.random(-1, 1, .05));
To test that the random numbers generated by the NumberUtilities.random() method are evenly distributed, you can use a script such as the following:
package { import flash.display.Sprite; import ascb.util.NumberUtilities; import flash.utils.Timer; import flash.events.TimerEvent; public class RandomNumberTest extends Sprite { private var _total:uint; private var _numbers:Object public function RandomNumberTest() { var timer:Timer = new Timer(10); timer.addEventListener(TimerEvent.TIMER, randomizer); timer.start(); _total = 0; _numbers = new Object(); } private function randomizer(event:TimerEvent):void { var randomNumber:Number = NumberUtilities.random(1, 10, 1); _total++; if(_numbers[randomNumber] == undefined) { _numbers[randomNumber] = 0; } _numbers[randomNumber]++; trace("random number: " + randomNumber); var item:String; for(item in _numbers) { trace("\\t" + item + ": " + Math.round(100 * _numbers[item]/_total)); } } } }
Simulating a Coin Toss
Problem
You want to simulate tossing a coin or some other Boolean (true
/false
) event in which you expect a 50
percent chance of either outcome.
Solution
Use the NumberUtilities.random() method to generate an integer that is either 0 or 1, and then correlate each possible answer with one of the desired results.
Discussion
You can use the random() method
from Recipe 4.7 to generate
a random integer in the specified range. To relate this result to an
event that has two possible states, such as a coin toss (heads
or tails
) or a Boolean condition (true
or false
), treat each random integer as
representing one of the possible states. By convention, programmers
use 0 to represent one state (such as “off”) and 1 to represent the
opposite state (such as “on”), although you can use 1 and 2 if you
prefer. For example, here’s how you could simulate a coin toss:
package { import flash.display.Sprite; import flash.text.TextField; import flash.events.MouseEvent; import ascb.util.NumberUtilities; public class CoinExample extends Sprite { private var _field:TextField; public function CoinExample() { _field = new TextField(); _field.autoSize = "left"; addChild(_field); var circle:Sprite = new Sprite(); circle.graphics.beginFill(0, 100); circle.graphics.drawCircle(100, 100, 100); circle.graphics.endFill(); circle.addEventListener(MouseEvent.CLICK, onClick); addChild(circle); } private function onClick(event:MouseEvent):void { var randomNumber:Number = NumberUtilities.random(0, 1); _field.text = (randomNumber == 0) ? "heads" : "tails"; } } }
In the following example, a function is used to test the coinFlip() routine to see if it is reasonably evenhanded. Do you expect a perfect 50/50 distribution regardless of the number of coin tosses? Test it and see.
package { import flash.display.Sprite; import flash.text.TextField; import ascb.util.NumberUtilities; public class CoinTest extends Sprite { private var _field:TextField; public function CoinTest() { _field = new TextField(); _field.autoSize = "left"; addChild(_field); var heads:Number = 0; var tails:Number = 0; var randomNumber:Number; for(var i:Number = 0; i < 10000; i++) { randomNumber = NumberUtilities.random(0, 1); if(randomNumber == 0) { heads++; } else { tails++; } } _field.text = "heads: " + heads + ", tails: " + tails; } } }
If you are testing the value of your random number, be sure to save the result in a variable (and test that!) rather than generate a new random number each time you perform the test.
The following example is wrong because it generates independent random numbers in the dependent else if clauses. In some cases, none of the conditions are true and the method returns an empty string:
package { import flash.display.Sprite; import ascb.util.NumberUtilities; public class RandomLetter extends Sprite { public function RandomLetter() { for(var i:Number = 0; i < 10000; i++) { trace(getRandomLetter()); } } private function getRandomLetter():String { if(NumberUtilities.random(0, 2) == 0) { return "A"; } else if(NumberUtilities.random(0, 2) == 1) { return "B"; } else if(NumberUtilities.random(0, 2) == 2) { return "C"; } // It's possible that none of the preceding will evaluate to true, // and the method will reach this point without returning a valid // string. return ""; } } }
This is the correct way to accomplish the goal:
package { import flash.display.Sprite; import ascb.util.NumberUtilities; public class RandomLetter extends Sprite { public function RandomLetter() { for(var i:uint = 0; i < 10000; i++) { trace(getRandomLetter()); } } private function getRandomLetter():String { // Assign the return value from random() to a variable // before testing the value. var randomInteger:uint = NumberUtilities.random(0, 2); if(randomInteger == 0) { return "A"; } else if(randomInteger == 1) { return "B"; } else if(randomInteger == 2) { return "C"; } return ""; } } }
See Also
Simulating Dice
Discussion
You can use the random() method from Recipe 4.7 to generate random integer values to simulate rolling a die or dice in your Flash movies. Mimicking the rolling of dice is an important feature in many games you might create using ActionScript, and the random() method makes your job easy.
Warning
NumberUtilities.random(1, 12) does not correctly simulate a pair of six-sided dice because the results must be between 2 and 12, not 1 and 12. Does NumberUtilities.random(2, 12) give the correct result? No, it does not. NumberUtilities.random(2, 12) results in a smooth distribution of numbers from 2 to 12, whereas in games played with two dice, 7 is much more common than 2 or 12. Therefore, you must simulate each die separately and then add the result together. Furthermore, in many games, such as backgammon, game play depends on the individual value of each die, not simply the total of both dice, so you’ll want to keep them separate.
It is not uncommon to want to generate a random number and then
store it for later use. If you want to reuse an existing random
number, be sure to save the result rather than generating a new random
number. Note the difference in these two scenarios. In the first
scenario, dice
always is the sum of
die1
plus die2
:
var die1:uint = NumberUtilities.random(1, 6); var die2:uint = NumberUtilities.random(1, 6); var dice:uint = die1 + die2;
In the following scenario, there is no relation between the
value of dice
and the earlier
random values stored in die1
and
die2
. In other words, even if
die1
and die2
add up to 7, dice
stores a completely different value
between 2 and 12:
var die1:uint = NumberUtilities.random(1, 6); var die2:uint = NumberUtilities.random(1, 6); var dice:uint = NumberUtilities.random(1, 6) + NumberUtilities.random(1, 6);
You can call NumberUtilities.random() with any range to simulate a multisided die. Here it has a range from 1 to 15 and generates a random number as though the user is rolling a 15-sided die, as might be found in a role-playing game:
var die1:uint = NumberUtilities.random(1, 15);
The following code uses the NumberUtilities.random() method in conjunction with programmatic drawing to create a visual representation of a single die:
package { import flash.display.Sprite; import flash.text.TextField; import flash.events.MouseEvent; import ascb.util.NumberUtilities; public class NumbersAndMath extends Sprite { var _die:Sprite; var _value:uint; public function NumbersAndMath() { _die = new Sprite(); addChild(_die); _die.addEventListener(MouseEvent.CLICK, rollDie); rollDie(null); } private function rollDie(event:MouseEvent):void { _value = NumberUtilities.random(1, 6); _die.graphics.clear(); _die.graphics.lineStyle(); _die.graphics.beginFill(0xFFFFFF); _die.graphics.drawRect(0, 0, 50, 50); _die.graphics.endFill(); _die.graphics.beginFill(0x000000); if(_value == 1 || _value == 3 || _value == 5) { _die.graphics.drawCircle(25, 25, 4); } if(_value == 2 || _value == 3 || _value == 4 || _value == 5 || _value == 6) { _die.graphics.drawCircle(11, 11, 4); _die.graphics.drawCircle(39, 39, 4); } if(_value == 4 || _value == 5 || _value == 6) { _die.graphics.drawCircle(11, 39, 4); _die.graphics.drawCircle(39, 11, 4); } if(_value == 6) { _die.graphics.drawCircle(11, 25, 4); _die.graphics.drawCircle(39, 25, 4); } } } }
Running the preceding code results in a single, clickable die drawn on the stage. Each time the user clicks the die, the value changes.
See Also
Simulating Playing Cards
Problem
You want to use ActionScript to deal cards for a card game using a standard 52-card deck (without Jokers).
Discussion
Playing cards requires a greater degree of sophistication than, say, rolling a couple dice. Therefore, to work with playing cards within your Flash applications, you should use a custom Cards class.
Tip
The Cards class is in the ascb.play package, and therefore you should be sure to import the class before you try to use it in your code.
import ascb.play.Cards;
You can create a new Cards object using the constructor as follows:
var cards:Cards = new Cards();
By default, a Cards object creates a standard deck of 52 playing cards. Next you need to deal the cards by using the deal() method. The deal() method returns an array of CardHand objects. You should specify at least one parameter when calling the deal() method; the number of hands to deal.
// Deal four hands. var hands:Array = cards.deal(4);
By default, the deal() method deals every card in the deck (except when 52 is not evenly divisible by the number of hands). Some card games, such as Euchre, require fewer cards in each hand with some cards remaining in the deck. You can, therefore, specify a second, optional parameter that determines the number of cards per hand:
// Deal four hands with five cards each. var hands:Array = cards.deal(4, 5);
Each CardHand object is an array of Card objects. The CardHand class also provides an interface to draw and discard cards from the deck from which the hand was originally dealt. You can use the discard() method by specifying a list of card indices as parameters. The cards then are removed from the hand and added back to the bottom of the deck:
// Discard the cards with indices 0 and 4 from the CardHand object // stored in the first element of the aHands array. hands[0].discard(0, 4);
Conversely, you can use the draw() method to draw cards from the top of the original deck. The draw() method draws one card by default if no parameters are specified. If you want to draw more than one card at a time, you can specify the number of cards as a parameter:
// Draw one card from the top of the deck, and add it to the // hand stored in the first element of the hands array. hands[0].draw(); // Draw four cards from the top of the deck, and add them to // the hand stored in the fourth element of the aHands array. hands[3].draw(4);
You can use the length
property of a CardHand object to
retrieve the number of cards in a hand, and you can use the getCardAt() method to retrieve a card at
a specified index.
As mentioned, each CardHand
is composed of Card objects.
Card objects, in turn, have four
properties: value
, name
, suit
, and display
. The value
property returns a numeric value from
0 to 12, where 0 is a two card and 12 is an Ace. The name
property returns the name of the card,
such as 2, 10, Q, or A. The suit
property returns clubs, diamonds, hearts, or spades. The display
property returns the name and suit
values joined with a space. The following example code illustrates use
of the Cards class:
package { import flash.display.Sprite; import ascb.play.Cards; import flash.util.trace; public class CardExample extends Sprite { public function CardExample() { var cards:Cards = new Cards(); var hands:Array = cards.deal(4, 10); var i:uint; var j:uint; for(i = 0; i < hands.length; i++) { trace("hand " + i); for(j = 0; j < hands[i].length; j++) { trace(hands[i].getCardAt(j)); } } } } }
Generating a Unique Number
Problem
You want to generate a unique number, such as a number to append to a URL to prevent caching of the URL.
Discussion
Unique numbers are most commonly used to generate a unique URL (to prevent it from being cached). That is, by appending a unique number to the end of a URL, it is unlike any previous URL; therefore, the browser obtains the data from the remote server instead of the local cache.
The NumberUtilities.getUnique() method returns a number based on the current epoch milliseconds. (The following example assumes you’ve imported ascb.util.NumberUtilities.)
// Display a unique number. trace(NumberUtilities.getUnique());
In most circumstances the preceding code returns the current epoch in milliseconds. However, it is possible that you may want to generate a set of unique numbers in less than a millisecond of processing time. In that case, you’ll find that the getUnique() method adds a random number to the epoch milliseconds to ensure a unique number. The following example generates more than one number within the same millisecond:
for(var i:Number = 0; i < 100; i++) { trace(NumberUtilities.getUnique()); }
Converting Angle Measurements
Problem
You want to work with angle values in ActionScript, but you must convert to the proper units.
Discussion
The _rotation
property of a movie clip object is
measured in degrees. Every other angle measurement in ActionScript,
however, uses radians, not degrees. This can be a problem in two ways.
First, if you want to set the _rotation
property based on the output of
one of ActionScript’s trigonometric methods, you must convert the
value from radians to degrees. Second, humans generally prefer to work
in degrees, which we must convert to radians before feeding to any of
the trigonometric methods. Fortunately, the conversion between radians
and degrees is simple. To convert from radians to degrees, you need
only to multiply by 180/Math.PI. Likewise, to convert from degrees to
radians you need only to multiply by the inverse: Math.PI/180.
However, you may find it more convenient to simply use the custom
Unit and Converter classes.
The Unit and Converter classes are two custom classes
found in the ascb.unit package
that facilitate conversions between various units of measurement,
including degrees, radians, and gradians (there are 400 gradians in a
complete circle). The first step is to create a Unit instance that describes the type of
unit from which you want to convert. The Unit class provides a large group of
constants that make it very convenient. The
Unit.DEGREE
,
Unit.RADIAN
, and
Unit.GRADIAN
constants return new Unit objects
that represent degrees, radians, and gradians, respectively. Unit objects have a handful of properties,
including name
, category
, label
, and
labelPlural
:
var degree:Unit = Unit.DEGREE; trace(degree.name); // Displays: degree trace(degree.category); // Displays: angle trace(degree.label); // Displays: degree trace(degree.labelPlural); // Displays: degrees
Once you’ve gotten a Unit instance that represents the unit from which you want to convert, you can then retrieve a Converter instance that can convert to a specific type of unit. Use the getConverterTo() method, and pass it a reference to a Unit object that represents the type of unit to which you want to convert. For example, the following code creates a Converter object that can convert from degrees to radians:
var converter:Converter = Unit.DEGREE.getConverterTo(Unit.RADIAN);
Once you’ve created a Converter instance, you can run the convert() method, specifying a value you want to convert; for example:
trace(converter.convert(90));
The convertWithLabel() method converts the value to a string that includes the appropriate label in the event that you want to display the value:
var converterToRadians:Converter = Unit.DEGREE.getConverterTo(Unit.RADIAN); var converterToDegrees:Converter = Unit.RADIAN.getConverterTo(Unit.DEGREE); trace(converterToRadians.convertWithLabel(1)); trace(converterToRadians.convertWithLabel(57.2957795130823)); trace(converterToDegrees.convertWithLabel(1)); trace(converterToDegrees.convertWithLabel(0.0174532925199433)); /* Displays: 0.0174532925199433 radians 1 radian 57.2957795130823 degrees 1 degree */
In the event that you find it more convenient to convert in the opposite direction, you can also use the getConverterFrom() method to create a Converter instance that converts one unit to another, for example:
var converter:Converter = Unit.DEGREE.getConverterFrom(Unit.GRADIAN); trace(converter.convert(100)); trace(converter.convert(23));
Calculating the Distance Between Two Points
Discussion
You can calculate the distance (in a straight line) from any two points by using the Pythagorean Theorem. The Pythagorean Theorem states that in any right triangle (a triangle in which one of the angles is 90 degrees) the length of the hypotenuse (the long side) is equal to the square root of the sum of the squares of the two other sides (referred to as the legs of the triangle). The Pythagorean theorem is written as:
a2 + b2 = c2
You can use this formula to calculate the distance between any two points, where a is the difference between the points’ X coordinates, b is the difference between their Y coordinates, and c (the distance to be determined) equals the square root of (a 2 + b 2). In ActionScript, this is written as:
var c:Number = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
How do you calculate the distance between two points using a right triangle? Although it might not seem immediately obvious, you can form an imaginary right triangle using any two points in the Flash coordinate system, as shown in Figure 4-1.
The hypotenuse of the imaginary triangle is formed by the line connecting the two points. The legs of the triangle are formed by lines extending horizontally and vertically from the two points. You can find the lengths of the legs by finding the differences between the X and Y coordinates. The length of leg a is determined by the difference in the points’ X coordinates, and the length of leg b is determined by the difference in the points’ Y coordinates. Once you know the lengths of legs a and b, you can use the Pythagorean Theorem to calculate the length of the hypotenuse, c, which represents the distance between the points (our original query).
Determining Points Along a Circle
Problem
You want to calculate the coordinates of a point along a circle, given the circle’s radius and the sweep angle.
Solution
Use the Math.sin() and Math.cos() methods to calculate the coordinates using basic trigonometric ratios.
Discussion
Finding the coordinates of a point along a circle is easy with some trigonometry. So let’s look at the formulas you can use within your ActionScript code and the theory behind them.
Given any point on the Stage—a point we’ll call p0, with coordinates (x0, y0)—plus a distance and the angle from the horizontal, you can find the coordinates of another point—which we’ll call p1, with coordinates (x1, y1)—using some basic trigonometric ratios. The angle is formed between a conceptual line from p0 to p1 and a line parallel to the X axis, as shown in Figure 4-2. The opposite side is the side furthest away from the angle. The adjacent side is the side that forms the angle with the help of the hypotenuse.
If you know the distance between two points and the angle to the horizontal, as shown in Figure 4-2, you can calculate the X and Y coordinates of the destination point using trigonometric functions. The trigonometric sine of the angle is equal to the ratio of the opposite side over the hypotenuse, like so:
sine(angle) = opposite/hypotenuse
Solving for the opposite side’s length, this can be written as:
opposite = sine(angle) * hypotenuse
As you can see in Figure 4-2, the opposite side represents the change in the Y direction.
The trigonometric cosine of the angle is equal to the ratio of the adjacent side over the hypotenuse, like so:
cosine(angle) = adjacent/hypotenuse
Solving for the adjacent side’s length, this can be written as:
adjacent = cosine(angle) * hypotenuse
You can see from Figure 4-2 that the adjacent side represents the change in the X direction.
Because the lengths of the opposite and adjacent sides yield the changes in the X and Y directions, by adding the original X and Y coordinates to these values, you can calculate the coordinates of the new point.
So how does this help in determining a point along a circle’s perimeter? Figure 4-3, which shows the triangle inscribed within a circle emphasizes the equivalency. The triangle’s hypotenuse equates to the circle’s radius, and the triangle’s angle equates to the sweep angle to the point of interest along the circle’s perimeter.
Therefore, the X coordinate of a point along the circle’s perimeter is determined by the radius times the cosine of the angle. The Y coordinate is determined by the radius times the sine of the angle. Here is the ActionScript code for finding the coordinates of p1 when the circle’s radius and center point (p0) are known:
x1 = x0 + (Math.cos(angle) * radius); y1 = y0 + (Math.sin(angle) * radius);
Therefore, these formulas can be used to determine any point along a circle’s perimeter, given the circle’s center point and radius. By changing the angle over time, you can trace the path of a circle.
The following example uses these trigonometric equations to move a sprite around in a circle:
package { import flash.display.Sprite; import ascb.units.Converter; import ascb.units.Unit; import flash.events.Event; public class NumbersAndMath extends Sprite { private var _square:Sprite; private var _angle:uint; public function NumbersAndMath() { _square = new Sprite(); _square.graphics.lineStyle(0); _square.graphics.drawCircle(0, 0, 20); addChild(_square); _angle = 0; addEventListener(Event.ENTER_FRAME, move); } private function move(event:Event):void { var converter:Converter = Unit.DEGREE.getConverterTo(Unit.RADIAN); var angleRadians:Number = converter.convert(_angle); _square.x = Math.cos(angleRadians) * 100 + 200; _square.y = Math.sin(angleRadians) * 100 + 200; _angle++; } } }
Converting Between Units of Measurement
Problem
You want to convert between Fahrenheit and Celsius, pounds and kilograms, or other units of measure.
Discussion
There are various systems of measurement used throughout the world. For example, temperature is commonly measured in both Fahrenheit and Celsius. Weight is sometimes measured in pounds, but quite frequently, a metric system that uses kilograms is employed instead. And distances are likely to be measured in miles instead of kilometers, or inches instead of centimeters. For these reasons, you may need to convert from one unit of measure to another.
Tip
Technically, pounds are a measurement of weight, while kilograms are a measurement of mass. Weight is a force that changes as the acceleration due to gravitational changes, whereas mass is constant regardless of the effects of gravity. The effect is that an object’s weight on the moon and Earth differ since there are different effects, thanks to gravity, but the mass of an object remains the same. However, on the surface of the Earth, mass and weight often are used interchangeably.
Each of these conversions has its own algorithm. For example, to convert from Centigrade to Fahrenheit, you should multiply by 9, divide by 5, and then add 32 (to convert from Fahrenheit to Centigrade, subtract 32, multiply by 5, and then divide by 9). Likewise, you can multiply by 2.2 to convert pounds to kilograms, and you can divide by 2.2 to convert kilograms to pounds. (We also saw in Recipe 4.12 how to convert angles from degrees to radians and vice versa.)
As with converting between different units of measure with angles, you can use the Unit and Converter classes to convert between other types of units. The Unit class has support for quite a range of categories of measurement (angles, temperature, volume, etc.). You can retrieve an array of supported categories using the static Unit.getCategories() method:
// Display the categories that are supported. trace(Unit.getCategories());
For each category, there are a variety units are supported. For example, in the angle category degrees, radians, and gradians are supported. You can retrieve a list of supported units for a given category using the static Unit.getUnits() method. Pass the method a category name to retrieve an array of units supported for that group. If you omit the parameter, then the entire list of supported units is returned:
// Display the units supported in the temperature category. trace(Unit.getUnits("temperature"));
You can use the built-in Unit constants for any supported unit of measurement, just as in Recipe 4.12. Then you can retrieve a Converter object by using the getConverterTo() or getConverterFrom() methods. The following example creates a Converter to calculate the Fahrenheit equivalents of Celcius measurements:
var converter:Converter = Unit.CELCIUS.getConverterTo(Unit.FAHRENHEIT);
Then, of course, you can use the convert() (and/or the convertWithLabel()) method to convert values:
trace(converter.convert(0)); // Displays: 32
See Also
For an example of using a hardcoded function to perform a single type of unit conversion, refer to Recipe 4.12, which includes a function to convert angles from degrees to radians and another function that does the opposite.
Get ActionScript 3.0 Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.