Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
630
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

method nextInt, and the starting number of the range is the number 1 that is added to randomNumberGenerator.nextInt( 6 ). We can generalize this result as

number = shiftingValue + randomNumbers.nextInt( scalingFactor );

where shiftingValue specifies the first number in the desired range of consecutive integers and scalingFactor specifies how many numbers are in the range.

It is also possible to choose integers at random from sets of values other than ranges of consecutive integers. For example, to obtain a random value from the sequence 2, 5, 8, 11 and 14, you could use the statement

number = 2 + 3 * randomNumbers.nextInt( 5 );

In this case, randomNumberGenerator.nextInt( 5 ) produces values in the range 04. Each value produced is multiplied by 3 to produce a number in the sequence 0, 3, 6, 9 and 12. We then add 2 to that value to shift the range of values and obtain a value from the sequence 2, 5, 8, 11 and 14. We can generalize this result as

number = shiftingValue +

differenceBetweenValues * randomNumbers.nextInt( scalingFactor );

where shiftingValue specifies the first number in the desired range of values, differenceBetweenValues represents the difference between consecutive numbers in the sequence and scalingFactor specifies how many numbers are in the range.

6.9.2. Random-Number Repeatability for Testing and Debugging

As we mentioned earlier in Section 6.9, the methods of class Random actually generate pseudorandom numbers based on complex mathematical calculations. Repeatedly calling any of Random's methods produces a sequence of numbers that appears to be random. The calculation that produces the pseudorandom numbers uses the time of day as a seed value to change the sequence's starting point. Each new Random object seeds itself with a value based on the computer system's clock at the time the object is created, enabling each execution of a program to produce a different sequence of random numbers.

[Page 250]

When debugging an application, it is sometimes useful to repeat the exact same sequence of pseudorandom numbers during each execution of the program. This repeatability enables you to prove that your application is working for a specific sequence of random numbers before you test the program with different sequences of random numbers. When repeatability is important, you can create a Random object as follows:

Random randomNumbers = new Random( seedValue );

The seedValue argument (type long) seeds the random-number calculation. If the same seedValue is used every time, the Random object produces the same sequence of random numbers. You can set a Random object's seed at any time during program execution by calling the object's setSeed method, as in

randomNumbers.setSeed( seedValue );

Error-Prevention Tip 6.2

While a program is under development, create the Random object with a specific seed value to produce a repeatable sequence of random numbers each time the program executes. If a logic error occurs, fix the error and test the program again with the same seed valuethis

allows you to reconstruct the same sequence of random numbers that caused the error. Once the logic errors have been removed, create the Random object without using a seed value, causing the Random object to generate a new sequence of random numbers each time the program executes.

[Page 250 (continued)]

6.10. Case Study: A Game of Chance (Introducing

Enumerations)

A popular game of chance is a dice game known as "craps," which is played in casinos and back alleys throughout the world. The rules of the game are straightforward:

You roll two dice. Each die has six faces, which contain one, two, three, four, five and six spots, respectively. After the dice have come to rest, the sum of the spots on the two upward faces is calculated. If the sum is 7 or 11 on the first throw, you win. If the sum is 2, 3 or 12 on the first throw (called "craps"), you lose (i.e., the "house" wins). If the sum is 4, 5, 6, 8, 9 or 10 on the first throw, that sum becomes your "point." To win, you must continue rolling the dice until you "make your point" (i.e., roll that same point value). You lose by rolling a 7 before making the point.

The application in Fig. 6.9 and Fig. 6.10 simulates the game of craps, using methods to define the logic of the game. In the main method of class CrapsTest (Fig. 6.10), line 8 creates an object of class Craps (Fig. 6.9) and line 9 calls its play method to start the game. The play method (Fig. 6.9, lines 2165) calls the rollDice method (Fig. 6.9, lines 6881) as necessary to roll the two dice and compute their sum. Four sample outputs in Fig. 6.10 show winning on the first roll, losing on the first roll, winning on a subsequent roll and losing on a subsequent roll, respectively.

Figure 6.9. Craps class simulates the dice game craps.

(This item is displayed on pages 251 - 252 in the print version)

1

//

Fig. 6.9: Craps.java

2

//

Craps class simulates the dice game craps.

3

import java.util.Random;

4

 

 

5public class Craps

