Programming: Arduino

Check out my other articles:


Blackjack

My friends at Vizic Technologies challenged me to write a version of Blackjack .. here it is. This is a fully featured version and allows the player to split pairs, double down and take insurance on a dealer blackjack. It is missing a couple of features - such as being able to Surrender a hand (BTW we don't play that in Australia) - but I am guessing another keen developer could easily implement these.

This version uses the 480 x 320 pixel version of the SmartGPU2 but since completing the application I have received a beautiful 480 x 272 SmartGPU2 and I might look at how I can reduce the graphics down to fit on this slightly narrower board.


Overview

This is a classic game of Blackjack. The rules it supports are essentially the same as that found in Las Vegas casinos but the code could easily be changed to support the variations found in other parts of the United States, Europe and Australia.

The rules of this game are as follows:

  • This version of Blackjack is played with a single 52-card deck.
  • Aces may be counted as 1 or 11 points, 2 to 9 according to pip value, and tens and face cards count as ten points.
  • The value of a hand is the sum of the point values of the individual cards. Except, a "Blackjack" is the highest hand - consisting of an ace and any 10-point card, and it outranks all other 21-point hands.
  • After the player has bet, the dealer will give two cards to the player and two cards to himself. One of the dealer cards is dealt face up. The facedown card is called the "hole card."
  • If the dealer has an ace showing, he will offer a side bet called "insurance." This side wager pays 2 to 1 if the dealer's hole card is any 10-point card. Insurance wagers are optional and may not exceed half the original wager.
  • If the dealer has a ten or an ace showing (after offering insurance with an ace showing), then he will peek at his facedown card to see if he has a blackjack. If he does, then he will turn it over immediately.
  • If the dealer does have a blackjack, then all wagers (except insurance) will lose, unless the player also has a blackjack, which will result in a push. The dealer will resolve insurance wagers at this time.
  • The player now plays his hand. The following are the choices available to the player:
    • Stand: Player stands pat with his cards.
    • Hit: Player draws another card (and more if he wishes). If this card causes the player's total points to exceed 21 then he loses.
    • Double: Player doubles his bet and gets one, and only one, more card.
    • Split: If the player has a pair, or any two 10-point cards, then he may double his bet and separate his cards into two individual hands. The dealer will automatically give each card a second card. Then, the player may hit, stand, or double normally. However, when splitting aces, each ace gets only one card.
  • After the player has had their turn, the dealer will turn over his hole card. If the dealer has 16 or less, then he will draw another card.
  • If the dealer goes over 21 points, then the player wins.
  • If the dealer does not bust, then the higher point total between the player and dealer will win.
  • Winning wagers pay even money, except a blackjack which pays 3 to 2.

Δ Top

Game Play

The splash screen - like all other graphics in the program - was borrowed from Google. It allows the user to set some basic options before launching the game. These options include whether or not the player can split a pair, whether they can double on split cards and whether a blackjack pays 3 to 2.

If I find time (and motivation) I might add some additional options such as allowing players to only double on a 9, 10 or 11 (known as the Reno rule) or 10 and 11 only (as played in European casinos). Other options might include being able to hit on split aces and allowing the dealer to hit on a soft seventeen. One option - being able to split cards a second time - would require significant rework of the application and a rethink on how these additional hands will be displayed on such a small screen.

Δ Top

Users are able to double or split a pair. When doubling, the dealt card is placed at right angles to the original cards. As the SmartGPU2 library does not support the rotating of images, this 'feature' required a full set of images to be included already rotated. When splitting, the hand currently being played is displayed full size with the other hand rendered in half size - this was done using the SmartGPU2 scaling facility when rendering JPGs.

Insurance is offered when the dealer's first card is an Ace. The player may bet up to one half his original bet as insurance against the dealer having a blackjack. If the dealer has a blackjack he will turn his cards up immediately and take the player's original bet then pay out his insurance. If the player happens to have a blackjack as well the original bet results in a push and the insurance is paid out.

If the dealer's hand does not result in a blackjack, the second card is not turned over and insurances bets are collected by the dealer. Cards that the dealer has seen are shown with a red background to indicate this, as shown in the picture below.

Play concludes when the player either breaks the bank - earns more than $10,000 - or loses all of his money. While playing the game, the number of hands played is recorded along with the number hands won, lost or pushed.

Δ Top


Code Fragments

The following code highlights areas of interest in the application. It is not intended to be a tutorial or a complete solution. I have provided the complete code for download and you may use or modify it as required.

Dealing the Cards

Four arrays are used to record the cards dealt from the deck into the dealers hand or either of the two players hands. These are declared in lines 001 - 004.

The cards array declared in line 001 has 52 elements corresponding to the 52 cards in a standard deck. The 0th element corresponds to the Ace of Spades, the 1st element to the 2 of Spades and so forth through to the 12th element which corresponds to the King of Spades. This pattern is repeated for the next 13 elements for the Ace of Clubs through to the King of Clubs and then again for Diamonds and then Hearts. When a card is randomly dealt from the deck, the corresponding element is set to 1 to indicate that it should not be dealt again in the same hand.

The dealer, player_FirstHand and player_SecondHand array holds the cards dealt to each hand. Each element in the array represents a single card with a stored between 0 and 51 representing the cards Ace of Spades through to the King of Hearts (as per the indexing of the card array). Three variables named dealer_CardCount, player_FirstHand_CardCount and player_SecondHand _CardCount are used to keep track of the number of cards dealt for each hand.

001
002
003
004
005
006
007
008
byte cards[52]={0};
byte dealer[12]={0};
byte player_FirstHand[12]={0};
byte player_SecondHand[12]={0};

byte dealer_CardCount = 0;
byte player_FirstHand_CardCount = 0;
byte player_SecondHand_CardCount = 0;

The getCard() function randomly selects a card represented by a number between 0 and 51 (line 007) and checks to see whether the card has been dealt before in this hand by checking the element in the cards array. If element is zero (indicating that the card has not already been dealt, line 009), the appropriate hand's array is updated with the new card, the card counter increased and the new cards value returned to the calling procedure. If the element is not zero (indicating that the card has already been dealt), a new card is selected and this repeated until a card that has not been previously dealt is found.

The second getCard() function overloads the first and accepts a third parameter that represents the card to be selected (line 044). This function is only used when debugging the program to force a particular situation, such as the dealer having a blackjack or the player getting a pair for splitting.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
byte getCard(byte player, byte hand) {

  byte card = 0;
  
  while (true) {
    
    card = random(52);

    if (cards[card] == 0) {
      
      cards[card] = 1;
      break;
      
    }
    
  }
    
  if (player == DEALER) {
   
    dealer[dealer_CardCount] = card;
    dealer_CardCount++;
    
  }
  
  if (player == PLAYER && hand == FIRST_HAND) {
   
    player_FirstHand[player_FirstHand_CardCount] = card;
    player_FirstHand_CardCount++;
    
  }
  
  if (player == PLAYER && hand == SECOND_HAND) {
   
    player_SecondHand[player_SecondHand_CardCount] = card;
    player_SecondHand_CardCount++;
    
  }
  
  return card;
  
}


byte getCard(byte player, byte hand, byte cardNumber) {
  
  if (player == DEALER) {
   
    dealer[dealer_CardCount] = cardNumber;
    dealer_CardCount++;
    
  }
  
  if (player == PLAYER && hand == FIRST_HAND) {
   
    player_FirstHand[player_FirstHand_CardCount] = cardNumber;
    player_FirstHand_CardCount++;
    
  }
  
  if (player == PLAYER && hand == SECOND_HAND) {
   
    player_SecondHand[player_SecondHand_CardCount] = cardNumber;
    player_SecondHand_CardCount++;
    
  }
  
  return cardNumber;
  
}

The initialDeal() function shows how the first two cards are dealt to both the player and dealer. Lines 008 - 016 shows the normal operation of the game with randomly generated cards whereas lines 021 - 029 which are commented out can be reinstated to force a particular event - in this case the dealer being dealt a blackjack - for testing and debugging.

The drawCard() function, described in the section Rendering a Single Card below, is used to render a single card at the position supplied in the first two parameters. The third parameter is used to nominate the card to display and - again - is a value from 0 to 51. Note how the dealer's first card (lines 008 - 009) and the player's first card (line010 - 011) are rendered with a random number returned from the getCard() function. The dealer's second card is not displayed to the player initially - line 012 places a call to getCard() generates a random card and places it in the dealers hand but this is not passed to the drawCard() call in lines 013 - 014. Instead a constant CARD_BLUE_BACKGROUND is passed instead which indicates to render the card face down with a blue background. The player's second card is rendered normally at lines 015 - 016.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
void initialDeal() {

  numberOfGamesPlayed++;  


/* Normal hand */

  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND, CARD_LARGE_TOP_DEALER, 
           getCard(DEALER, FIRST_HAND,0), false, true);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND, CARD_LARGE_TOP_PLAYER, 
           getCard(PLAYER, FIRST_HAND), false, true);
  getCard(DEALER, FIRST_HAND);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + CARD_LARGE_SPACING, 
           CARD_LARGE_TOP_DEALER, CARD_BLUE_BACKGROUND, false, true);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + CARD_LARGE_SPACING, 
           CARD_LARGE_TOP_PLAYER, getCard(PLAYER, FIRST_HAND), false, true);


