DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 5.10 Simulating Playing Cards

5.10.1 Problem

You want to use ActionScript to deal cards for a card game using a standard 52-card deck (without jokers).

5.10.2 Solution

Create a custom Cards class that uses the randRange( ) method to deal the cards.

5.10.3 Discussion

To work with playing cards within your Flash movies, you should create a custom Cards class. The Cards class should have an array property that contains all the cards in the deck and a deal( ) method that deals the cards to a specified number of players. Additionally, you should define a supporting class named CardPlayer. The CardPlayer class represents each player in a card game, including their hands of cards. Let's take a look at the example code first:

// Include the Math.as file on whose methods this recipe relies.
#include "Math.as"

// When a new card player is created by way of its constructor, pass it a reference
// to the card deck and give it a unique player ID.
function CardPlayer (deck, id) {
  this.hand = null;
  this.deck = deck;
  this.id = id;
}

// The setHand(  ) method sets the cards in a player's hand.
CardPlayer.prototype.setHand = function (hand) {
  this.hand = hand;
};

// The getHand(  ) method returns the cards in a player's hand.
CardPlayer.prototype.getHand = function (  ) {
  return this.hand;
};

// The draw(  ) method deals the specified number of cards to the player.
CardPlayer.prototype.draw = function (num) {
  this.deck.draw(this.id, num);
};

// The discard(  ) method discards the cards specified 
// by their indexes in the player's hand.
CardPlayer.prototype.discard = function (cardsArIds) {
  cardsArIds.sort(  );
  for (var i = 0; i < cardsArIds.length; i++) {
    this.hand.splice(cardsArIds[i] - i, 1);
  }
};

// The Cards constructor creates a deck of cards.
function Cards (  ) {

  // Create a local array that contains the names of the four suits.
  var suits = ["Hearts", "Diamonds", "Spades", "Clubs"];

  // The cards property is an array that is populated with all the cards.
  this.cards = new Array(  );

  // Specify the names of the cards for stuffing into the cards array later.
  var cardNames = ["2", "3", "4", "5", "6", "7", "8", "9", "10",
                    "J", "Q", "K", "A"];

  // Create a 52-card array. Each element is an object that contains properties for
  // the card's integer value (for sorting purposes), card name, suit name, and
  // display name. The display name combines the card's name and suit in a single
  // string for display to the user.
  for (var i = 0; i < suits.length; i++) {
    // For each suit, add 13 cards.
    for (var j = 0; j < 13; j++) {
      this.cards.push(
                      {val: j, 
                       name: cardNames[j], 
                       suit: suits[i],
                       display: cardNames[j] + " " + suits[i]});
    }
  }
}

// The deal(  ) method needs to know the number of players in the game and the number
// of cards to deal each player. If the cardsPerPlayer parameter is undefined, then
// it deals all the cards.
Cards.prototype.deal = function (numOfPlayers, cardsPerPlayer) {

  // Create an array, players, that holds the cards dealt to each player.
  var players = new Array(  );

  // The players array contains CardPlayer objects. Each card player is given a
  // reference to this deck of cards, as well as a unique player ID.
  for (var i = 0; i < numOfPlayers; i++) {
    players.push(new CardPlayer(this, i));
  }

  // Make a copy of the deck of cards from which we'll deal. This way, we can always
  // recreate a fresh deck from the original cards property.
  this.cardsToDeal = this.cards.concat(  );

  // If a cardsPerPlayer value was passed in, deal that number of cards. Otherwise,
  // divide the number of cards (52) by the number of players.
  var cardsEach = cardsPerPlayer;
  if (cardsPerPlayer == undefined) {
    cardsEach = Math.floor(this.cards.length / numOfPlayers);
  }

  // Deal the specified number of cards to each player.
  var rand, hand;
  for (var i = 0; i < numOfPlayers; i++) {

    hand = new Array(  );

    // Deal a random card to each player. Remove that card from the
    // CardsToDeal array so that it cannot be dealt again.
    for (var j = 0; j < cardsEach; j++) {
      rand = Math.randRange(0, this.cardsToDeal.length - 1);
      hand.push(this.cardsToDeal[rand]);
      this.cardsToDeal.splice(rand, 1);
    }

    // Use Cards.orderHand(  ) to sort a player's hand and use setHand(  ) to assign it
    // to the card player object.
    players[i].setHand(Cards.orderHand(hand.concat(  )));
  }

  // Return the players array.
  return players;
};

