8.6 Case Study: Card Shuffling and Dealing Simulation

So far, this chapter’s examples have used arrays of value-type elements. This section uses random-number generation and an array of reference-type elements—namely, objects representing playing cards—to develop a class that simulates card shuffling and dealing. This class can then be used to implement apps that play card games. The exercises at the end of the chapter use the techniques developed here to build a poker app.

We first develop class Card (Fig. 8.10), which represents a playing card that has a face (e.g., "Ace", "Deuce", "Three", …, "Jack", "Queen", "King") and a suit (e.g., "Hearts", "Diamonds", "Clubs", "Spades"). Next, we develop class DeckOfCards (Fig. 8.11), which creates a deck of 52 playing cards in which each element is a Card object. Then we build an app (Fig. 8.12) that uses class DeckOfCards’s card shuffling and dealing capabilities.

8.6.1 Class Card and Getter-Only Auto-Implemented Properties

Class Card (Fig. 8.10) contains two auto-implemented string properties—Face and Suit—that store references to the face value and suit name for a specific Card. Prior to C# 6, auto-implemented properties required both a get and a set accessor. Face and Suit are declared as C# 6 getter-only auto-implemented properties, which client code can use only to get each property’s value. Such properties are read only. Getter-only auto-implemented properties can be initialized only either in their declarations or in all of the type’s constructors. Initializing an auto-implemented property in its declaration is another C# 6 feature known as auto-property initializers. To do so, follow the property declaration with an = and the initial value, as in


Type PropertyName { get; set; } = initializer;

You can also initialize instance variables in their declarations.

The constructor (lines 9–13) receives two strings that it uses to initialize the class’s properties. Method ToString (line 16)—implemented here as an expression-bodied method—creates a string consisting of the Face of the card, the string "of " and the Suit of the card (e.g., "Ace of Spades"). An object’s ToString method is called implicitly in many cases when the object is used where a string is expected, such as

Fig. 8.10 Card class represents a playing card.

Alternate View

  1   // Fig. 8.10: Card.cs
  2   // Card class represents a playing card.
  3   class Card
  4   {
  5      private string Face { get; } // Card’s face ("Ace", "Deuce", ...)
  6      private string Suit { get; } // Card’s suit ("Hearts", "Diamonds", ...)
  7
  8      // two-parameter constructor initializes card's Face and Suit
  9      public Card(string face, string suit)
 10      {
 11         Face = face; // initialize face of card
 12         Suit = suit; // initialize suit of card
 13      }
 14
 15      // return string representation of Card
 16      public override string ToString() => $"{Face} of {Suit}";
 17   }
  • when Write or WriteLine outputs the object,

  • when the object is concatenated to a string using the + operator, or

  • when the object is inserted into a string-interpolation expression.

ToString also can be invoked explicitly to obtain a string representation of an object.

ToString must be declared with the header exactly as shown in line 16 of Fig. 8.10. We’ll explain the purpose of the override keyword in Section 11.4.1.

8.6.2 Class DeckOfCards

Class DeckOfCards (Fig. 8.11) creates and manages an array of Card references. The named constant NumberOfCards (line 10) specifies the number of Cards in a deck (52). Line 11 declares an instance-variable named deck that refers to an array of Card objects with Number-OfCards (52) elements—the elements of the deck array are null by default. Like simple-type array-variable declarations, the declaration of a variable for an array of objects (e.g., Card[] deck) includes the type of the array’s elements, followed by square brackets and the name of the array variable. Class DeckOfCards also declares int instance variable current-Card (line 12), representing the next Card to be dealt from the deck array. Note that we cannot use var for type inference with NumberOfCards, deck and currentCard, because they are not local variables of a method or property.

Fig. 8.11 DeckOfCards class represents a deck of playing cards.

Alternate View

  1   // Fig. 8.11: DeckOfCards.cs
  2   // DeckOfCards class represents a deck of playing cards.
  3   using System;
  4
  5   class DeckOfCards
  6   {
  7      // create one Random object to share among DeckOfCards objects
  8      private static Random randomNumbers = new Random();
  9
 10      private const int NumberOfCards = 52; // number of cards in a deck
 11      private Card[] deck = new Card[NumberOfCards];
 12      private int currentCard = 0; // index of next Card to be dealt (0-51)
 13
 14      // constructor fills deck of Cards
 15      public DeckOfCards()
 16      {
 17         string[] faces = {"Ace", "Deuce", "Three", "Four", "Five", "Six",
 18             "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King"};   
 19         string[] suits = {"Hearts", "Diamonds", "Clubs", "Spades"};      
 20
 21         // populate deck with Card objects
 22         for (var count = 0; count < deck.Length; ++count)
 23         {
 24            deck[count] = new Card(faces[count % 13], suits[count / 13]);
 25         }
 26      }
 27
 28      // shuffle deck of Cards with one-pass algorithm
 29      public void Shuffle()
 30      {
 31         // after shuffling, dealing should start at deck[0] again
 32         currentCard = 0; // reinitialize currentCard
 33
 34         // for each Card, pick another random Card and swap them
 35         for (var first = 0; first < deck.Length; ++first)
 36         {
 37            // select a random number between 0 and 51
 38            var second = randomNumbers.Next(NumberOfCards);
 39
 40            // swap current Card with randomly selected Card
 41            Card temp = deck[first];      
 42            deck[first] = deck[second];   
 43            deck[second] = temp;          
 44         }
 45      }
 46
 47      // deal one Card
 48      public Card DealCard()
 49      {
 50         // determine whether Cards remain to be dealt
 51         if (currentCard < deck.Length)
 52         {
 53            return deck[currentCard++]; // return current Card in array
 54         }
 55         else
 56         {
 57            return null; // indicate that all Cards were dealt
 58       }
 59    }
 60 }