/* Dealer blackjack 

  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND, CARD_LARGE_TOP_DEALER, 
           getCard(DEALER, FIRST_HAND, 0), false, true);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND, CARD_LARGE_TOP_PLAYER, 
           getCard(PLAYER, FIRST_HAND), false, true);
  getCard(DEALER, FIRST_HAND, 10);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + CARD_LARGE_SPACING, 
           CARD_LARGE_TOP_DEALER, CARD_BLUE_BACKGROUND, false, true);
  drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + CARD_LARGE_SPACING, 
           CARD_LARGE_TOP_PLAYER, getCard(PLAYER, FIRST_HAND), false, true);

*/

  ..
  
}
Δ Top

Rendering a Single Card

The drawCard() function is used to render a single card at the position supplied in the first two parameters. The third parameter is used to nominate the card to display and - again - is a value from 0 to 51. The final parameters indicate whether the card should be rotated and rendered full size or small. The various combinations are shown below: full size normal orientation, full size rotated, small size normal orientation and small size rotated.

        

The routine is quite simple really and consists of four switch statements that correspond to the four rendering modes shown above. Each rendering mode is supported by a rendering function - drawCardLarge(), drawCardLarge_Rotated(), drawCardSmall() and drawCardSmall_Rotated() - starting at line number 102.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
void drawCard(int xPos, int yPos, byte card, bool rotated, bool large) {
  
  if (large) {
    
    if (!rotated) {
      
      switch (card) {
        
        case 0:    drawCardLarge(xPos, yPos, "AS");   break;
        case 1:    drawCardLarge(xPos, yPos, "2S");   break;
        case 2:    drawCardLarge(xPos, yPos, "3S");   break;
        case 3:    drawCardLarge(xPos, yPos, "4S");   break;
        case 4:    drawCardLarge(xPos, yPos, "5S");   break;
        case 5:    drawCardLarge(xPos, yPos, "6S");   break;
        case 6:    drawCardLarge(xPos, yPos, "7S");   break;
        case 7:    drawCardLarge(xPos, yPos, "8S");   break;
        case 8:    drawCardLarge(xPos, yPos, "9S");   break;
        case 9:    drawCardLarge(xPos, yPos, "10S");  break;
        case 10:   drawCardLarge(xPos, yPos, "JS");   break;
        case 11:   drawCardLarge(xPos, yPos, "QS");   break;
        case 12:   drawCardLarge(xPos, yPos, "KS");   break;
  
        case 13:   drawCardLarge(xPos, yPos, "AC");   break;
        ...
        
        case 26:   drawCardLarge(xPos, yPos, "AD");   break;
        ...
        
        case 39:   drawCardLarge(xPos, yPos, "AH");   break;
        ...
        
      }
  
    }
    else {
          
      switch (card) {
        
        case 0:    drawCardLarge_Rotated(xPos, yPos, "ASR");   break;
        ...
  
        case 13:   drawCardLarge_Rotated(xPos, yPos, "ACR");   break;
        ...
        
        case 26:   drawCardLarge_Rotated(xPos, yPos, "ADR");   break;
        ...
        
        case 39:   drawCardLarge_Rotated(xPos, yPos, "AHR");   break;
        ...
  
      }
      
    }
    
  }
  
  if (!large) {
       
    if (!rotated) {
 
      switch (card) {
      
        case 0:    drawCardSmall(xPos, yPos, "AS");   break;
        ...

        case 13:   drawCardSmall(xPos, yPos, "AC");   break;
        ...
      
        case 26:   drawCardSmall(xPos, yPos, "AD");   break;
        ...
      
        case 39:   drawCardSmall(xPos, yPos, "AH");   break;
        ...
  
      }
      
    }
    else {
        
      switch (card) {
      
        case 0:    drawCardSmall_Rotated(xPos, yPos, "ASR");   break;
        ...

        case 13:   drawCardSmall_Rotated(xPos, yPos, "ACR");   break;
        ...

        case 26:   drawCardSmall_Rotated(xPos, yPos, "ADR");   break;
        ...

        case 39:   drawCardSmall_Rotated(xPos, yPos, "AHR");   break;
        ...
   
      }
      
    }

  }
  
}

void drawCardLarge(int xPos, int yPos, char card[2]) {
      
  lcd.drawRoundRect(xPos, yPos, xPos + CARD_LARGE_WIDTH, yPos +
                    CARD_LARGE_HEIGHT, radius5, WHITE, solidFill);
  lcd.drawRoundRect(xPos, yPos, xPos + CARD_LARGE_WIDTH, yPos + 
                    CARD_LARGE_HEIGHT, radius5, BLACK, hollowFill);
  lcd.imageJPGSD(xPos + CARD_LARGE_INSET, yPos + CARD_LARGE_INSET, 
                    SCALE1_1, card);

}

void drawCardLarge_Rotated(int xPos, int yPos, char card[2]) {
      
  lcd.drawRoundRect(xPos, yPos + CARD_LARGE_ROTATED_Y_OFFSET, xPos + 
                    CARD_LARGE_HEIGHT, yPos + CARD_LARGE_ROTATED_Y_OFFSET 
                    + CARD_LARGE_WIDTH, radius5, WHITE, solidFill);
  lcd.drawRoundRect(xPos, yPos + CARD_LARGE_ROTATED_Y_OFFSET, xPos + 
                    CARD_LARGE_HEIGHT, yPos + CARD_LARGE_ROTATED_Y_OFFSET 
                    + CARD_LARGE_WIDTH, radius5, BLACK, hollowFill);
  lcd.imageJPGSD(xPos + CARD_LARGE_INSET, yPos + 
                    CARD_LARGE_ROTATED_Y_OFFSET + CARD_LARGE_INSET, 
                    SCALE1_1, card);

}

