iPhone Game Programming Series: Blackjack – Part 1: The Deck

September 9th, 2010 Posted by: brandontreb - posted under:Tutorials

It has been quite some time since our last iPhone video game series and now we are ready to start a new one.  Given the success of our iTennis tutorial series, we will be following along the same line and create a game without using OpenGL ES.  If you are interested in OpenGL ES programming, check out , he’s super rad. In this series we will be creating a simple Blackjack game with the following features/functionality:

  • Deck of cards
  • Basic Blackjack rules/logic
  • Dealer
  • Basic Dealer AI
  • Player
  • Controls for hit/stand
  • Audio
  • User Interface

In this first part of the series we are going to introduce the core of the game; the deck.  We will be building a very generic deck of cards that could be used in any type of card game.  The deck will be portable enough to just drag and drop into any project that requires a deck of cards. So, let’s begin by creating a view based application and calling it ICBBlackjack.

Creating A Card Object

Before we can create the deck, we need to create a card. Before we dig into the code, let me explain how a card object will work.  A card has 2 properties that we care about.  They are suit and value.  Suit is pretty obvious (Hearts, Diamonds, Spades, Clubs), but the value might not be what you’d expect.  For the cases of the numeric cards, the value is simply their value, however it changes a bit for the face cards and ace.

The Ace gets assigned a value of 1 (pretty obvious), while the face cards get values based on their ordering from the 10 card.  So a Jack = 11, Queen = 12, and King = 13.  Obviously this isn’t the case when playing a game like Blackjack, but we will let another class figure that out. For now, we will use the value as and identifier for the card. Add a new class file to your project called Card.m.  Make sure you check the box to include the .h file. Open up Card.h and add the following code:

Card.h

typedef enum {
        Hearts,
        Diamonds,
        Spades,
        Clubs
} Suit;
 
#define Ace   1
#define Jack  11
#define Queen 12
#define King  13
 
@interface Card : NSObject {
        NSInteger value;
        Suit suit;
}
 
@property (nonatomic) NSInteger value;
@property (nonatomic) Suit suit;
 
- (id) initWithValue:(NSInteger) aValue suit:(Suit) aSuit;
 
@end

Ok, so a few things here to point out.  The first thing we see at the top is an enum.  The enum allows us to declare our own type called Suit.  We could have just as easily used constants, but the enum makes things a little more clear.  This allows us to do things like myCard.suit = Clubs.

Now, we define the “Special” values which will be assigned to the Ace and face cards. Finally, we declare our value and suit properties and declare and init method to build a card with these values set. Now, it’s time to implement our Card class.  Open Card.m and add the following code.

Card.m

#import "Card.h"
 
@interface Card(Private)
 
- (NSString *) valueAsString;
- (NSString *) suitAsString;
 
@end
 
@implementation Card
 
@synthesize value,suit;
 
- (id) initWithValue:(NSInteger) aValue suit:(Suit) aSuit {
        if(self = [super init]) {
                self.value = aValue;
                self.suit = aSuit;
        }
        return self;
}
 
- (NSString *) valueAsString {
        switch (self.value) {
                case Ace:
                        return @"Ace";
                        break;
                case Jack:
                        return @"Jack";
                        break;
                case Queen:
                        return @"Queen";
                        break;
                case King:
                        return @"King";
                        break;
                default:
                        return [NSString stringWithFormat:@"%d",self.value];
                        break;
        }
}
 
- (NSString *) suitAsString {
        switch (self.suit) {
                case Hearts:
                        return @"Hearts";
                        break;
                case Diamonds:
                        return @"Diamonds";
                        break;
                case Spades:
                        return @"Spades";
                        break;
                case Clubs:
                        return @"Clubs";
                        break;
                default:
                        return nil;
                        break;
        }
}
 
- (NSString *) description {
        return [NSString stringWithFormat:@"%@ of %@",
                        [self valueAsString],
                        [self suitAsString]];
}
@end

Wow, so this looks like a lot of overkill.  The truth is, you only need the init method that I created.  The description method allows us to overwrite the printing method for a card and gives us pretty output.  The other 2 methods are simply help methods for it (we have declared them as private at the top).  You can choose to omit them if you like, but make debugging a lot easier.  When you do something like NSLog(@”%@”,myCard), it will print “Ace of Spades”. Great, now you have a Card object to work with.  Now we just need to create 52 of these puppies and we are on our way to hundreds of card games.

Creating The Deck

The deck of cards is fairly straight forward.  First off, we need an array of cards.  The card needs to have 3 methods implemented for correct functionality.  They are draw, shuffle, and cardsRemaining.  The reason the cardsRemaining method is needed is to prevent users from trying to draw cards from an empty deck (we will discuss this in a bit). Add a new class file to your project called Deck.m and add the following code to Deck.h:

Deck.h

#import "Card.h"
 
@interface Deck : NSObject {
 
@private
        NSMutableArray *cards;
}
 
- (void) shuffle;
- (Card *) draw;
- (NSInteger) cardsRemaining;
 
@end

A few things I want to point out.  First, we import Card.h.  Normally we would import the models in the .m file if they are not needed in the header.  However, we need to declare the draw method which returns a Card object.  So, we include the import statement here.

