Archive for category Blackjack
Blackjack Lesson 8: Let the Player Play
Source Code
blackjack.l8.cpp
In this lesson we’ll finally let the user decide whether to double or not and whether to hit or stand. Rather thango into more text based tutorials covering adding more player types and whatnot, we’re going to stop here and begin the tutorials on rasterization. Once we’ve learned enough about rendering and animating sprites we’ll come back to blackjack and make a graphical version. This version of blackjack is a functional design. It will halt the program when waiting for input. The graphical version will require a state based approach. The program will still need to be doing things when waiting for user input but will also have to know when to wait for user input.
The first change we’re going to make to the current blackjack code is to ask for user input when deciding whether or not to double.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | bool Double() { char input = ' '; cout<<"Would you like to double? y or n"<<endl<<flush; cin>>input; if(input=='n' || input == 'N') return false; int k; hasDoubled = 0; //flag that we've doubled (also stores number of aces) for(k=0;k<4;k++) hasDoubled += playerCards[k*13+12]; //add up the number of aces the player has return true; } |
If the user enters ‘n’ or ‘N’ then the function returns false indicating that the user doesn’t want to double. If the user enters something else then we do what we did in the previous lesson and process the double. Next up we let the user decide whether to hit or stand.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | bool Hit() { int curHand = HandValue(); if(curHand<12) { if(curHand>=9) { if(Double()) { cout<<"Player has DOUBLED!"<<endl<<flush; bet *= 2; //double our bet return true; } } else return true; } char input = ' '; cout<<"Would you like to hit? y or n"<<endl<<flush; cin>>input; if(input=='n' || input == 'N') return false; return true; } |
If the user enters ‘n’ or ‘N’ then we return false indicating that the player wants to stand. If the user enters something else then we return true. As you can see, this isn’t difficult to do at all. The cin keyword accepts any variable type and automatically converts what the user enters to that data type. Finally, we ask the user if they want to play again.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /***************** This is where the game will loop when playing multiple hands *****************/ bool done = false; while (!done) { ... char input = ' '; cout<<"Would you like to play another hand? y or n"<<endl<<flush; cin>>input; if(input=='n' || input == 'N') done = true; } |
Some people like to use things like while(1) or for(;;) which functially produce an infinite loop. Technically we could do:
1 2 3 4 5 6 7 8 9 10 11 | while (1) { ... char input = ' '; cout<<"Would you like to play another hand? y or n"<<endl<<flush; cin>>input; if(input=='n' || input == 'N') break; } |
But this is grammarically incorrect. while(1) claims that this loop will continue forever. But this is not actually true. We intend to exit this loop eventually. While in the first example we only need to look for the varible “done” to ensure that our program has an exit condition that can be reached and know this is the only stopping condition, the second gives no such indicator. It’s sloppy coding and requires you look for other indicators to figure out what will cause the loop to exit. And who knows how many ways there are to get out of the loop. At no time should you ever have a truely infinite loop; a loop with no stopping condition. If you do, your code is broken. Since you must have a stoping condition, that condition should be specified in the while() or for() statement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //bad bad bad int j = 0; for(;;) { //do stuff ... j++; if(j>100) break; } //the right way for(j=0;j<101;j++) { } |
All stopping conditions belong in the loop statement. It makes the code significantly more readable and grammarically correct.
And that concludes this lesson. You now know how to program a decent game of blackjack. The next set of tutorials will cover raster graphics and be posted from a link on the front page. The blackjack tutorials will not continue here until we’ve learned enough about raster graphics.
Blackjack Lesson 7: Doubling
Source Code
blackjack.l7.cpp
In the official game of blackjack, if one person doubles, everybody can double. In this lesson, that rule is ignored. “Human” players can (and will) double if they are dealt the proper cards. Implementing doubling is actually quite easy and introduces the concept of multi-use variables. Multi-use variables are variables which serve more than one purpose simultaniously. Not like generic variables which have multiple uses but can serve only one unique purpose at a time.
1 2 3 4 5 6 7 | class cPlayer { public: int playerCards[52]; //the cards the player has int bet,money; int hasDoubled; ... |
We’ve added “hasDoubled” to our base player class. “has” is generally the start to a true/false question such as “has the garbage been taken out?” And this is a true/false variable. But it is also a counter variable. And that is why it’s an int rather than a bool. When hasDoubled is less than 0, the player has not doubled. When hasDoubled is greater than or equal to 0, then the player has doubled and the variable stores the number of aces the player had at the time of the double. Since a player can only elect to double their first turn after the initial two cards are dealt, hasDouble can only have a value from -1 to 2 inclusive. So using a 32 bit variable that can go up to over 2 billion is a whee bit excessive. But, we don’t care. Make it work, then make it work better.
Previously, we were already calling the Double() method to check if the player was doubling. All we need to do then is provide the logic for doubling.
1 2 3 4 5 6 7 8 | bool Double() //cHuman will always double { int k; hasDoubled = 0; //flag that we've doubled (also stores number of aces) for(k=0;k<4;k++) hasDoubled += playerCards[k*13+12]; //add up the number of aces the player has return true; } |
Easy. All we do is count the number of aces and store the result in hasDoubled. Next, we make a slight modification to the Hit() method.
1 2 3 4 5 6 7 8 9 10 11 | bool Hit() { ... if(Double()) { cout<<"Player has DOUBLED!"<<endl<<flush; bet *= 2; //double our bet return true; } ... } |
We display to the user that the player has doubled and double the bet. We could double the bet in the Double() method but it doesn’t matter. We then return true because doubling requires taking a card. A slight change then is made to our code for handling playing the hand in our main function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //actual game play for(j=1;j<numPlayers;j++) { //check to see if player has blackjack if(players[j]->HandValue()==21) { ... } else { ... //continue dealing cards until the player says stop or doubles //on a double, deal only one additional card (rule 11.1) //*IMPORTANT* you must check the double condition first //exercise for the reader: why? while(players[j]->hasDoubled < 0 && players[j]->Hit()) { ... } } |
And that’s all there is to it for allowing a player to double and recieve 1 and only 1 additional card. Why the order of the logic conditions matters is left as an exercise to the reader. Knowing why is important as it comes up quite a bit when coding.
And finally, the last thing we need to do is change how the value of the hand is calculated. If you remember, any aces in the hand when the player doubles must be counted only as 1 and not 11. Only an ace drawn by the player after declaring a double can be counted as an 11.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | int HandValue() { int j; int value = 0; int numAces = 0; for(j=0;j<52;j++) { if((j+1)%13!=0) //aces have special handling value+=playerCards[j]*cardValues[j%13]; else numAces+=playerCards[j]; } if(hasDoubled > 0) //if the player has doubled, and had at least one ace { value += (hasDoubled); //all aces for double have value of 1 (11.3) numAces -= hasDoubled; //remove them from the count } if(numAces >= 1) //if we have at least one ace if(value + 11 + numAces - 1 <=21) //and can make it 11 without busting { value+=11; //use the ace as an 11 numAces--; } value+=numAces; //use the remaining aces as 1 each return value; |
Previously we were using a loop to count aces. This is pointless as only one ace can have a value of 11. If the player has two aces and values both at 11 then the player will have a hand valued at 22 which is over the 21 limit. So there’s no point in doing repeated checks to see if a player should make an ace a 1 or an 11. If the player has doubled then all the aces from the first two cards are valued at 1. We add those in and subtract that number of aces from the total ace count. Next we see if the player has another ace. If they do, we see if the value of that ace at 11 plus the number of remaining aces puts us over 21. If it doesn’t we add 11 and subtract one ace from our total. Finally, we add in the remaining aces valued at 1 each.
And that’s it. The only other changes to the code are cosmetic in terms of what messages are displayed where. In Lesson 8 we’ll finally let the user play.
Blackjack Lesson 6: Scoring
Source Code
blackjack.l5.cpp
We’re using the same code as the last lesson since this is already implemented in that code. It was just a bit much to cover in a single lesson. The final flow chart covers deciding who wins and who loses. Implementing it is actually quite simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | //final tally to see who wins and loses j=0; //store this to simplify int playerValue, dealerValue = players[j]->HandValue(); cout<<"Dealer has "<<dealerValue<<endl<<flush; players[j]->ShowHand(); cout<<endl<<flush; for(j=1;j<numPlayers;j++) { playerValue = players[j]->HandValue(); cout<<"Player "<<j<<" has "<<playerValue<<endl<<flush; players[j]->ShowHand(); cout<<endl<<flush; if(playerValue<=21) //see if player breaks { //if the dealer broke or the player //scored higher (ties go to dealer) if(dealerValue > 21 || playerValue > dealerValue) { players[j]->money+=players[j]->bet; cout<<"Player WINS!"<<endl<<flush; } else { players[j]->money-=players[j]->bet; cout<<"Player LOSES!"<<endl<<flush; } } else { players[j]->money-=players[j]->bet; cout<<"Player LOSES!"<<endl<<flush; } } |
The first thing we do is reveal the Dealer’s hand to the players. Next we loop through all the players and show their hands and the value of their hands. If the player has more than 21 then they lose. If the player has less than 22 then we consider the dealer’s hand. If the dealer is over 21, the player wins. If the player has a higher hand than the dealer then the player wins. If the player ties with the dealer or has less than the dealer then the player loses.
And that’s all there is to it. You now have the complete code to allow two computer players to play a hand of blackjack. In the next lesson we’ll start letting the user play and implement the ability to double.
Blackjack Lesson 5: Defining the Players and Playing the Hand
Source Code
blackjack.l5.cpp
In this lesson some important things have changed. Since we’re going to have multiple types of players we need to use inheritence and since we want to use a dynamic number of players we need to use pointers. Let’s start from the begining.
1 2 3 4 5 6 7 8 9 10 11 | void main() { ... int numPlayers = 2; cPlayer ** players = new cPlayer*[numPlayers]; //init array of pointers ... players[0] = new cDealer(); //our players (player 0 is always the dealer) for(j=1;j<numPlayers;j++) players[j] = new cHuman(); |
In order to use an array of pointers we need to use ** to define our players variable. The first * covers the dynamic array and the second * is the pointer to the inherited clss we will be using for that array entry. We then first initialize the array of pointers using new. Next we initialize each of those pointers to the class type. If the variable type is “cPlayer” then we can only initialize the pointer to the “cPlayer” class or any class derived from “cPlayer.” You cannot initialize the pointers to just anything.
Now we have an array of players. Player 0 is the dealer and the rest are defined to be human players. But, we need to define what those classes are. To do that, you have to first start with the base class, cPlayer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class cPlayer { public: //virtual functions can be overwritten by derived classes //we'll set the default behavior for hitting to the dealer rules virtual bool Hit() { if(HandValue()<17) //dealer stays on 17 and over, otherwise hits return true; return false; } //dealer never doubles virtual bool Double() { return false; } ... |
The “virtual” keyword is used to allow derived classes to override the default behavior. For our purposes we’re going to set the default behavior to Dealer rules for hitting and staying. The dealer cannot double so we just return false. Next we define the cDealer class. The default logic covers flowchart 3.
1 2 3 4 5 6 7 8 | class cDealer: public cPlayer { public: cDealer() //initialize the class with the base constructor { cPlayer(); } }; |
The colon after cDealer tells the compiler that this is a derived class. “public cPlayer” tells the compiler we’re deriving from cPlayer and that the parent class is available. If we didn’t set it to public we wouldn’t be able to initialize a pointer to this derived class. Inside this class we then add any functions and variables we want and redefine any virtual functions from the base class if we want. Since we want to use the base class to initialize the player, we call that constructor from the derived class’s constructor. We can then initialize any additional variables that only exist in the derived class.
Next up is the cHuman class. This will implement most of flowchart 2. Doubling is not yet an option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class cHuman: public cPlayer { public: cHuman() //initialize the class with the base constructor { cPlayer(); } bool Hit() { int curHand = HandValue(); if(curHand<12) { if(curHand>=9) { if(Double()) { bet*=2; return true; } } else return true; } if(curHand<21) { if(curHand<17) return true; } return false; } }; |
If we wanted to allow this class to be derived from we would want to use the virtual keyword on the Hit method. But, since we assume we’re not going to derive any further, we don’t make the function virtual. We could also add more virtual functions to this class. There really isn’t any limit. You just have to keep it logical and simple. The logic contained in the Hit method covers flow chart 2. If the player is holding less than 21 then we have a couple options. For now we use the dealer’s rules to decide if the player hits or stays. In future lessons we’ll ask the user to decide. And in other classes, we’ll define other rules entirely. Using inheritence allows us to have any number of playing styles all play at once. Maybe we want some computer controlled players to hit only when they’re holding 15 or lower. Notice also that we didn’t redefine the Double method. The net result is that the player will never double. We’ll implement the logic for doubling later.
Now that we have the classes for our two types of players defined and initialized, it’s time to make one more critical change. Previously we had used method calls like so: players[j].ShowHand() with periods. Now that we’re using an array of pointers instead of an array of objects we have to change to use players[j]->ShowHand() instead. Simple yet important. Your compiler should spit out errors if you don’t use the proper method. It’s one of those things you need to memorize; when to use a period and when to use the arrow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | ... //actual game play for(j=1;j<numPlayers;j++) { ... //check to see if player has blackjack if(players[j]->HandValue()==21) { ... } else while(players[j]->Hit()) { valid=false; while(!valid) { card = rand()%52; if(deckOfCards[card]>0) { deckOfCards[card]--; players[j]->TakeCard(card); valid=true; } } } } //dealer's turn while(players[0]->Hit()) { valid=false; while(!valid) { card = rand()%52; if(deckOfCards[card]>0) { deckOfCards[card]--; players[0]->TakeCard(card); valid=true; } } } ... |
In the previous lesson we stopped at handling the case where the player had 21. Now we can continue and actually play through a hand. All we have to do is call the Hit() method of the player until they return false. Every time true is returned, a new card is dealt to the player and we repeat the process. Since the Hit() method is the only thing that can exit the while statement there must exist a stopping condition in the Hit() method or the program will go into an infinite loop. You could add an additional failsafe such as counting the number of loops and exiting after X cards have been drawn. But that’s lazy coding. You simply shouldn’t have a Hit() method that can’t return false. Since the dealer always goes last we start with player 1 and then copy the code and handler player 0 outside the loop.
And finally, it’s clean up time. Since we’re now working with an array of pointers, there is a change that needs to be made to how we free all the memory used.
1 2 3 4 | //end of the game for(j=0;j<numPlayers;j++) delete players[j]; delete [] players; |
First we must loop through all the pointers and free the memory associated with them. And then we do like we did previously and free the memory used by the array itself.
And that concludes this lesson. In the next lesson we’ll cover the logic of deciding who wins and who loses.
Blackjack Lesson 4: Finishing up the Initial Deal
Source Code
blackjack.l4.cpp
In this lesson we finish up the initial deal and flowchart number 1. The first thing we’ve done is make the number of players dynamic.
1 2 3 4 5 | int numPlayers = 2; cPlayer * players = new cPlayer[numPlayers]; int bet; //temp bet variable int card; //temp card variable |
We’ve also added a temporary card variable. You’ll see why later. For our code we must always have at least 1 player since player 0 is always the dealer. You also can’t initialize a dynamic array of size 0. For now we’re going to stick with two players; the dealer and the human. Even though we’re “hard coding” the number of players, it is very important to code as though there could be a million players. You’re not writing code to handle a million players. You’re writing code to handle one player and then repeating that code possibly a million times. Even if you’re not using a dynamic array like we’re doing it’s very important not to limit the number of objects your code can handle. You always want to code in such a way that it can be easily expanded later on. You may only want to allow 5 players now but if someone decides they want to use your code to handle a million players, they shouldn’t have to rewrite a single line of game logic. They should simply be able to change numPlayers from 5 to 1000000.
The next step in our flow chart we need to handle is taking bets. This must be done before any cards are dealt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | srand(GetTickCount()); //randomize the timer based on the tickcount //fill up our deck with all the cards for(j=0;j<52;j++) deckOfCards[j]=numDecks; //initialize the players players[0].ClearHand(); players[1].ClearHand(); //take bets for(j=1;j<numPlayers;j++) { bet = -1; while(bet<0) { cout<<"How much would you like to bet, player "<<j<<"?"<<endl<<flush; cin>>bet; } players[j].bet = bet; cout<<"Player "<<j<<" has bet $"<<bet<<endl<<flush; } |
srand() is a C++ function used to seed the random function. The random function isn’t actually random. If you did an srand(0) and then printed out 1000 random numbers, exited the program and did it again, you’d get the exact same list of 1000 numbers. The only thing random about the rand() function is that the numbers aren’t obviously sequential. But, the fact is a mathematical function is used to generate the numbers and so they come out in the same order every time. By seeding the random function with GetTickCount() we start with a list of “random” numbers that is different every time we run the program.
Note that when taking bets we start j at 1. This is because 0 is the dealer and the dealer is handled by the computer. The dealer also doesn’t place a bet. They simply match the bets of each individual player. If player X bets $5 and beats the dealer then the dealer pays out $5 to that player. All we care about the bet is that it’s more than $0. We’re also working with integers so fractional bets like $1.25 will be rounded down to $1. If you want to set a minimum bet, this is the place to do it. For this tutorial, we’re setting the minimum bet to $1 and the player must bet in dollar increments.
The next step is to deal all the players 2 cards each, including the dealer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //initial deal //deal each player, including the dealer, two cards for(k=0;k<2;k++) for(j=0;j<numPlayers;j++) { valid = false; while(!valid) { card = rand()%52; if(deckOfCards[card]>0) { deckOfCards[card]--; valid=true; } } if(j+k==0) { cout<<"Dealer is showing a "; CardToString(card); cout<<endl<<flush; } players[j].TakeCard(card); } |
I think the dealer actually deals a card to themselves after dealing each card to all the other players first. But, we don’t care. The design document is a guideline. It can be fudged here and there. The order in which we deal the cards does not effect the rules of the game. We’re reusing the “valid” variable to verify that the random card we drew is still in the deck. This is why we have a temporary card variable. We set that variable with a random number and then verify it before moving on. One of the important rules of blackjack is that the dealer shows one card. It may be the first card that the dealer deals to themselves or the second. Again, it doesn’t matter. Rather than checking to see if j is 0 and k is 0, we simply add the two together and since the only way for j+k to equal 0 is if both is zero, we get the same logic but with only one check rather than two. If it is the dealer’s card and it is the first card dealt then we display the card so that everyone can see it. The other players will see their cards when it’s their turn. This is a purely cosmetic choice as it keeps the screen from filling up with unnecessary text.
And finally, after the deal we start the actual game logic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //actual game play for(j=1;j<numPlayers;j++) { cout<<"Player "<<j<<" has the following hand:"<<endl<<flush; players[j].ShowHand(); //check to see if player has blackjack if(players[j].HandValue()==21) { cout<<"BLACKJACK for Player "<<j<<"!"<<endl<<flush; if(players[0].Has10() || players[0].HasAce()) { if(!players[0].HasAce()) {//dealer has a 10, pay out 1:1 players[j].money+=players[j].bet; cout<<"Dealer has a 10 but no ace, player wins $"<<players[j].bet<<endl<<flush; } else { //dealer has an ace, player must wait for dealer to play for pay out cout<<"Dealer has an ace, player must wait."<<endl<<flush; } } else {//dealer doesn't have an ace or 10 so pay out 3:2 (round up) players[j].money+=(players[j].bet*3.0/2.0+0.5); cout<<"Dealer has no ace or 10, player wins $"<<(players[j].bet*3.0/2.0+0.5)<<endl<<flush; } } else { //play the hand } } |
Note that again we start j at 1 rather than 0. This is because player 0 is the dealer and is a special case for game logic. The first thing we do is see if the player has 21. The only way to have 21 after the initial deal is to have blackjack. However, the amount a player wins for having blackjack varies based on the hand that the dealer has. We added a couple functions to the player class to simplify the logic here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | bool Has10() { int j,k; for(k=0;k<4;k++) for(j=8;j<12;j++) if(playerCards[k*13+j]>0) return true; return false; } bool HasAce() { int k; for(k=0;k<4;k++) if(playerCards[k*13+12]>0) return true; return false; } |
If the dealer has an ace or a 10 then we check to see which the dealer has. If the dealer has an ace then the player doesn’t win anything immediatly since the dealer may have blackjack as well. We don’t find that out until everybody else has played. If the dealer has a 10 then the dealer pays the player whatever the player bet. If the dealer doesn’t have an ace or a 10 then the dealer pays the player 3:2 on what the player bet. Casinos have 50 cent pieces for the case that the player bet an odd value like $5 but for our code we’re going to round up. This is another case of fudging the rules.
And that concludes flowchart number 1. Since we fudged a few things it gives you the reader a chance to correct those things so that they more accuratly reflect the official Casino rules. It would be rather dull to just give you all the answers so you could simply cut and paste the code. Also in the code but not covered here is the fix for the code that calculates the value of the player’s hand. In our next lesson we’ll cover the code to “play the hand” which is flowchart number 2.
Blackjack Lesson 3: Getting Started
Source Code
blackjack.l3.cpp
The first thing is figuring out what headers we’re going to need to include. In order to do that we need to know what keywords we want to use. The first thing we’re going to need is random numbers. So we look up the C++ keyword to generate random numbers and it turns out to be “rand.” And we need to include windows.h to be able to use it. Since we’re not working with graphics we need to be able to put text on the screen. We’ll use the old school “cout” and “cin” since those give us a very easy way to get user input and display information to the user.
1 2 3 | #include <windows.h> #include <iostream> //just include iostream.h for VS 6 and earlier using namespace std; //necessary for visual studio .net |
Next up we need to define our cards. A deck of cards has 52 cards with 4 suits and 13 cards in each suit. Card go from 2 to Ace. What we want to do is store the name of the cards, the suit of the cards and the numerical value of the cards. Note that the Ace is worth 1 or 11 depending on certain game rules. We’ll worry about that later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | char cardNames[13][32] = { "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "jack", "queen", "king", "ace" }; char cardSuits[4][32] = { "clubs", "spades", "hearts", "diamonds" }; char cardValues[13] = { 2,3,4,5,6,7,8,9,10,10,10,10,11 }; |
We could have defined a card class and had a suit, name and value variable but that would be excessive. This is a very simple and compact way to store everything we need to know about the cards.
One very important thing we need to do is be able to tell the user what a card is in plain english. “You have card 43″ is not very useful.
1 2 3 4 | void CardToString(int card) { cout<<cardNames[card%13]<<" of "<<cardSuits[card/13]<<endl<<flush; } |
endl is a line break and flush forces the text to display to the user. Note that to get the card name we mod the card number passed in (a value from 0 to 51) by 13 and to get the suit we divide by 13. In memory the deck is essentially stored with all the clubs in order from 2 to Ace, all the spades, etc. It’s not necessary to actually shuffle the deck. Dividing by 13 will get a number from 0 to 3 and moding by 13 will get a value from 0 to 12. This is what we want. There is no error handling for this function. If you pass in a number larger than 51, bad things may happen.
Next up is the player class. You will need to instantiate an instance of this class for the dealer and every player. There’s not much a player needs to know. They simply need to know what cards they have and how much money they have. Methods are then provided to do basic functions like draw a card or set a bet amount and add up their hand.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | class cPlayer { public: int playerCards[52]; //the cards the player has int bet,money; cPlayer() { money = 100; //start with 100 dollars to bet with bet = 0; ClearHand(); } void ClearHand() { for(int j=0;j<52;j++) playerCards[j]=0; } void TakeCard(int c) { playerCards[c%52]++; cout<<"You have been given a "; CardToString(c); } void PlaceBet(int n) { bet = n; } int HandValue() { int j,k; int value = 0; for(j=0;j<52;j++) { if((j+1)%13!=0) //aces have special handling value+=playerCards[j]*cardValues[j%13]; } for(k=1;k<5;k++) for(j=0;j<playerCards[13*k-1];j++) if(value+11<21) value+=11; else value+=1; return value; } }; |
The player class has only a few variables that are needed. One holds the player’s hand. To do that we simply use an integer array of 52. This will tell us how many of each card the player has. bet is used to store the amount the player is betting on this hand and money is the amount of money the player has.
The cPlayer() constructor is automatically called and sets the amount of money the player has to 100 dollars. We also set the bet to 0 and call the ClearHand() method to set the card counts all to 0.
The TakeCard() method is used to place a card in the player’s hand. Note that we mod the card number by 52. This prevents the code from trying to go outside the bounds of the array. However, our code should be such that a number larger than 51 or less than 0 is ever passed into this method. You should never use error handling as an excuse to write sloppy code. It’s better to write code that doesn’t generate the error condition rather than write code to handle the error condition.
PlaceBet() simply sets the value of the bet. Note that we don’t bother checking to see if the player has enough money to cover the bet. We’ll play with that condition later. And finally we have CountHand() Which first runs through all the value cards and adds them up. The first loop ignores Aces. This is because an Ace is only worth 11 if all the other cards + 11 is less than or equal to 21. So we first start adding up all the other cards, then we add up the aces so that we arrive at the largest number that is less than or equal to 21. Note that if the hand is over 21 then aces are just one so that we bust with the lowest number over 21. Not that it matters.
There is a logic error with this code which will cause a bust under certain circumstances where a bust should not occur. See if you can figure out a solution before it’s corrected in a future tutorial. And that’s it for the player class. Next we start working on the flow charts.
The first flow chart deals with setting up the deck and dealing cards. Variables are commented in the code so I’m not going to do it here.
1 2 3 4 5 6 7 8 9 10 11 | while (!valid) { cout<<"How many decks would you like to play with? (3,4,6,8)"<<endl<<flush; cin>>numDecks; if( numDecks<9 && ((numDecks%3)==0 || (numDecks%4)==0)) valid=true; else cout<<"That is an invalid selection."<<endl<<flush; } cout<<"You have selected to use "<<numDecks<<" decks."<<endl<<flush; |
According to our design document (the official casino rules) we must use 3,4,6, or 8 decks in our game. In order to verify the users selection we could use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | if(numDecks == 3 || numDecks == 4 || numDecks == 6 || numDecks == 8) //or switch(numDecks) { case 3: case 4: case 6: case 8: valid = true; break; default: valid = false; break; } |
But what happens when you’re dealing with a large number of cases? Where possible it’s best to find a mathematical formula to check user input when a large number of cases is possible. Looking at the valid possibilities you see that decks must be used in multiples of 3 or 4 but the total number of decks must be less than 9. That’s what the check used in the code tests for. If we wanted to allow a deck of size 9 or 12 the only thing we’d have to change is the check to see if the number of decks is less than 9.
Now that we’ve selected a valid deck size, it’s time to populate the deck and while we’re at it, test our card arrays to make sure everything is working properly.
1 2 3 4 5 6 | //fill up our deck with all the cards for(j=0;j<52;j++) { deckOfCards[j]=numDecks; CardToString(j); //test our card arrays } |
If our card definition arrays are working properly we will see all the cards in the deck displayed properly in order. One of the easiest ways to debug code is simply to output values either to the screen or to a log file. You then just have to see if the value is correct and if so, why the output generated from that value is incorrect.
And finally in this tutorial we initialize our dealer and a player and test our ability to distribute cards.
1 2 3 4 5 6 7 8 9 10 11 12 | //initialize the players players[0].ClearHand(); players[1].ClearHand(); //do a test run for(j=0;j<4;j++) { k = rand()%52; players[1].TakeCard(k); } cout<<"The value of your hand is: "<<players[1].HandValue()<<endl<<flush; players[1].ClearHand(); |
This code will display the four cards dealt to the user and if everything is correct, the value of the hand should match the cards that were dealt. If not, you’ve got some debugging to do. You need to figure out if the code is displaying the wrong card names or if the values for the cards are wrong.
And that concludes this tutorial. You can now select the number of decks to use and deal cards to players. In the next tutorial we will finish the first flow chart of taking bets, dealing out the cards for the round and then checking for blackjack.
Blackjack Lesson 2: The Flowchart
A flowchart doesn’t need to be all that complicated. There are three basic shapes you need; the diamond, the rectangle and the arrow. The diamond is a decision block. It asks a question and then there is an output for each possible answer to the question. Generally you want to stick to “Yes” and “No” questions since computers only understand “True” or “False.” A rectangle is just an operation block. It defines what will happen in the current step before moving onto the next step. The arrows then simply connect all the blocks together to define how the program will flow. A block can have any number of inputs but only a diamond can have more than one output.
For blackjack there are four distinct flowcharts. The first flowchart covers the initial setup and deal.

There are a number of things this chart doesn’t tell you. One, is that the deal is given a card after all the other players have been dealt a card. Also, that the second card the dealer is given is placed face down. Third, the section of the flowchart after the deal must be repeated for each and every player before we continue to the second flowchart. Another bit of information that the chart doesn’t cover is the value of the cards. An Ace is worth 1 or 11 (whichever gives the player the best hand), cards 2-10 have face value and jacks, queens and kings are worth 10. And finally, the flowchart does not define how we know a dealer has an ace or a ten. In the casino the dealer checks the card that is face down by an electronic marker on the cards. So in the code, we need to check both cards.
Next up is flowchart 2 which covers the logic behind what options a player has. The diagram starts at the diamond with “Does the player have less than 12?”

This flowchart continues until the player is done. In the official casino blackjack rules you may have noticed the ability to take out “insurance” on a hand. Note that we don’t deal with the player going over 21 at this point. We will handle that later. In the casino, going bust (over 21) results in the immediate removal of your bet into the house bank.

Now that all the players have played their hand it’s time for the dealer to reveal his hidden card and follow the standard rules. A dealer must hit if they have less than 17 and stand on 17 or above. Pretty easy logic for that part.

The final stage of blackjack is to check all the players to see if they won or lost. The case of the player having the same value as the bank in our flowchart results in the player losing. It’s a subtle yet important rule. In casino blackjack you must beat the house on card values. If both you and the bank get blackjack, you lose. If both you and the house get 21 by any other means, you lose.
And that concludes this lesson. In the casino rules there are also the options for “insurance” and “splitting” which are not covered in these flowcharts. That is left as an exercise for the reader for now but will be added in as we advance through the lessons.
Blackjack Lesson 1: The Design Document
A design document essentially lays out how the game will work. It can be as detailed or as sparse as you feel comfortable with. You generally do not want to go into a project with no clear direction of what a “finished product” is or your product will never be finished. Our finished product will implement the official casino rules and allow for an infinite number of players per game. Players can be of an infinite number of player types defined using OOP (Object Oriented Programming) concepts. Our first edition will be pure text and allow for a human to play in the tournament. Our second edition will use software rendering techniques to display the game to the player. You’ll want to read through these rules in their entirety and actually play the game to become comfortable with how it is supposed to be played so that you’re better prepared to debug the game logic as we go through and implement the game in software.
Blackjack: Introduction
This isn’t your typical blackjack tutorial. In this series we’re going to cover the entire process of making a product from design document to final release. Along the way you will learn all the basic concepts of the C++ language as well as many of the advanced concepts involving Object Oriented Programming.
If you’re new to C++, have never made a game, or just need a refresher course, this is the series for you.