Class DeckOfCards: Constructor

The constructor uses a for statement (lines 22–25) to fill the deck array with Cards. The for statement initializes count to 0 and loops while count is less than deck.Length, causing count to take on each integer value from 0 to 51 (the indices of the deck array). Each Card is instantiated and initialized with two strings—one from the faces array at lines 17–18 (which contains the strings "Ace" through "King") and one from the suits array at line 19 (which contains the strings "Hearts", "Diamonds", "Clubs" and "Spades"). The calculation count % 13 (line 24) always results in a value from 0 to 12—the 13 indices of array faces. Similarly, the calculation count / 13 always results in a value from 0 to 3—the four indices of array suits. When the deck array is initialized, it contains the Cards with faces "Ace" through "King" in order for each suit. Note that we cannot use a foreach loop in lines 22–25, because we need to modify each element of deck.

Class DeckOfCards: Shuffle Method

Method Shuffle (lines 29–45) shuffles the Cards in the deck. The method loops through all 52 Cards and performs the following tasks:

  • For each Card, a random number between 0 and 51 is picked to select another Card.

  • Next, the current Card object and the randomly selected Card object are swapped in the array. This exchange is performed by the three assignments in lines 41–43. The extra variable temp temporarily stores one of the two Card objects being swapped.

The swap cannot be performed with only the two statements


deck[first] = deck[second];
deck[second] = deck[first];

If deck[first] is the "Ace" of "Spades" and deck[second] is the "Queen" of "Hearts", then after the first assignment, both array elements contain the "Queen" of "Hearts", and the "Ace" of "Spades" is lost—hence, the extra variable temp is needed. After the for loop terminates, the Card objects are randomly ordered. Only 52 swaps are made in a single pass of the entire array, and the array of Card objects is shuffled.

Recommendation: Use an Unbiased Shuffling Algorithm

It’s recommended that you use a so-called unbiased shuffling algorithm for real card games. Such an algorithm ensures that all possible shuffled card sequences are equally likely to occur. A popular unbiased shuffling algorithm is the Fisher-Yates algorithm


http://en.wikipedia.org/wiki/Fisher–Yates_shuffle

This page also uses pseudocode to shows how to implement the algorithm.

Class DeckOfCards: DealCard Method

Method DealCard (lines 48–59) deals one Card in the array. Recall that currentCard indicates the index of the next Card to be dealt (i.e., the Card at the top of the deck). Thus, line 51 compares currentCard to the length of the deck array. If the deck is not empty (i.e., currentCard is less than 52), line 53 returns the top Card and increments current-Card to prepare for the next call to DealCard—otherwise, line 57 returns null to indicate that the end of deck has been reached.

8.6.3 Shuffling and Dealing Cards

Figure 8.12 demonstrates the card shuffling and dealing capabilities of class DeckOfCards (Fig. 8.11). Line 10 of Fig. 8.12 creates a DeckOfCards object named myDeckOfCards and uses type inference to determine the variable’s type. Recall that the DeckOfCards constructor creates the deck with the 52 Card objects in order by suit and face. Line 11 invokes myDeckOfCards’s Shuffle method to rearrange the Card objects. The for statement in lines 14–22 deals all 52 Cards in the deck and displays them in four columns of 13 Cards each. Line 16 deals and displays a Card object by invoking myDeckOfCards’s DealCard method. When a Card object is placed in a string-interpolation expression, the Card’s To-String method is invoked implicitly. Because the field width is negative, the result is displayed left-aligned in a field of width 19.

Fig. 8.12 Card-shuffling-and-dealing app.

Alternate View

  1   // Fig. 8.12: DeckOfCardsTest.cs
  2   // Card shuffling and dealing app.
  3   using System;
  4
  5   class DeckOfCardsTest
  6   {
  7      // execute app
  8      static void Main()
  9      {
 10         var myDeckOfCards = new DeckOfCards();                 
 11         myDeckOfCards.Shuffle(); // place Cards in random order
 12
 13         // display all 52 Cards in the order in which they are dealt
 14         for (var i = 0; i < 52; ++i)
 15         {
 16            Console.Write($"{myDeckOfCards.DealCard(),-19}");
 17
 18            if ((i + 1) % 4 == 0)
 19            {
 20               Console.WriteLine();
 21            }
 22         }
 23      }
 24   }

Eight of Clubs       Ten of Clubs       Ten of Spades       Four of Spades
Ace of Spades        Jack of Spades     Three of Spades     Seven of Spades
Three of Diamonds    Five of Clubs      Eight of Spades     Five of Hearts
Ace of Hearts        Ten of Hearts      Deuce of Hearts     Deuce of Clubs
Jack of Hearts       Nine of Spades     Four of Hearts      Seven of Clubs
Queen of Spades      Seven of Diamonds  Five of Diamonds    Ace of Clubs
Four of Clubs        Ten of Diamonds    Jack of Clubs       Six of Diamonds
Eight of Diamonds    King of Hearts     Three of Clubs      King of Spades
King of Diamonds     Six of Spades      Deuce of Spades     Five of Spades
Queen of Clubs       King of Clubs      Queen of Hearts     Seven of Hearts
Ace of Diamonds      Deuce of Diamonds  Four of Diamonds    Nine of Clubs
Queen of Diamonds    Jack of Diamonds   Six of Hearts       Nine of Diamonds
Nine of Hearts       Three of Hearts    Six of Clubs        Eight of Hearts
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset