Expanding and Using CardLib


Now that you've had a look at defining and using events, you can use them to Ch13CardLib. The event you'll add to your library will be generated when the last Card object in a Deck object is obtained using GetCard and will be called LastCardDrawn. This event will allow subscribers to reshuffle the deck automatically, cutting down on the processing necessary by a client. The delegate defined for this event (LastCardDrawnHandler) needs to supply a reference to the Deck object such that the Shuffle() method will be accessible from wherever the handler is. Add the following code to Deck.cs:

namespace Ch13CardLib { public delegate void LastCardDrawnHandler(Deck currentDeck); 

The code to define the event and raise it is simply:

 public event LastCardDrawnHandler LastCardDrawn;     public Card GetCard(int cardNum) {    if (cardNum >= 0 && cardNum <= 51) { if ((cardNum == 51) && (LastCardDrawn != null)) LastCardDrawn(this); return cards[cardNum]; }    else       throw new CardOutOfRangeException((Cards)cards.Clone()); }

This is all the code required to add the event to the Deck class definition. Now you just need to use it.

A Card Game Client for CardLib

After spending all this time developing the CardLib library, it would be a shame not to use it. Before finishing this section on OOP in C# and the .NET Framework, it's time to have a little fun and write the basics of a card game application that uses the familiar playing card classes.

As in previous chapters, you'll add a client console application to the Ch13CardLib solution, add a reference to the Ch13CardLib project, and make it the startup project. This application will be called Ch13CardClient.

To start with, you'll create a new class called Player in a new file in Ch13CardClient, Player.cs. This class will contain a private Cards field called hand, a private string field called name, and two read- only properties: Name and PlayHand. These properties simply expose the private fields. Note that even though the PlayHand property is read-only, you will have write access to the reference to the hand field returned, allowing you to modify the cards in the player's hand.

You'll also hide the default constructor by making it private, and supply a public nondefault constructor that accepts an initial value for the Name property of Player instances.

Finally, you'll provide a bool type method called HasWon(). This will return true if all the cards in the player's hand are of the same suit (a simple winning condition, but that doesn't matter too much).

The code for Player.cs is as follows:

using System; using System.Collections.Generic; using System.Text; using Ch13CardLib;     namespace Ch13CardClient {    public class Player    { private Cards hand; private string name; public string Name { get { return name; } } public Cards PlayHand { get { return hand; } } private Player() { } public Player(string newName) { name = newName; hand = new Cards(); } public bool HasWon() { bool won = true; Suit match = hand[0].suit; for (int i = 1; i < hand.Count; i++) { won &= hand[i].suit == match; } return won; }    } } 

Next, you define a class that will handle the card game itself, called Game. This class is found in the file Game.cs of the Ch13CardClient project.

This class has four private member fields:

  • playDeck: A Deck type variable containing the deck of cards to use

  • currentCard: An int value used as a pointer to the next card in the deck to draw

  • players: An array of Player objects representing the players of the game

  • discardedCards: A Cards collection for the cards that have been discarded by players but not shuffled back into the deck

The default constructor for the class initializes and shuffles the Deck stored in playDeck, sets the currentCard pointer variable to 0 (the first card in playDeck), and wires up an event handler called Reshuffle() to the playDeck.LastCardDrawn event. This handler simply shuffles the deck, initializes the discardedCards collection, and resets currentCard to 0, ready to read cards from the new deck.

The Game class also contains two utility methods, SetPlayers() for setting the players for the game (as an array of Player objects) and DealHands() for dealing hands to the players (7 cards each). The number of players allowed is restricted from 2 to 7 in order to make sure that there are enough cards to go around.

Finally, there is a PlayGame() method that contains the game logic itself. You'll come back to this function shortly, after you've looked at the code in Program.cs. The rest of the code in Game.cs is as follows:

using System; using System.Collections.Generic; using System.Text; using Ch13CardLib;     namespace Ch13CardClient {    public class Game    { private int currentCard; private Deck playDeck; private Player[] players; private Cards discardedCards;           public Game()       { currentCard = 0; playDeck = new Deck(true); playDeck.LastCardDrawn += new LastCardDrawnHandler(Reshuffle); playDeck.Shuffle(); discardedCards = new Cards();       }     private void Reshuffle(Deck currentDeck) { Console.WriteLine("Discarded cards reshuffled into deck."); currentDeck.Shuffle(); discardedCards.Clear(); currentCard = 0; } public void SetPlayers(Player[] newPlayers) { if (newPlayers.Length > 7) throw new ArgumentException("A maximum of 7 players may play this" + " game."); if (newPlayers.Length < 2) throw new ArgumentException("A minimum of 2 players may play this" + " game."); players = newPlayers; } private void DealHands() { for (int p = 0; p < players.Length; p++) { for (int c = 0; c < 7; c++) { players[p].PlayHand.Add(playDeck.GetCard(currentCard++)); } } } public int PlayGame() { // Code to follow. }    } }

Program.cs contains the Main() function, which will initialize and run the game. This function performs the following steps:

  • An introduction is displayed.

  • The user is prompted for a number of players between 2 and 7.

  • An array of Player objects is set up accordingly.

  • Each player is prompted for a name, used to initialize one Player object in the array.

  • A Game object is created, and players assigned using the SetPlayers() method.

  • The game is started using the PlayGame() method.

  • The int return value of PlayGame() is used to display a winning message (the value returned is the index of the winning player in the array of Player objects).

The code for this (commented for clarity) is shown here:

static void Main(string[] args) { // Display introduction. Console.WriteLine("KarliCards: a new and exciting card game."); Console.WriteLine("To win you must have 7 cards of the same suit in" + " your hand."); Console.WriteLine(); // Prompt for number of players. bool inputOK = false; int choice = -1; do { Console.WriteLine("How many players (2-7)?"); string input = Console.ReadLine(); try { // Attempt to convert input into a valid number of players. choice = Convert.ToInt32(input); if ((choice >= 2) && (choice <= 7)) inputOK = true;  } catch { // Ignore failed conversions, just continue prompting. } } while (inputOK == false); // Initialize array of Player objects. Player[] players = new Player[choice]; // Get player names. for (int p = 0; p < players.Length; p++) { Console.WriteLine("Player {0}, enter your name:", p + 1); string playerName = Console.ReadLine(); players[p] = new Player(playerName); } // Start game. Game newGame = new Game(); newGame.SetPlayers(players); int whoWon = newGame.PlayGame(); // Display winning player. Console.WriteLine("{0} has won the game!", players[whoWon].Name); }

Next, you come to PlayGame(), the main body of the application. Now, I'm not going to go into a huge amount of detail about this method, but I have filled it with comments to make it a bit more comprehensible. In actual fact, none of the code is that complicated, there's just quite a bit of it.

Play proceeds with each player viewing cards and an upturned card on the table. They may either pick up this card or draw a new one from the deck. After drawing a card each player must discard one, replacing the card on the table with another one if it has been picked up, or placing the discarded card on top of the one on the table (also adding the discarded card to the discardedCards collection).

One key point to bear in mind when digesting this code is the way in which the Card objects are manipulated. The reason that these objects are defined as reference types rather than as value types (using a struct) should now become clear. A given Card object may appear to exist in several places at once, because references can be held by the Deck object, the hand fields of the Player objects, the discardedCards collection, and the playCard object (the card currently on the table). This makes it easy to keep track of the cards and is used in particular in the code that draws a new card from the deck. The card is only accepted if it isn't in any player's hand or in the discardedCards collection.

The code is:

public int PlayGame() { // Only play if players exist. if (players == null) return -1; // Deal initial hands. DealHands(); // Initialize game vars, including an initial card to place on the // table: playCard. bool GameWon = false; int currentPlayer; Card playCard = playDeck.GetCard(currentCard++); discardedCards.Add(playCard); // Main game loop, continues until GameWon == true. do { // Loop through players in each game round. for (currentPlayer = 0; currentPlayer < players.Length; currentPlayer++) { // Write out current player, player hand, and the card on the // table. Console.WriteLine("{0}'s turn.", Console.WriteLine("Current hand:"); foreach (Card card in players[currentPlayer].PlayHand) { Console.WriteLine(card); } Console.WriteLine("Card in play: {0}", playCard); // Prompt player to pick up card on table or draw a new one. bool inputOK = false; do { Console.WriteLine("Press T to take card in play or D to " + "draw:"); string input = Console.ReadLine(); if (input.ToLower() == "t") { // Add card from table to player hand. Console.WriteLine("Drawn: {0}", playCard); // Remove from discarded cards if possible (if deck // is reshuffled it won't be  if (discardedCards.Contains(playCard)) { discardedCards.Remove(playCard); } players[currentPlayer].PlayHand.Add(playCard); inputOK = true; } if (input.ToLower() == "d") { // Add new card from deck to player hand. Card newCard; // Only add card if it isn't  // or in the discard pile bool cardIsAvailable; do { newCard = playDeck.GetCard(currentCard++); // Check if card is in discard pile cardIsAvailable = !discardedCards.Contains(newCard); if (cardIsAvailable) { // Loop through all player hands to see if newCard is // already in a hand. foreach (Player testPlayer in players) { if (testPlayer.PlayHand.Contains(newCard)) { cardIsAvailable = false; break; } } } } while (!cardIsAvailable); // Add the card found to player hand. Console.WriteLine("Drawn: {0}", newCard); players[currentPlayer].PlayHand.Add(newCard); inputOK = true; } } while (inputOK == false); // Display new hand with cards numbered. Console.WriteLine("New hand:"); for (int i = 0; i < players[currentPlayer].PlayHand.Count; i++) { Console.WriteLine("{0}: {1}", i + 1, players[currentPlayer].PlayHand[i]); } // Prompt player for a card to discard. inputOK = false; int choice = -1; do { Console.WriteLine("Choose card to discard:"); string input = Console.ReadLine(); try { // Attempt to convert input into a valid card number. choice = Convert.ToInt32(input); if ((choice > 0) && (choice <= 8)) inputOK = true; } catch { // Ignore failed conversions, just continue prompting. } } while (inputOK == false); // Place reference to removed card in playCard (place the card // on the table), then remove card from player hand and add // to discarded card pile. playCard = players[currentPlayer].PlayHand[choice - 1]; players[currentPlayer].PlayHand.RemoveAt(choice - 1); discardedCards.Add(playCard); Console.WriteLine("Discarding: {0}", playCard); // Space out text for players Console.WriteLine(); // Check to see if player has won the game, and exit the player // loop if so. GameWon = players[currentPlayer].HasWon(); if (GameWon == true) break; } } while (GameWon == false); // End game, noting the winning player. return currentPlayer; }

Have fun playing the game — and make sure that you spend some time going through it in detail. One thing to try is to put a breakpoint in the Reshuffle() method and play the game with seven players. If you keep drawing cards and discarding the cards drawn it won't take long for reshuffles to occur, as with seven players there are only three cards spare. This way, you can prove to yourself that things are working properly by noting the three cards when they reappear.




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net