6{

7

// create random number generator for use in

method rollDice

8

private Random randomNumbers = new Random();

 

9

 

 

10// enumeration with constants that represent the game status

11private enum Status { CONTINUE, WON, LOST };

12

13// constants that represent common rolls of the dice

14private final static int SNAKE_EYES = 2;

15private final static int TREY = 3;

16private final static int SEVEN = 7;

17private final static int YO_LEVEN = 11;

18private final static int BOX_CARS = 12;

19

20// plays one game of craps

21public void play()

22{

23

int

myPoint =

0; // point

if no win or loss

on first roll

24

Status gameStatus; // can

contain CONTINUE,

WON

or LOST

25

 

 

 

 

 

 

26

int

sumOfDice

= rollDice(); // first roll of

the

dice

27

 

 

 

 

 

 

28// determine game status and point based on first roll

29switch ( sumOfDice )

30{

31case SEVEN: // win with 7 on first roll

32case YO_LEVEN: // win with 11 on first roll

33

gameStatus = Status.WON;

34

break;

35case SNAKE_EYES: // lose with 2 on first roll

36case TREY: // lose with 3 on first roll

37

case BOX_CARS: // lose with 12 on first roll

38

gameStatus = Status.LOST;

39

break;

40

default: // did not win or lose, so remember point

41

gameStatus = Status.CONTINUE; // game is not over

42

myPoint = sumOfDice; // remember the point

43

System.out.printf( "Point is %d\n", myPoint );

44

break; // optional at end of switch

45

} // end switch

46

 

47// while game is not complete

48while ( gameStatus == Status.CONTINUE ) // not WON or LOST

49{

50sumOfDice = rollDice(); // roll dice again

51

52// determine game status

53if ( sumOfDice == myPoint ) // win by making point

54

gameStatus = Status.WON;

55

else

 

56

if

( sumOfDice == SEVEN ) // lose by rolling 7 before point

57

 

gameStatus = Status.LOST;

58

} // end

while

59

 

 

60// display won or lost message

61if ( gameStatus == Status.WON )

62System.out.println( "Player wins" );

63else

64System.out.println( "Player loses" );

65} // end method play

66

67// roll dice, calculate sum and display results

68public int rollDice()

69{

70// pick random die values

71

int

die1 = 1

+

randomNumbers.nextInt( 6

);

// first die roll

72

int

die2

=

1

+

randomNumbers.nextInt(

6

);

// second die roll

73

 

 

 

 

 

 

 

 

 

74

int

sum

=

die1

+ die2; // sum of die

values

75

 

 

 

 

 

 

 

 

 

76// display results of this roll

77System.out.printf( "Player rolled %d + %d = %d\n",

78die1, die2, sum );

79

80return sum; // return sum of dice

81} // end method rollDice

82} // end class Craps

Figure 6.10. Application to test class Craps.

(This item is displayed on page 253 in the print version)

1

//

Fig. 6.10: CrapsTest.java

 

2

//

Application to test class

Craps.

3

 

 

 

4public class CrapsTest

5{

6 public static void main( String args[] )

7{

8Craps game = new Craps();

9game.play(); // play one game of craps

10} // end main

11} // end class CrapsTest

Player rolled 5 + 6 = 11 Player wins

Player rolled 1 + 2 = 3

Player loses

Player rolled 5 + 4 = 9

Point is 9

Player rolled 2 + 2 = 4

Player rolled 2 + 6 = 8

Player rolled 4 + 2 = 6

Player rolled 3 + 6 = 9

Player wins

Player rolled 2 + 6 = 8

Point is 8

Player rolled 5 + 1 = 6

Player rolled 2 + 1 = 3

Player rolled 1 + 6 = 7

Player loses

Let's discuss the declaration of class Craps in Fig. 6.9. In the rules of the game, the player must roll two dice on the first roll, and must do the same on all subsequent rolls. We declare method rollDice (lines 6881) to roll the dice and compute and print their sum. Method rollDice is declared once, but it is called from two places (lines 26 and 50) in method play, which contains the logic for one complete game of craps. Method rollDice takes no arguments, so it has an empty parameter list. Each time it is called, rollDice returns the sum of the dice, so the return type int is indicated in the method header (line 68). Although lines 71 and 72 look the same (except for the die names), they do not necessarily produce the same result. Each of these statements produces a random value in the range 16. Note that randomNumbers (used in lines 7172) is not declared in the method. Rather it is declared as a private instance variable of the class and initialized in line 8. This enables us to create one Random object that is reused in each call to rollDice.

[Page 251]

[Page 252]

The game is reasonably involved. The player may win or lose on the first roll, or may win or lose on any subsequent roll. Method play (lines 2165) uses local variable myPoint (line 23) to store the "point" if the player does not win or lose on the first roll, local variable gameStatus (line 24) to keep track of the overall game status and local variable sumOfDice (line 26) to maintain the sum of the dice for the most recent roll. Note that myPoint is initialized to 0 to ensure that the application will compile. If you do not initialize myPoint, the compiler issues an error, because myPoint is not assigned a value in every branch of the switch statement, and thus the program could try to use myPoint before it is assigned a value. By contrast, gameStatus does not require initialization because it is assigned a value in every branch of the switch statementthus, it is guaranteed to be initialized before it is used.

[Page 253]