The next thing I want to point out is we have marked the cards array as private.  We don’t want anyone mucking around in our deck without going through our methods.  The reason for this is to prevent synchronization issues where someone modifies the deck without notifying the class.  Kinda trivial, but good practice non the less. Now for the implementation.  Add the following code to Deck.m.

Deck.m

#import "Deck.h"
 
@implementation Deck
 
- (id) init {
        if(self = [super init]) {
                cards = [[NSMutableArray alloc] init];
                for(int suit = 0; suit <= 3; suit++) {
                        for(int value = 1; value <= 13; value++) {
                                Card *card = [[Card alloc] initWithValue:value suit:suit];
                                [cards addObject:card];
                                [card release];
                        }
                }
        }
        return self;
}
 
/*
 * Random sort used from this blog post
 * http://zaldzbugz.wordpress.com/2010/07/16/randomly-sort-nsarray/
 */
int randomSort(id obj1, id obj2, void *context ) {
        // returns random number -1 0 1
        return (arc4random()%3 - 1);
}
 
- (void) shuffle {
        for(int x = 0; x < 500; x++) {
                [cards sortUsingFunction:randomSort context:nil];
        }
}
 
- (Card *) draw {
        if([self cardsRemaining] > 0) {
                Card *card = [[cards lastObject] retain];
                [cards removeLastObject];
                return [card autorelease];
        }
 
        NSException* myException = [NSException
                exceptionWithName:@"OutOfCardsException"
                reason:@"Tried to draw a card from a deck with 0 cards."
                userInfo:nil];
        @throw myException;
}
 
- (NSInteger) cardsRemaining {
        return [cards count];
}
 
- (NSString *) description {
        NSString *desc = [NSString stringWithFormat:@"Deck with %d cards\n",[self cardsRemaining]];
        for(int x = 0; x < [self cardsRemaining]; x++) {
                desc = [desc stringByAppendingFormat:@"%@\n",[[cards objectAtIndex:x] description]];
        }
        return desc;
}
 
- (void) dealloc {
        [cards release];
        [super dealloc];
}
 
@end

Ok, this file needs a little more explanation.  First, we see the init method has been implemented.  There are 2 for loops.  The outer loop is from 0 to 3 representing the suit of each card.  Basically, we want to create 13 cards for each of the 4 suits.  The inner loop is from 1 to 13 representing the card’s value.  We simply instantiate a Card object with the suit and value and add it to the cards array. Let’s chat about each of the methods.

Shuffle

The shuffle method simply sorts the array based on a random number.  If we do it once, the deck will barely be shuffled, which makes sense.  So, I have randomly sorted the array 500 times to ensure that it has been effectively shuffled.  Note: I stole the randomSort method from http://zaldzbugz.wordpress.com/2010/07/16/randomly-sort-nsarray/ based on a quick/lazy Google search, but it’s a pretty common way of accomplishing this task.

Draw

We first check to see if the deck is empty. If it’s not, we retain the last object, remove it from the array and return an auto release of it.  It will be up to the caller to retain the Card.  Here is something you don’t see everyday, we are throwing an exception if the user tries to draw from an empty deck.  Kind of a jerk move, but it will ensure that they check the cardsRemaining before performing a draw.  Again, not totally necessary, but good practice.

cardsRemaining

Returns the length of the cards array I override the description method in this class to print out the deck in its entirety.  Again, this is very useful for debugging. Finally, we rock the memory management and release our card deck array when our deck gets cleaned up.

Sample Run/Output

Here is a simple example of how to create a deck, shuffle, and draw.

        Deck *d = [[Deck alloc] init];
        NSLog(@"%@",d);
 
        [d shuffle];
        NSLog(@"%@",d);
 
        NSLog(@"Drew Card: %@",[d draw]);
        NSLog(@"Drew Card: %@",[d draw]);
        NSLog(@"Drew Card: %@",[d draw]);
        NSLog(@"Drew Card: %@",[d draw]);
 
        NSLog(@"%@",d);
 
        [d release];

The output is a little long, so I’ll just past some snippets of it.  You can run this for yourself to see the full output.

Deck with 52 cards
Ace of Hearts
2 of Hearts
3 of Hearts
4 of Hearts
5 of Hearts
6 of Hearts
7 of Hearts
8 of Hearts
9 of Hearts
10 of Hearts
Jack of Hearts
Queen of Hearts
King of Hearts
Ace of Diamonds
2 of Diamonds
3 of Diamonds
...
(After shuffle)
Deck with 52 cards
King of Diamonds
6 of Hearts
5 of Spades
9 of Clubs
2 of Diamonds
8 of Clubs
7 of Hearts
Ace of Clubs
10 of Diamonds
Jack of Diamonds
8 of Spades
6 of Diamonds
Ace of Spades
3 of Spades
...
2010-09-07 14:56:37.953 ICBBlackJack[5465:207] Drew Card: Queen of Spades
2010-09-07 14:56:37.953 ICBBlackJack[5465:207] Drew Card: 9 of Spades
2010-09-07 14:56:37.954 ICBBlackJack[5465:207] Drew Card: 9 of Hearts
2010-09-07 14:56:37.955 ICBBlackJack[5465:207] Drew Card: Queen of Clubs

