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.8 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, winning on a subsequent roll and losing 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
.
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
.
Main
’s Local VariablesThe 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.
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.
Using enumeration constants (like Status.Won
, Status.Lost
and Status.Continue
) rather than literal integer values (such as 0, 1 and 2) can make code easier to read and maintain.
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.
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 case
s. 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
.
enum
You could also declare an enum
’s underlying type to be byte
, sbyte
, short
, ushort
, int
,
private enum MyEnum : typeName {Constant1, Constant2, ...}
where typeName represents one of the integral simple types.
enum
ConstantsIf 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).
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.
Craps
ExampleNote 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).
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 case
s 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.