void drawCardSmall(int xPos, int yPos, char card[2]) {
      
  lcd.drawRoundRect(xPos, yPos, xPos + CARD_SMALL_WIDTH, yPos + 
                    CARD_SMALL_HEIGHT, radius5, WHITE, solidFill);
  lcd.drawRoundRect(xPos, yPos, xPos + CARD_SMALL_WIDTH, yPos + 
                    CARD_SMALL_HEIGHT, radius5, BLACK, hollowFill);
  lcd.imageJPGSD(xPos + CARD_SMALL_INSET, yPos + CARD_SMALL_INSET, 
                    SCALE1_2, card);
  
}

void drawCardSmall_Rotated(int xPos, int yPos, char card[2]) {
      
  lcd.drawRoundRect(xPos, yPos + CARD_SMALL_ROTATED_Y_OFFSET, xPos + 
                    CARD_SMALL_HEIGHT, yPos + CARD_SMALL_ROTATED_Y_OFFSET 
                    + CARD_SMALL_WIDTH, radius5, WHITE, solidFill);
  lcd.drawRoundRect(xPos, yPos + CARD_SMALL_ROTATED_Y_OFFSET, xPos + 
                    CARD_SMALL_HEIGHT, yPos + CARD_SMALL_ROTATED_Y_OFFSET 
                    + CARD_SMALL_WIDTH, radius5, BLACK, hollowFill);
  lcd.imageJPGSD(xPos + CARD_SMALL_INSET, yPos + 
                    CARD_SMALL_ROTATED_Y_OFFSET + CARD_SMALL_INSET, 
                    SCALE1_2, card);

}
Δ Top

Rendering a Full Hand

By calling the drawCard() function for each element of the dealer, player_FirstHand or player_SecondHand arrays we can draw the complete hand. Below are two functions - drawFirstHand_Large() and drawFirstHand_Small - that draw the player's first hand in large and small formats respectively. Not shown are the two functions that perform the same functions for the player's second hand if he has split a pair.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
void drawFirstHand_Large() {

  for (int x=0; x< player_FirstHand_CardCount; x++) {
   
    if (x == player_FirstHand_CardCount - 1 && player_FirstHand_Double) {
  
      drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + 
      		   (x * CARD_LARGE_SPACING), CARD_LARGE_TOP_PLAYER, 
      		   player_FirstHand[x], true, true);   
  
    }
    else {
  
      drawCard(CARD_LARGE_LEFT_PLAYER_FIRSTHAND + 
               (x * CARD_LARGE_SPACING), CARD_LARGE_TOP_PLAYER, 
               player_FirstHand[x], false, true);   
  
    }
    
  }
    
}

void drawFirstHand_Small() {

  for (int x=0; x< player_FirstHand_CardCount; x++) {
       
    if (x == player_FirstHand_CardCount - 1 && player_FirstHand_Double) {
      
      drawCard(CARD_SMALL_LEFT_PLAYER_FIRSTHAND + 
               (x * CARD_SMALL_SPACING), CARD_SMALL_TOP_PLAYER, 
               player_FirstHand[x], true, false);   
      
    }
    else {
      
      drawCard(CARD_SMALL_LEFT_PLAYER_FIRSTHAND + 
               (x * CARD_SMALL_SPACING), CARD_SMALL_TOP_PLAYER, 
               player_FirstHand[x], false, false);   
      
    }
        
  }
  
}
Δ Top

Wiring Diagram

The diagram below show the wiring diagram when using a standard Arduino Uno (or compatible) and the SmartGPU2. Click on the image to see a detailed drawing.


Δ Top

Downloads

Feel free to download, modify and adapt this code to your needs. Please credit me if you use some or all of this code in your own project. If you alter the code to fix bugs or add functionality, please submit them back to me and I will add them to the download.

   Arduino Source Code
Δ Top

Comments?

 Anything to say?
Name :
Email :
Comments :
Confirmation: