Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Basis of many card games: How to make a deck of cards

4.00/5 (3 votes)
3 Aug 2009CPOL7 min read 116.9K  
Shows various methods to making a robust deck and card class.

Introduction

When most of us began programming, we started looking for various project ideas to try. I've noticed card games come up a lot, so let's have a look at some ways to implement a card game. We will be focusing on the core of many card games, the cards, and the deck.

Using the code

The Card class is fairly simple, although it can be done a number of ways. I've chosen to use numeric variables for the internal data about the suit and rank, which will get converted to strings via predefined arrays when they need to be outputted. This lets us easily compare the suits and ranks (see if they're equal or sequential), without trying to use strings to represent them internally. If we use strings, we'd have to use a whole bunch of other checks for different numbers, as opposed to:

Java
if ( card1.rank.equals("three") && card2.rank.equals("four") )

So, we end up with something like:

Java
if ( card1.rank + 1 == card2.rank)

So, we have read-only suit and rank variables, a simple constructor, and a toString method. The class will be ultra fast as it knows which strings to output just by accessing indexes of static arrays. We don't even have to use String.ParseInt().

The Card class really isn't that complicated, so most of our time will be spent on the Deck. Our Deck class will have to make and dole out Cards. To hold our Cards, we can use a number of different storage methods/structures. I will show examples using an array and an ArrayList. Each storage method will have a constructor to make the cards, a drawFromDeck() method that will return a random card, and a getTotalCards() method that will return the number of cards left in the Deck. The hard part is, once we return a card, we have to make sure it isn't used again.

Using array
Java
package javacards;
import java.util.Random;
public class Deck {
    private Card[] cards;
    int i;

    Deck()
    {
        i=51;
        cards = new Card[52];
        int x=0;
        for (int a=0; a<=3; a++)
        {
            for (int b=0; b<=12; b++)
             {
               cards[x] = new Card(a,b);
               x++;
             }
        }
    }

    public Card drawFromDeck()
    {
        Random generator = new Random();
        int index=0;

        do {
            index = generator.nextInt( 52 );
        } while (cards[index] == null);

        i--;
        Card temp = cards[index];
        cards[index]= null;
        return temp;
    }

    public int getTotalCards()
    {
        return i;
    }
}

First, we have a constructor which fills our deck with cards of various suits and ranks. Every time we add a card at x, we increment x to put us at a fresh spot.

drawFromDeck() returns a random card from our container. We need to get a random index and return that card. We have a post-test loop that finds an array index (less than 52) that isn't null. When we find one, we return its card and set it to null. Our deck might need to give feedback on how many cards are left (maybe to determine if there are enough for another round of black jack, or if we need to add all the cards back), so we make a variable i that represents the number of cards left in the deck. i starts at 51, and is decremented every time we take a card out. So if i is -1, then we know we're out of cards.

Now, we need to write a test for this program. We want to make sure the cards that get drawn really are drawn in a random order. So, we have Main:

Java
package javacards;
public class Main {

    public static void main(String[] args)
    {
        Deck deck = new Deck();
        Card C;

        System.out.println( deck.getTotalCards() );

       while (deck.getTotalCards()!= 0 )
       {
           C = deck.drawFromDeck();
           System.out.println( C.toString() );
       }
    }

When we run this, we get a good look at how the cards would come off the deck. They seem random enough, so we're okay. However, this tutorial would be pretty nit if I just ended it here, and many of you are probably looking at the drawFromDeck() method with disdain at its inefficiency, so we press on...

This is really a pretty sloppy way of doing things, as we just keep trying to find an index in the array with a card in it. If the index doesn't have a card in it, we look in a new index, until we finally get one. We really should only have to use one index, and we can do that if we make sure that we're only looking through places that actually have cards in them.

So to fix this, we use our variable i that represents the number of cards in the array that aren't used up yet. Now, we need to make sure our selection comes only from that set of cards. Say the first card we take out is at index 4. We set that to null and decrement the number of cards. But, index 4 is still null. We know we have 51 cards left, but we don't know where they are in the deck. There could be empty/null indexes all over the place, at 4, 7, 20, and all we can do is just keep our fingers crossed that we don't land on one of them. How about we organize our empty card spots a little better? Maybe we can switch the chosen card's empty spot with the last card in the deck? Let's put our i variable to work.

Array example number 2
Java
package javacards;
import java.util.Random;
public class Deck {
    private Card[] cards;
    int i;

