Case Study: A Game of Chance (Introducing Enumerations)
Case Study A Game of Chance (Introducing Enumerations)
One popular game of chance is the 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 your point.
The application in Fig. 7.9 and Fig. 7.10 simulates the game of craps, using methods to define the logic of the game. In the Main method of class CrapsTest (Fig. 7.10), line 7 creates an object of class Craps (Fig. 7.9), and line 8 calls its Play method to start the game. The Play method (Fig. 7.9, lines 2470) calls the RollDice method (Fig. 7.9, lines 7385) as needed to roll the two dice and compute their sum. The four sample outputs in Fig. 7.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 7.9. Craps class simulates the dice game craps.
1 // Fig. 7.9: Craps.cs 2 // Craps class simulates the dice game craps. 3 using System; 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 // enumeration with constants that represent common rolls of the dice 14 private enum DiceNames 15 { 16 SNAKE_EYES = 2, 17 TREY = 3, 18 SEVEN = 7, 19 YO_LEVEN = 11, 20 BOX_CARS = 12 21 } 22 23 // plays one game of craps 24 public void Play() 25 { 26 // gameStatus can contain CONTINUE, WON or LOST 27 Status gameStatus = Status.CONTINUE; 28 int myPoint = 0; // point if no win or loss on first roll 29 30 int sumOfDice = RollDice(); // first roll of the dice 31 32 // determine game status and point based on first roll 33 switch ( ( DiceNames ) sumOfDice ) 34 { 35 case DiceNames.SEVEN: // win with 7 on first roll 36 case DiceNames.YO_LEVEN: // win with 11 on first roll 37 gameStatus = Status.WON; 38 break; 39 case DiceNames.SNAKE_EYES: // lose with 2 on first roll 40 case DiceNames.TREY: // lose with 3 on first roll 41 case DiceNames.BOX_CARS: // lose with 12 on first roll 42 gameStatus = Status.LOST; 43 break; 44 default: // did not win or lose, so remember point 45 gameStatus = Status.CONTINUE; // game is not over 46 myPoint = sumOfDice; // remember the point 47 Console.WriteLine( "Point is {0}", myPoint ); 48 break; 49 } // end switch 50 51 // while game is not complete 52 while ( gameStatus == Status.CONTINUE ) // game not WON or LOST 53 { 54 sumOfDice = RollDice(); // roll dice again 55 56 // determine game status 57 if ( sumOfDice == myPoint ) // win by making point 58 gameStatus = Status.WON; 59 else 60 // lose by rolling 7 before point 61 if ( sumOfDice == ( int ) DiceNames.SEVEN ) 62 gameStatus = Status.LOST; 63 } // end while 64 65 // display won or lost message 66 if ( gameStatus == Status.WON ) 67 Console.WriteLine( "Player wins" ); 68 else 69 Console.WriteLine( "Player loses" ); 70 } // end method Play 71 72 // roll dice, calculate sum and display results 73 public int RollDice() 74 { 75 // pick random die values 76 int die1 = randomNumbers.Next( 1, 7 ); // first die roll 77 int die2 = randomNumbers.Next( 1, 7 ); // second die roll 78 79 int sum = die1 + die2; // sum of die values 80 81 // display results of this roll 82 Console.WriteLine( "Player rolled {0} + {1} = {2}", 83 die1, die2, sum ); 84 return sum; // return sum of dice 85 } // end method RollDice 86 } // end class Craps |
Figure 7.10. Application to test class Craps.
(This item is displayed on pages 295 - 296 in the print version)
1 // Fig. 7.10: CrapsTest.cs 2 // Application to test class Craps. 3 public class CrapsTest 4 { 5 public static void Main( string[] args ) 6 { 7 Craps game = new Craps(); 8 game.Play(); // play one game of craps 9 } // end Main 10 } // end class CrapsTest
|
Let's discuss the declaration of class Craps in Fig. 7.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 7385) to roll the dice and compute and print their sum. Method RollDice is declared once, but it is called from two places (lines 30 and 54) 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 73). Although lines 76 and 77 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 76 and 77) 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 2470) uses local variable gameStatus (line 27) to keep track of the overall game status, local variable myPoint (line 28) to store the "point" if the player does not win or lose on the first roll and local variable sumOfDice (line 30) 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 statementthus, the application could try to use myPoint before it is definitely 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. However, as good programming practice, we initialize it anyway.
Note that local variable gameStatus is declared to be of a new type called Status, which we declared in 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 user-defined type called an enumeration, which declares a set of constants represented by identifiers. An enumeration is introduced by the keyword enum and a type name (in this case, Status). As with a class, braces ({ and }) delimit the body of an enum declaration. Inside the braces is a comma-separated list of enumeration constants. The enum constant names must be unique, but their underlying values need not be.
|
Variables of type Status should be assigned only one of the three constants declared in the enumeration. When the game is won, the application sets local variable gameStatus to Status.WON (lines 37 and 58). When the game is lost, the application sets local variable gameStatus to Status.LOST (lines 42 and 62). Otherwise, the application sets local variable gameStatus to Status.CONTINUE (line 45) to indicate that the dice must be rolled again.
Line 30 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 3349, which uses the sumOfDice value from line 30 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 in the DiceNames enumeration in lines 1421. These are used in the cases of the switch statement. The identifier names use casino parlance for these sums. Notice that in the DiceNames enumeration, a value is explicitly assigned to each identifier name. When the enum is declared, each constant in the enum declaration contains an underlying constant value of type int. If you do not assign a value to an identifier in the enum declaration, the compiler will do so. If the first enum constant is unassigned, the compiler gives it the value 0. If any other enum constant is unassigned, the compiler gives it a value equal to one more than the value of the preceding enum constant. For example, in the Status enumeration, the compiler implicitly assigns 0 to Status.WON, 1 to Status.CONTINUE and 2 to Status.LOST.
You could also declare an enum's underlying type to be byte, sbyte, short, ushort, int, uint, long or ulong by writing
private enum MyEnum : typeName { CONSTANT1, CONSTANT2, ... }
where typeName represents one of the integral simple types.
If you need to compare a simple-type value to the underlying value of an enumeration constant, you must use a cast operator to make the two types match. In the switch statement at lines 3349, we use the cast operator to convert the int value in sumOfDice to type DiceNames and compare it to each of the constants in DiceNames. Lines 3536 determine whether the player won on the first roll with SEVEN (7) or YO_LEVEN (11). Lines 3941 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 4448) saves sumOfDice in myPoint (line 46) and displays the point (line 47).
If we are still trying to "make our point" (i.e., the game is continuing from a prior roll), the loop in lines 5263 executes. Line 54 rolls the dice again. If sumOfDice matches myPoint in line 57, line 58 sets gameStatus to Status.WON, and the loop terminates because the game is complete. In line 61, we use the cast operator ( int ) to obtain the underlying value of DiceNames.SEVEN so that we can compare it to sumOfDice. If sumOfDice is equal to SEVEN (7), line 62 sets gameStatus to Status.LOST, and the loop terminates because the game is over. When the game completes, lines 6669 display a message indicating whether the player won or lost and the application terminates.
Note the use of the various program-control mechanisms we have discussed. The Craps class uses two methodsPlay (called from CrapsTest.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 3536) and for sums of SNAKE_EYES, trEY and BOX_CARS (lines 3941).