Case Study: A Game of Chance (Introducing Enumerations)
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 5 public 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 11 private enum Status { CONTINUE, WON, LOST }; 12 13 // constants that represent common rolls of the dice 14 private final static int SNAKE_EYES = 2; 15 private final static int TREY = 3; 16 private final static int SEVEN = 7; 17 private final static int YO_LEVEN = 11; 18 private final static int BOX_CARS = 12; 19 20 // plays one game of craps 21 public 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 29 switch ( sumOfDice ) 30 { 31 case SEVEN: // win with 7 on first roll 32 case YO_LEVEN: // win with 11 on first roll 33 gameStatus = Status.WON; 34 break; 35 case SNAKE_EYES: // lose with 2 on first roll 36 case 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 ", myPoint ); 44 break; // optional at end of switch 45 } // end switch 46 47 // while game is not complete 48 while ( gameStatus == Status.CONTINUE ) // not WON or LOST 49 { 50 sumOfDice = rollDice(); // roll dice again 51 52 // determine game status 53 if ( 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 61 if ( gameStatus == Status.WON ) 62 System.out.println( "Player wins" ); 63 else 64 System.out.println( "Player loses" ); 65 } // end method play 66 67 // roll dice, calculate sum and display results 68 public 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 77 System.out.printf( "Player rolled %d + %d = %d ", 78 die1, die2, sum ); 79 80 return 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 4 public class CrapsTest 5 { 6 public static void main( String args[] ) 7 { 8 Craps game = new Craps(); 9 game.play(); // play one game of craps 10 } // end main 11 } // end class CrapsTest
|
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.
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.
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 comma-separated 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.)
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.