    Deck()
    {
        i=51;
        cards = new Card[52];
        int x=0;
        for (int a=0; a<=3; a++)
        {
            for (int b=0; b<=12; b++)
             {
               cards[x] = new Card(a,b);
               x++;
             }
        }
    }

    public Card drawFromDeck()
    {
        Random generator = new Random();
        int index=0; 

        index = generator.nextInt( i );

        Card temp = cards[index];
        cards[index]=cards[i];
        cards[i]=null;
        i--;
        return temp;
    }
}

Our random index now is some number within the boundaries of the cards left, instead of always 52. If we have 40 cards left, index won't be 48. We get a random card, switch it's now null location with the last card in the deck, and then return the random card. We picked the fourth card, and put the last card in its place. The null value we get when we take out the card at index 4 is now located at spot 51, the last spot in the deck, as opposed to some random spot in the middle of the deck. This ensures that the indexes up through i always have cards in them, and that the null values are always moved to the end of the array. When we get a random index between 0 and i, we can be 100% sure that there is a fresh card in that spot.

To test this method, we can use the exact same test code as we did for the first example, as all that's changed is the internal workings of the Deck class. In fact, we will use that test code for all our Deck implementations.

We've implemented a lot of features with arrays that could've easily been done with generics, like an ArrayList (adding adds to the end of the array, removing a card by setting its place to null, keeping track of the size of the array, etc. ). An ArrayList has most of the features we need built in already, so we don't have to write our own code to do them.

ArrayList example
Java
package javacards;
import java.util.Random;import java.util.ArrayList;
public class Deck {
    private ArrayList<card /> cards;

     Deck()
    {
        cards = new ArrayList<card />();
        for (int a=0; a<=3; a++)
        {
            for (int b=0; b<=12; b++)
             {
               cards.add( new Card(a,b) );
             }
        }
    }

    public Card drawFromDeck()
    {
        Random generator = new Random();
        int index= generator.nextInt( cards.size() );
        return cards.remove(index);
    }

     public int getTotalCards()
    {
        return cards.size();
    }
}

We can just add a card to the end of the ArrayList with add(), as opposed to making our own variable to keep track of what index we're on. cards.size() automatically keeps track of how many cards are left in the array. When we return a card, we can actually remove it from the ArrayList, thus decrementing the size, leaving us with a collection that only has cards left in it (as opposed to just replacing its spot with the last card in the array, and setting the last spot to null).

Run the test code again to make sure everything still works.

To mention other data structures, we could have used a linked list to hold the cards, but our primary purpose is to extract a card from the deck, not to iterate through the cards, making a linked list not the best choice.

One of the goals of OO programming is to emulate the real world. When we return a card from our deck, we remove it from the deck so it's not used again, just like a real dealer would. But when a dealer gives you a card, they just take it off the top of the deck, they don't fish through the deck to get a random card. They shuffle the deck, randomizing the cards before hand, so they can just pop cards off the top later and know that they're random. This leads us to another example:

ArrayList 2: shuffling the cards before we deal them
Java
package javacards;
import java.util.Random;import java.util.ArrayList;
public class Deck {
    private ArrayList<card /> cards;

     Deck()
    {
        cards = new ArrayList<card />();
        int index_1, index_2;
        Random generator = new Random();
        Card temp;

        for (int a=0; a<=3; a++)
        {
            for (int b=0; b<=12; b++)
             {
               cards.add( new Card(a,b) );
             }
        }


        for (int i=0; i<100; i++)
        {
            index_1 = generator.nextInt( cards.size() - 1 );
            index_2 = generator.nextInt( cards.size() - 1 );

            temp = (Card) cards.get( index_2 );
            cards.set( index_2 , cards.get( index_1 ) );
            cards.set( index_1, temp );
        }
    }

    public Card drawFromDeck()
    {       
        return cards.remove( 0 );
    }

    public int getTotalCards()
    {
        return cards.size();
    }
}

We put the cards in the ArrayList, then randomly take 100 pairs of cards and switch them, shuffling our deck. To draw from the deck, we just return the last element/card, and then remove that card from the deck. All the randomization is done before hand in the constructor (prior to any actual dealing of the cards), making our drawFromDeck method much simpler and less processor intensive. Piece of cake! :)

I hoped you've enjoyed and learned something from this tutorial!

Please post any and all questions, comments, concerns, or commentary!

History

  • 8-3-09: Submitted article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)