Assignment 16: Part 2 (Deck)

Task

Now that you have a PlayingCard, you can model a deck of playing cards.

Steps

Create a class named Deck. It should contain the following public methods. Again, the (reusable) documentation explains what each should do.

/**
 * Constructs a new deck of 52 standard playing cards with no jokers.
 * If the parameter shuffled == true, the new deck will be shuffled.
 * Otherwise, it will be in sorted order.
 */
public Deck(boolean shuffled)

/**
 * Constructs a deck of 52 shuffled playing cards.
 */
public Deck()

/**
 * Removes the top card from this deck and returns it.
 * If this deck is empty, will return null instead.
 */
public PlayingCard draw()

/**
 * Returns the number of cards currently remaining in this deck.
 */
public int getSize()

/**
 * Shuffles the cards remaining in this deck.  That is, only
 * reorders those cards that have not yet been drawn.  The deck's
 * current size does not change and any previously drawn cards 
 * are NOT returned to the deck.
 */
public void shuffle()

Make sure your method signatures match these exactly. Any instance variables you have should be private. All you really need is a PlayingCard[] and int count variable. (Or, if you'd rather use an ArrayList instead, one ArrayList<PlayingCard>.)

When constructing a Deck, you need to add cards to it in some sort of order so that you know you generated and added one of each of the 52 cards. The particular order is up to you--all the Aces, then all the 2s, etc; or all of one suit and then all of the next suit.

The first Deck constructor allows the user of your code to ask for a deck either with or without shuffling. Most of the time, they'll probably want it shuffled though, so the second constructor makes the deck a little easier to use. [Hint: Writing the second constructor is very simple if you remember how to call one constructor from another.]

For the draw() method, we don't want the Deck to crash if some programmer accidentally tries to draw a card from an empty deck. There's not much we can do in this situation though--either document what exception the method will throw in this situation, or else return null. For this implementation, I chose the latter approach.

For the shuffle() method, check out the Fisher-Yates algorithm for a relatively easy, but efficient and effective shuffling method. See the FAQ on Shuffling Tips below for more discussion of this.

You may write additional methods if you desire--such as a toString() method--but only those listed above are required.

Sample Output

Depends on how much you want to test your Deck at this point.

What to Submit

Hang onto this file for now--you're not going to submit it until after you finish A16-Part 3.

Grading

See A16-Part 3.

FAQs

So, although we can create Joker playing cards, there aren't actually going to be any in the Deck?
That is correct. Mostly, the Joker was a nice clean way to handle invalid arguments to the PlayingCard constructor (since we haven't learned how to throw exceptions yet, only catch them). It is true that, for certain games, someone might actually want Jokers included in a new deck. So we could provide a third constructor: Deck(boolean shuffled, int jokers), which would allow someone to specify exactly how many jokers they want added (since sometimes you only want one, not two). But this extra code is not really necessary at this point.
Why are there two constructors? What is the difference between them? How do you use the constructor that takes a boolean parameter?
The idea here is that sometimes you may want the cards in a new Deck to be ordered, though most of the time you'll probably want them shuffled. The first constructor lets you specify this when you create a deck object in your main method:
  Deck ordered = new Deck(false); //ordered deck
  Deck random = new Deck(true); //shuffled deck

However, most of the time, you'll proably end up giving true as the argument. Therefore, the second constructor should do the same thing as Deck(true) and provide a shuffled deck:

  Deck mixedUp = new Deck(); //shuffled deck

This makes the Deck easier/faster to use most of the time.

To implement this, write the Deck(boolean) constructor first. Load the cards into the array in some sort of order using a couple for loops. Then, after the loops, you can:

  if (shuffled) {
    this.shuffle();
  }

See how this will call the shuffle() method if shuffled == true and will skip the call if shuffled == false? Also, this way your shuffling code occurs only in one place: in your shuffle method!

You can then write the body of Deck() in one line by invoking the other constructor with the value of true. As I mentioned in lab, you do this with the line:

this(true);
How do I load the array with cards?
Well, first you need to declare the PlayingCard[] as an instance variable. Then construct an array of size 52. You can either do this when you declare the variable or at the start of the constructor (as shown here):
  private PlayingCard[] cards;
  ...

  public Deck(boolean shuffled) {
    this.cards = new PlayingCard[52];
    ...
  }

Then you need to load the cards array with 52 new PlayingCard objects. Each element of the array will hold a (reference to) a PlayingCard. (And remember that each PlayingCard object then has its own suit and value stored inside it, along with associated methods. Pretty neat, huh?) So, use one or two loops to generate all 52 combinations of suit and value, and, in the inner loop:

  cards[count] = new PlayingCard(value, suit);
  count++;

You need a count instance variable to tell you how many cards are left in your array as you draw them out later. So you can use that here to also load the array. It can start at 0; after the cards are all loaded, it will be 52.

As always, there are other ways to do this, but this technique is short and sweet, especially if you're stuck.

How do I know if my Deck was constructed correctly?
Probably the easiest way is to temporarily print out the contents of the array that contains all the Deck's cards at the end of the constructor. This will verify that the constructor did its job. To do this, you'll either need to use a for loop or else figure out how java.util.Arrays.toString(Object[]) works.

If you chose to use an ArrayList instance variable (instead of an array) and named it cards, then you can simply add this line as the last line of the Deck constructor:

  System.out.println(this.cards);

As with printing any object, this println statement actually invokes the ArrayList's toString() method, which in turn will invoke your PlayingCard's toString() method to print each card in the list.

Remember this printing is just for debugging purposes to see if your constructor worked. Once you're done testing, remove it!

Shuffling tips

Shuffling, like many algorithms, is fairly easy to accomplish poorly. This blog post by Jeff Atwood describes what I mean by this.

You are not required to use Fisher-Yates to shuffle your cards for this assignment. However, once you know there's a better way to do something, it makes sense to do it that better way. :)

The Fisher-Yates algorithm is not very complex: 1 loop in which you generate a single random number and then swap to elements of an array. But you still have to understand what it's doing. I think the easiest way to make sense of the algorithm by looking at the pen and pencil examples given on the Wikipedia page.

To help you further, here is an implementation in Java that will shuffle an array of ints: ShuffleExample.java.

Note that if you use this code as a starting point for your own shuffle method, you will have to change the code given in a number of ways:

How do I test my Deck?
One way to test it is with a few temporary print statements as you're writing the code, as described in the FAQ above regarding the constructor.

For more comprehensive testing, though, it is hard to test your deck when you can't see its current state. Therefore, I'd recommend you write a method that prints all the undrawn cards currently remaining in the array/ArrayList. This method should not change the state of those cards though. This could either be a print() or toString() method. For example:

  public String toString() {
    String cardsLeft = "";
    for (int i = this.count - 1; i >= 0; i--) {
      cardsLeft += this.cards[i].toString() + "\n";
    }
    return cardsLeft;
  }

Then write a main method that does the following:

On to A16, Part 3 →