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

Problem

You want to specify a value in binary, octal, or hexadecimal.

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

Recipe 4.2

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.

// The radix is 2, so output as binary
trace(new uint(51).toString(2));  // Displays: 110011
// The radix 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"));     // The radix is implicitly 16. Displays: 18
trace(parseInt("017"));      // The radix 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:

  1. 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.

  2. Divide the input value by the number chosen in Step 1 (in this case, .01).

  3. Use Math.round() to round the calculated value from Step 2 to the nearest integer.

  4. 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:

number

The number to round.

roundToInterval

The interval to which to round the number. For example, if you want to round to the nearest tenth, use 0.1 as the interval. Or, to round to the nearest multiple of six, use 6.

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

Problem

You want to add leading or trailing zeros or spaces to a number to display it as a string.

Solution

Use the custom NumberFormat class, apply a mask, and then call the format() method.

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

See Also

Recipes 4.2 and 4.6

Formatting Numbers for Display Without a Mask

Problem

You want to format a number for display without using a mask.

Solution

Create a NumberFormat object with no mask setting, then call the format() method.

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

Problem

You want to format a number as currency, such as dollars.

Solution

Use the NumberFormat.currencyFormat() method.

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@

See Also

Recipes 4.3, 4.5, and Appendix A for creating special characters, including the Euro (), Yen (¥), and British pound ( £) symbols. To align currency amounts in text fields, set the field’s format to right justification using the TextFormat .align property.

Generating a Random Number

Problem

You want to use ActionScript to generate 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));
      }
    }
    
  }
}

See Also

Recipes 4.3 and 4.11

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

Recipe 4.7

Simulating Dice

Problem

You want to mimic rolling dice.

Solution

Use the NumberUtilities.random() method to generate random numbers in the desired range.

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

Recipe 4.7

Simulating Playing Cards

Problem

You want to use ActionScript to deal cards for a card game using a standard 52-card deck (without Jokers).

Solution

Use the custom Cards class.

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.

Solution

Use the NumberUtilities.getUnique() method.

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.

Solution

Use the Unit and Converter classes.

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

Problem

You want to calculate the distance between two points.

Solution

Use Math.pow() and Math.sqrt() in conjunction with the Pythagorean theorem.

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 a right triangle is drawn between two points to calculate the distance between the points
Figure 4-1. The hypotenuse of a right triangle is drawn between two points to calculate the distance between the points

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.

The angle, adjacent side, opposite side, and hypotenuse of a right triangle
Figure 4-2. The angle, adjacent side, opposite side, and hypotenuse of a right triangle

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.

Using trigonometry to determine a point along a circle’s perimeter
Figure 4-3. Using trigonometry to determine a point along a 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++;
    }
        
  }
}

See Also

Recipes 7.7 and 7.8

Converting Between Units of Measurement

Problem

You want to convert between Fahrenheit and Celsius, pounds and kilograms, or other units of measure.

Solution

Use the Unit and Converter classes.

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.