Note that local variable gameStatus is declared to be of a new type called Status, which we declared at line 11. Type Status is declared as a private member of class Craps, because Status will be used only in that class. Status is a programmer-declared type called an enumeration, which, in its simplest form, declares a set of constants represented by identifiers. An enumeration is a special kind of class that is introduced by the keyword enum (new to J2SE 5.0) and a type name (in this case, Status). As with any class, braces ({ and }) delimit the body of an enum declaration. Inside the braces is a commaseparated list of enumeration constants, each representing a unique value. The identifiers in an enum must be unique. (You will learn more about enumerations in Chapter 8.)

[Page 254]

Good Programming Practice 6.3

Use only uppercase letters in the names of constants. This makes the constants stand out in a program and reminds the programmer that enumeration constants are not variables.

Variables of type Status can be assigned only one of the three constants declared in the enumeration or a compilation error will occur. When the game is won, the program sets local variable gameStatus to Status.WON (lines 33 and 54). When the game is lost, the program sets local variable gameStatus to Status.LOST (lines 38 and 57). Otherwise, the program sets local variable gameStatus to Status.CONTINUE (line 41) to indicate that the dice must be rolled again.

Good Programming Practice 6.4

Using enumeration constants (like Status.WON, Status.LOST and Status.CONTINUE) rather than literal integer values (such as 0, 1 and 2) can make programs easier to read and maintain.

Line 26 in method play calls rollDice, which picks two random values from 1 to 6, displays the value of the first die, the value of the second die and the sum of the dice, and returns the sum of the dice. Method play next enters the switch statement at lines 2945, which uses the sumOfDice value from line 26 to determine whether the game has been won or lost, or whether it should continue with another roll. The sums of the dice that would result in a win or loss on the first roll are declared as public final static int constants in lines 1418. These are used in the cases of the switch statement. The identifier names use casino parlance for these sums. Note that these constants, like enum constants, are declared with all capital letters by convention, to make them stand out in the program. Lines 3134 determine whether the player won on the first roll with SEVEN (7) or YO_LEVEN (11). Lines 3539 determine whether the player lost on the first roll with SNAKE_EYES (2), TREY (3), or BOX_CARS (12). After the first roll, if the game is not over, the default case (lines 4044) saves sumOfDice in myPoint (line 42) and displays the point (line 43).

If we are still trying to "make our point" (i.e., the game is continuing from a prior roll), the loop in lines 4858 executes. Line 50 rolls the dice again. In line 53, if sumOfDice matches myPoint, line 54 sets gameStatus to Status.WON, then the loop terminates because the game is complete. In line 56, if sumOfDice is equal to SEVEN (7), line 57 sets gameStatus to Status.LOST, and the loop terminates because the game is complete. When the game completes, lines 6164 display a message indicating whether the player won or lost and the program terminates.

Note the use of the various program-control mechanisms we have discussed. The Craps class uses three methodsmain, play (called from main) and rollDice (called twice from play)and the switch, while,

if...else and nested if control statements. Note also the use of multiple case labels in the switch

statement to execute the same statements for sums of SEVEN and YO_LEVEN (lines 3132) and for sums of

SNAKE_EYES, TREY and BOX_CARS (lines 3537).

You might be wondering why we declared the sums of the dice as public final static int constants rather than as enum constants. The answer lies in the fact that the program must compare int sumOfDice (line 26) to these constants to determine the outcome of each roll. Suppose we were to declare enum Sum containing constants (e.g., Sum.SNAKE_EYES) representing the five sums used in the game, then use these constants in place of the final variables in the cases of the switch statement (lines 2945). Doing so would prevent us from using sumOfDice as the switch statement's controlling expressionJava does not allow an int to be compared to an enumeration constant. To achieve the same functionality as the current program, we would have to use a variable currentSum of type Sum as the switch's controlling expression. Unfortunately, Java does not provide an easy way to convert an int value to a particular enum constant. To translate an int into an enum constant would require a separate switch statement. Clearly this would be cumbersome and not improve the readability of the program (thus defeating the purpose of using an enum), so we are better off using public final static int constants to represent the sums of the dice.

[Page 255]

[Page 255 (continued)]

6.11. Scope of Declarations

