- 7.1 Introduction
- 7.2 Packaging Code in C#
- 7.3 static Methods, static Variables and Class Math
- 7.4 Methods with Multiple Parameters
- 7.5 Notes on Using Methods
- 7.6 Argument Promotion and Casting
- 7.7 The .NET Framework Class Library
- 7.8 Case Study: Random-Number Generation
- 7.9 Case Study: A Game of Chance; Introducing Enumerations
- 7.10 Scope of Declarations
- 7.11 Method-Call Stack and Activation Records
- 7.12 Method Overloading
- 7.13 Optional Parameters
- 7.14 Named Parameters
- 7.15 C# 6 Expression-Bodied Methods and Properties
- 7.16 Recursion
- 7.17 Value Types vs. Reference Types
- 7.18 Passing Arguments By Value and By Reference
- 7.19 Wrap-Up
7.9 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 app in Fig. 7.7 simulates the game of craps, using methods to define the logic of the game. The Main method (lines 24–80) calls the static RollDice method (lines 83–94) as needed to roll the two dice and compute their sum. The four sample outputs show winning on the first roll, losing on the first roll, losing on a subsequent roll and winning on a subsequent roll, respectively. Variable randomNumbers (line 8) is declared static, so it can be created once during the program’s execution and used in method RollDice.
1 // Fig. 7.7: Craps.cs 2 // Craps class simulates the dice game craps. 3 using System; 4 5 class Craps 6 { 7 // create random-number generator for use in method RollDice 8 private static 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 SnakeEyes = 2, 17 Trey = 3, 18 Seven = 7, 19 YoLeven = 11, 20 BoxCars = 12 21 } 22 23 // plays one game of craps 24 static void Main() 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.YoLeven: // win with 11 on first roll 37 gameStatus = Status.Won; 38 break; 39 case DiceNames.SnakeEyes: // lose with 2 on first roll 40 case DiceNames.Trey: // lose with 3 on first roll 41 case DiceNames.BoxCars: // 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 {myPoint}"); 48 break; 49 } 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 { 59 gameStatus = Status.Won; 60 } 61 else 62 { 63 // lose by rolling 7 before point 64 if (sumOfDice == (int) DiceNames.Seven) 65 { 66 gameStatus = Status.Lost; 67 } 68 } 69 } 70 71 // display won or lost message 72 if (gameStatus == Status.Won) 73 { 74 Console.WriteLine("Player wins"); 75 } 76 else 77 { 78 Console.WriteLine("Player loses"); 79 } 80 } 81 82 // roll dice, calculate sum and display results 83 static int RollDice() 84 { 85 // pick random die values 86 int die1 = randomNumbers.Next(1, 7); // first die roll 87 int die2 = randomNumbers.Next(1, 7); // second die roll 88 89 int sum = die1 + die2; // sum of die values 90 91 // display results of this roll 92 Console.WriteLine($"Player rolled {die1} + {die2} = {sum}"); 93 return sum; // return sum of dice 94 } 95 }
Player rolled 2 + 5 = 7 Player wins
Player rolled 2 + 1 = 3 Player loses
Player rolled 2 + 4 = 6 Point is 6 Player rolled 3 + 1 = 4 Player rolled 5 + 5 = 10 Player rolled 6 + 1 = 7 Player loses
Player rolled 4 + 6 = 10 Point is 10 Player rolled 1 + 3 = 4 Player rolled 1 + 3 = 4 Player rolled 2 + 3 = 5 Player rolled 4 + 4 = 8 Player rolled 6 + 6 = 12 Player rolled 4 + 4 = 8 Player rolled 4 + 5 = 9 Player rolled 2 + 6 = 8 Player rolled 6 + 6 = 12 Player rolled 6 + 4 = 10 Player wins
Fig. 7.7 | Craps class simulates the dice game craps.
7.9.1 Method RollDice
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 83–94) to roll the dice and compute and display their sum. Method RollDice is declared once, but it’s called from two places (lines 30 and 54) in method Main, 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’s called, RollDice returns the sum of the dice as an int. Although lines 86 and 87 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 1–6. Variable randomNumbers (used in lines 86–87) is not declared in the method. Rather it’s declared as a private static variable of the class and initialized in line 8. This enables us to create one Random object that’s reused in each call to RollDice.
7.9.2 Method Main’s Local Variables
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 Main (lines 24–80) 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. Variable myPoint is initialized to 0 to ensure that the app will compile. If you do not initialize myPoint, the compiler issues an error, because myPoint is not assigned a value in every case of the switch statement—thus, the app could try to use myPoint before it’s definitely assigned a value. By contrast, gameStatus does not require initialization because it’s assigned a value in every branch of the switch statement—thus, it’s guaranteed to be initialized before it’s used. However, as good practice, we initialize it anyway.
7.9.3 enum Type Status
Local variable gameStatus (line 27) is declared to be of a new type called Status, which we declared in line 11. 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—by default, the first constant has the value 0 and each subsequent constant’s value is incremented by 1. The enum constant names must be unique, but the value associated with each constant need not be. Type Status is declared as a private member of class Craps, because Status is used only in that class.
Variables of type Status should be assigned only one of the three constants declared in the enumeration. When the game is won, the app sets local variable gameStatus to Status.Won (lines 37 and 59). When the game is lost, the app sets gameStatus to Status.Lost (lines 42 and 66). Otherwise, the app sets gameStatus to Status.Continue (line 45) to indicate that the dice must be rolled again.
7.9.4 The First Roll
Line 30 in method Main 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 Main next enters the switch statement at lines 33–49, which uses the sumOfDice value to determine whether the game has been won or lost, or whether it should continue with another roll.
7.9.5 enum Type DiceNames
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 14–21. These are used in the switch statement’s cases. The identifier names use casino parlance for these sums. In the DiceNames enumeration, we assign a value explicitly to each identifier name. When the enum is declared, each constant in the enum declaration is a 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 one higher than that of the preceding enum constant. For example, in the Status enumeration, the compiler implicitly assigns 0 to Status.Continue, 1 to Status.Won and 2 to Status.Lost.
7.9.6 Underlying Type of an enum
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.
7.9.7 Comparing Integers and enum Constants
If you need to compare a simple integral type value to the underlying value of an enumeration constant, you must use a cast operator to make the two types match—there are no implicit conversions between enum and integral types. In the switch expression (line 33), 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 35–36 determine whether the player won on the first roll with Seven (7) or YoLeven (11). Lines 39–41 determine whether the player lost on the first roll with SnakeEyes (2), Trey (3) or BoxCars (12). After the first roll, if the game is not over, the default case (lines 44–48) saves sumOfDice in myPoint (line 46) and displays the point (line 47).
Additional Rolls of the Dice
If we’re still trying to “make our point” (i.e., the game is continuing from a prior roll), the loop in lines 52–69 executes. Line 54 rolls the dice again. If sumOfDice matches myPoint in line 57, line 59 sets gameStatus to Status.Won, and the loop terminates because the game is complete. In line 64, 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 66 sets gameStatus to Status.Lost, and the loop terminates because the game is over. When the game completes, lines 72–79 display a message indicating whether the player won or lost, and the app terminates.
Control Statements in the Craps Example
Note the use of the various program-control mechanisms we’ve discussed. The Craps class uses two methods—Main and RollDice (called twice from Main)—and the switch, while, if...else and nested if control statements. Also, notice that we use multiple case labels in the switch statement to execute the same statements for sums of Seven and YoLeven (lines 35–36) and for sums of SnakeEyes, Trey and BoxCars (lines 39–41).
Code Snippets for Auto-Implemented Properties
Visual Studio has a feature called code snippets that allows you to insert predefined code templates into your source code. One such snippet enables you to easily create a switch statement with cases for all possible values for an enum type. Type switch in the C# code then press Tab twice. If you specify a variable of an enum type in the switch statement’s expression and press Enter, a case for each enum constant will be generated automatically.
To get a list of all available code snippets, type Ctrl + k, Ctrl + x. This displays the Insert Snippet window in the code editor. You can navigate through the Visual C# snippet folders with the mouse to see the snippets. This feature also can be accessed by right clicking in the source code editor and selecting the Insert Snippet... menu item.