Deck with 48 cards
King of Diamonds
6 of Hearts
5 of Spades
9 of Clubs
2 of Diamonds
8 of Clubs
7 of Hearts
Ace of Clubs
10 of Diamonds
Jack of Diamonds
8 of Spades
6 of Diamonds
Ace of Spades

Note that drawing a card pulls from the end of the array. It doesn’t matter if you pull from the front or the back, just make sure it’s consistent.

Conclusion

Click Here To Download The Code For This Tutorial

And there you have it! A fully functional deck of cards.  Please be sure to join me next time when we will start implementing the dealer and some basic Blackjack logic. Click Here To Download The Code For This Tutorial Feel free to post questions in the comments section or .

  • Leddo

    Great tutorial; clear, concise and well thought out. Thank you ! Look forward to the next installment.

  • http://drudoo.com Drudoo

    Nice tutorial. Looking forward to the next one.

  • http://icode.dreamvision-soft.com/blog/?p=81 iPhone Game Programming Series: Blackjack – Part 1: The Deck | iCode

    [...] Original post on iCodeBlog [...]

  • http://ajfek.pl Pawel Kata

    Awesome stuff! Thanks a lot! :D

  • Rick

    You don’t need to import Cards.h in Deck.h.

    Deck.h just needs to be aware that the Card class exists so you can use a forward reference instead.

    @class Card;

    @interface Deck : NSObject {

    @private
    NSMutableArray *cards;
    }

    - (void) shuffle;
    - (Card *) draw;
    - (NSInteger) cardsRemaining;

    @end

  • Rick

    Forgot to mention that you’ll need to make sure you do the #import Card.h in Deck.m

  • pentwo

    Nice tutorial.
    Looking forward the next part.

  • http://waynedahlberg.com Wayne Dahlberg

    I really appreciate you taking the time to step us noobs through this. It’s like gold to someone trying to wrap their brain around obj.c.

  • Krishn

    Great Tutorial.

    Waiting for next part

  • http://www.oyunturk.org yarış oyunları

    thanks for article…

    good luck !

  • Mark

    Doing a build and analyze complains about randomSort() it wants the return type to be an NSInteger.

    I changed it to this and the warning went away

    NSInteger randomSort(id obj1, id obj2, void *context ) {

  • 1024

    You have some HTML codes in your code. Instead of “less than” and “greater than” signs HTML codes &lt and &rt show up.

  • http://brandontreb.com brandontreb

    Good catch, it’s pretty trivial though. Should work either way. Clang is a little picky sometimes. :)

  • http://brandontreb.com brandontreb

    Why do you feel that your approach is preferred?

    With mine, there is only one import statement rather than declaring the @class in the .h and doing the import in the .m.

    I have seen that style before and I’m curious as to its benefits, perhaps you can shed some light on it for me?

  • jake

    Nice ! thanks keep these coming.

  • Janice

    Why do you break all C and Objective-C conventions in so many places? You don’t even use UPPER case for defines????

    #define Ace 1
    #define Jack 11
    #define Queen 12
    #define King 13

  • Kimmy

    *PLEASE* post only code that you know will compile correctly:

    for(int x = 0; x &-l-t-; 500; x++) {

  • http://brandontreb.com brandontreb

    Kimmy,

    This is an issue with the plugin for wordpress. I don’t have control over it. Download the sample which does compile.

    Sorry for your inconvenience.

  • http://brandontreb.com brandontreb

    Meh…

  • http://brandontreb.com brandontreb

    yeah, stupid wordpress plugin. I’d be open for suggestions for better ones?

  • Brian

    Because your way is prone to circular import problems.
    By doing forward declares, you never have to worry about such things.

  • http://brandontreb.com brandontreb

    @Brian,

    Ahh, totally. Can’t believe I didn’t see that.

    I will make sure to do forward declares in future tutorials.

    Thanks for pointing that out.

  • gina

    fantastic. I have been a fan and follower for the last few months and have found your tutes a great tool for learning. Thanks for taking the time to reach out to us. look forward to more.

  • http://waynedahlberg.com Wayne Dahlberg

    Eagerly awaiting part two. Don’t pay attention to the semantic nazis, this was/is a great tutorial. :)

  • ciwol

    Hi,
    nice tuto, i’m creating a card game (bellote) and i used your code and it helped a lot,

    actually i’ve a problem with the split functiun (share the deck in 4 hands), if you have idea or a few time to help, i’ll love it!

    @+ and thx

  • RobbPell

    Great tutorial can’t wait for part two.

  • http://www.twitter.com/charlie_fulton Charlie

    Brandon,

    Awesome job, this was a great tutorial. It had been awhile since I had been on icodeblog, love the new design!

  • Prefect

    Great Tutorial so far,
    but when is the next part coming?

    This is like a good series. Cliffhanger and then waiting for the next episode.
    But this one seems to never come.

  • Justin Hinerman

    When is part 2? This is great.