You have seen declarations of various Java entities, such as classes, methods, variables and parameters. Declarations introduce names that can be used to refer to such Java entities. The scope of a declaration is the portion of the program that can refer to the declared entity by its name. Such an entity is said to be "in scope" for that portion of the program. This section introduces several important scope issues. (For more scope information, see the Java Language Specification, Section 6.3: Scope of a Declaration, at java.sun.com/docs/books/jls/second_edition/html/names.doc.html#103228.)

The basic scope rules are as follows:

1.The scope of a parameter declaration is the body of the method in which the declaration appears.

2.The scope of a local-variable declaration is from the point at which the declaration appears to the end of that block.

3.The scope of a local-variable declaration that appears in the initialization section of a for statement's header is the body of the for statement and the other expressions in the header.

4.The scope of a method or field of a class is the entire body of the class. This enables non-static methods of a class to use the class's fields and other methods.

Any block may contain variable declarations. If a local variable or parameter in a method has the same name as a field, the field is "hidden" until the block terminates executionthis is called shadowing. In Chapter 8, we discuss how to access shadowed fields.

Common Programming Error 6.10

A compilation error occurs when a local variable is declared more than once in a method.

Error-Prevention Tip 6.3

Use different names for fields and local variables to help prevent subtle logic errors that occur when a method is called and a local variable of the method shadows a field of the same name in the class.

The application in Fig. 6.11 and Fig. 6.12 demonstrates scoping issues with fields and local variables. When the application begins execution, class ScopeTest's main method (Fig. 6.12, lines 711) creates an object of class Scope (line 9) and calls the object's begin method (line 10) to produce the program's output (shown in Fig. 6.12).

Figure 6.11. Scope class demonstrating scopes of a field and local variables.

(This item is displayed on page 256 in the print version)

1

//

Fig. 6.11: Scope.java

2

//

Scope class demonstrates field and local variable scopes.

3

 

 

4public class Scope

5{

6

// field that

is

accessible to all methods of this class

7

private int x

=

1;

8

 

 

 

9// method begin creates and initializes local variable x

10// and calls methods useLocalVariable and useField

11public void begin()

12{

13

int x = 5; // method's local

variable

x shadows

field

x

 

14

 

 

 

 

 

 

15

System.out.printf( "local x

in method

begin is

%d\n",

x

);

16

 

 

 

 

 

 

17useLocalVariable(); // useLocalVariable has local x

18useField(); // useField uses class Scope's field x

19useLocalVariable(); // useLocalVariable reinitializes local x

20useField(); // class Scope's field x retains its value

21

22System.out.printf( "\nlocal x in method begin is %d\n", x );

23} // end method begin

24

25// create and initialize local variable x during each call

26public void useLocalVariable()

27{

28int x = 25; // initialized each time useLocalVariable is called

30System.out.printf(

31"\nlocal x on entering method useLocalVariable is %d\n", x );

32++x; // modifies this method's local variable x

33System.out.printf(

34"local x before exiting method useLocalVariable is %d\n", x );

35} // end method useLocalVariable

36

37// modify class Scope's field x during each call

38public void useField()

39{

40System.out.printf(

41"\nfield x on entering method useField is %d\n", x );

42x *= 10; // modifies class Scope's field x

43System.out.printf(

44"field x before exiting method useField is %d\n", x );

45} // end method useField

46} // end class Scope

Figure 6.12. Application to test class Scope.

(This item is displayed on page 257 in the print version)

1

//

Fig. 6.12: ScopeTest.java

 

2

//

Application to test class

Scope.

3

 

 

 

4public class ScopeTest

5{

6// application starting point

7 public static void main( String args[] )

8{

9Scope testScope = new Scope();

10testScope.begin();

11} // end main

12} // end class ScopeTest

local x in method begin is 5

local x on entering method useLocalVariable is 25 local x before exiting method useLocalVariable is 26

field x on entering method useField is 1 field x before exiting method useField is 10

local x on entering method useLocalVariable is 25 local x before exiting method useLocalVariable is 26

field x on entering method useField is 10 field x before exiting method useField is 100

local x in method begin is 5

[Page 256]

In class Scope, line 7 declares and initializes the field x to 1. This field is shadowed (hidden) in any block (or method) that declares a local variable named x. Method begin (lines 1123) declares a local variable x (line 13) and initializes it to 5. This local variable's value is output to show that the field x (whose value is 1) is shadowed in method begin. The program declares two other methodsuseLocalVariable (lines 2635) and useField (lines 3845)that each take no arguments and do not return results. Method begin calls each method twice (lines 1720). Method useLocalVariable declares local variable x (line 28). When useLocalVariable is first called (line 17), it creates local variable x and initializes it to 25 (line 28), outputs the value of x (lines 3031), increments x (line 32) and outputs the value of x again (lines 3334). When uselLocalVariable is called a second time (line 19), it re-creates local variable x and re-initializes it to 25, so the output of each useLocalVariable call is identical.

[Page 257]

Method useField does not declare any local variables. Therefore, when it refers to x, field x (line 7) of the class is used. When method useField is first called (line 18), it outputs the value (1) of field x (lines 4041), multiplies the field x by 10 (line 42) and outputs the value (10) of field x again (lines 4344) before returning. The next time method useField is called (line 20), the field has its modified value, 10, so the method outputs 10, then 100. Finally, in method begin, the program outputs the value of local variable x again (line 22) to show that none of the method calls modified start's local variable x, because the methods all referred to variables named x in other scopes.