// The Cards.draw(  ) method is called from CardPlayer.draw(  ) to draw the specified
// number of cards from the deck and add them to the player's hand.
Cards.prototype.draw = function (player, numToDraw) {

  // Get the player's current hand.
  var hand = players[player].getHand(  );

  // Add the specified number of cards to the hand.
  for (var i = 0; i < numToDraw; i++) {
    rand = Math.randRange(0, this.cardsToDeal.length - 1);
    hand.push(this.cardsToDeal[rand]);
    this.cardsToDeal.splice(rand, 1);
  }

  // Sort the hand and reassign it to the player object.
  var orderedHand = Cards.orderHand(hand);
  players[player].setHand(orderedHand);
};

// Used by sort(  ) in the orderHand(  ) method to sort the cards by suit and rank
Cards.sorter = function(a, b) {
  if (a.suit > b.suit) {
    return 1;
  } else if (a.suit < b.suit) {
    return -1;
  } else {
    return (Number(a.val) > Number(b.val))
  }
};

// This method sorts an array of cards by calling the sort(  ) method on that array
// with a sorter method of Cards.sorter(  ).
Cards.orderHand = function(hand) {
  hand.sort(Cards.sorter);
  return hand;
};

Now let's look more carefully at some of the pieces of the CardPlayer and Cards classes and what they do.

First, we define the CardPlayer class constructor. Each CardPlayer object contains three properties: the hand (which is an array of cards), a reference to the deck from which the game is being played (an instance of the Card class), and a player ID that is unique within the game (i.e., each player is identified by a number, such as 0, 1, 2, or 3). The CardPlayer class defines getter and setter methods for the hand property and draw( ) and discard( ) methods to obtain new cards and remove old ones.

The first part of the Card class is the constructor method, which creates an array of all possible cards. There are many ways to approach this. The example uses nested for statements to create a 52-card array (the loop executes 13 times for each of the 4 suits). First, it creates an array specifying the names of the four suits and another array specifying the names of each card. Then, it populates the cards array by creating 13 cards for each suit. The push( ) method appends a new object for each card, which becomes an element of the cards array.

The deal( ) method creates an array of card players. It returns an array of CardPlayer objects (one for each player in the game). The number of players is determined by the numOfPlayers parameter, allowing for flexibility in how the cards are dealt. The code "deals" the cards by randomly selecting an element (card) from the cardsToDeal array and adding it to a player's hand. It is important that the selected element is removed from cardsToDeal, as shown using splice( ) in this example, so that it is not dealt twice. Next, each player's hand is sorted using the orderHand( ) method, and then each player's hand's contents are set for each player's CardPlayer object.

The orderHand( ) and sorter( ) methods work together to sort a user's hand by suit and then by rank. The orderHand( ) method calls the sort( ) method on the array, with sorter( ) as the custom sorter function.

Additionally, the Card class defines a draw( ) method, which is useful for games such as poker in which players can draw additional cards. The draw( ) method draws a specified number of cards from the deck and adds them to a player's hand.

This example code tests the Cards class:

// First, create a new Cards object.
c = new Cards(  );

// Call the deal(  ) method, specifying four players and six cards per player.
players = c.deal(4, 6);

// For each player . . . 
for (var i = 0; i < players.length; i++) {

  // Display the player number.
  trace("player " + i);

  // Display the player's cards.
  for (var j = 0; j < players[i].getHand(  ).length; j++) {
    trace("  " + players[i].getHand(  )[j].display);
  }
}

// Now, for the first player, discard the first and third cards from the hand and
// draw two new cards.
players[0].discard([0, 2]);
players[0].draw(2);

// Now display the players' hands again. Notice that the first player has two 
// different cards while the rest of the players have the same hands as before.
for (var i = 0; i < players.length; i++) {

  // Display the player number.
  trace("player " + i);
  // Display the player's cards.
  for (var j = 0; j < players[i].getHand(  ).length; j++) {
    trace("  " + players[i].getHand(  )[j].display);
  }
}

Note that our code draws cards from the deck randomly rather than from the "top." This is necessary because we added the cards in rank and suit order, so drawing from the top would not give a randomized deal. An alternative solution is to randomize (shuffle) the cards when creating the array and then deal the cards in order from the deck. In that case, the programmer should be careful to shuffle the cards again each time a new deck of cards is created from the master copy (rather than shuffling the master copy, which would result in all games being played with the same deck of cards).

Also note that our example doesn't deal gracefully with games in which you might run out of cards. It assumes that 52 cards is sufficient for the current hand and that a new deck will be created for each subsequent hand. Enhancing the example is left to you as an exercise.

    [ Team LiB ] Previous Section Next Section