Project 2: Blackjack

Executive Summary

You will implement a fully functioning game of Blackjack. No prior knowledge of the game is needed. However, you will need to know what cards appear in a standard deck of playing cards. See your instructor if you have any questions on that account.

Blackjack (also called "21") is basically a game between an individual player and a dealer. There's a gambling side to the game that this project does not require, but is available for extra credit. The project simply plays the game with the user as "Player" and program as "Dealer", and determines the winner and loser.

Honor: The course policy and the instructions it references, most pertinently COMPSCIDEPTINST 1531.1C, spell out what kinds of assistance are permissible for programming projects. The instructor can give explicit permission for things that would otherwise not be allowed. For this project, you are allowed
  1. to get help from other current SI204 instructors as well as your own and from current SI204 MGSP leaders (any assistance must be documented though), and
  2. to use general purpose C++ resources online (though such use must be documented).
For this project, you are not allowed to do the following:
  1. Resources that might specifically address this project, say looking through code that implements blackjack or other card games, are not allowed.
  2. You are very specifically not allowed to look at previous semesters' SI204 programming project or lab solutions that may have addressed similar issues.
Combining the instructions referenced in the course policy and the explicit permissions above, and what you get is summarized as:
Pass-by-Reference:

You may not include pass-by-reference in your solution to this project. We will eventually cover this in class, but if you already know about it from previous experience, take note that it is disallowed.

Download the coversheet.pdf. Fill out and attach this coversheet when you submit your work.

Part 1 [Up to 30pts] Shuffle

Name your source code file p1m.cpp

A part 1 solution will create and optionally shuffle a deck of cards, printing out the cards (one per line) represented as integers, as described below. Here are specifications:

  1. When your program starts, the user will be presented with this message:
    Shuffle: [n | u <seed>]:
    
    If the user enters n, no shuffling will take place. Otherwise, we'll shuffle using a random number generator, which of course needs to be seeded. In particular, if the user uses the u option followed by a number, this number is then used as the seed. But before we can shuffle, let's talk about how to represent the deck of cards.
  2. Each card will be represented by a single integer cardvalue in the following way:

    • The cardvalue is a combination of the card's face and suit:
      cardvalue =  facevalue + 100 × suitvalue
      
    • The facevalue is represented as follows:
      2 = 2, 3 = 3, ..., 10 = 10, 11 = J(jack), 12 = Q(queen), 13 = K(king), 14 = A(ace)
      
    • The suitvalue is represented as follows:
      1 = ♣, 2 = ♦, 3 = ♥, 4 = ♠
      
    • Note this also means that for an integer variable cardvalue, one can compute suitvalue and facevalue as follows:
      facevalue = cardvalue % 100
      suitvalue = cardvalue / 100
      
    Example:
    K♠ → (facevalue=13,suitvalue=4) → 13 + 100*4  → 413
    
  3. NOTE: you should not be storing face and suit values in your code. You will store a single int for each card. You can recover the face and suit from that int (when you need them) using the above definition.

  4. Initially, you should create your deck of cards so that the card numbers in your deck are ordered numerically in increasing order:
    1. Start with the 2 of clubs up to the ace of clubs;
    2. then all the diamonds;
    3. then all the hearts;
    4. then all the spades (ending with the ace of spades).
    Therefore, your program should work exactly as follows, when the user doesn't shuffle the deck:
    ~/$ ./p1m
    Shuffle: [n | u <seed>]: n  
    102 
    103 
    104 
    
    412 
    413 
    414 
    
    
    ← 2 + 100*1 ← 2♣
    ← 3 + 100*1 ← 3♣
    ← 4 + 100*1 ← 4♣
    
    ← 12 + 100*4 ← Q♠
    ← 13 + 100*4 ← K♠
    ← 14 + 100*4 ← A♠
  5. When the user chooses the shuffling option, the program should shuffle the deck created in step 3 right above. Shuffling must be done exactly as follows: assuming the cards are ordered as described above, and indexed from zero to 51:
    for i from 0 up to 51 do    // so 52 times in total 
      set j to rand() % 52                                           
      swap the index i element of the deck with the index j element 
    
  6. (Note, this is not a good enough shuffling algorithm for "real" applications because it introduces a small amount of bias in the ordering, but this is what we're going to do to keep in simple.)

    Sample runs: red text represents user input.

    ~/$ ./p1m
    Shuffle: [n | u <seed>]: u 2019  
    107 
    403 
    203 
    408 
    
    413 
    412 
    407 
    213 
    414 
    
    
    
    ← 7 + 100*1 ← 7♣
    ← 3 + 100*4 ← 3♠
    ← 3 + 100*2 ← 3♦
    ← 8 + 100*4 ← 8♠
    
    ← 13 + 100*4 ← K♠
    ← 12 + 100*4 ← Q♠
    ← 7 + 100*4 ← 7♠
    ← 13 + 100*2 ← K♦
    ← 14 + 100*4 ← A♠
    ~/$ ./p1m
    Shuffle: [n | u <seed>]: u 2018  
    310 
    410 
    313 
    411 
    
    208 
    308 
    205 
    111 
    302 
    
    
    ← 10 + 100*3 ←10♥
    ← 10 + 100*4 ←10♠
    ← 13 + 100*3 ← K♥
    ← 11 + 100*4 ← J♠
    
    ← 8 + 100*2 ← 8♦
    ← 8 + 100*3 ← 8♥
    ← 5 + 100*2 ← 5♦
    ← 11 + 100*1 ← J♣
    ← 2 + 100*3 ← 2♥

    MILESTONE 1: Submit

    ~/bin/submit -c=SI204 -p=proj02 p1m.cpp
    
  7. Part 1b

    Copy your p1m.cpp to p1.cpp Now, instead of printing the integer "cardvalues," let's print proper cards.
    For example, instead of the integer 310 you'll print out the card 10♥.
    Our Unix terminal program understands "UTF-8" character encodings, which means we can print card suits to the screen. Each suit symbol is represented by a string. Here are the suits and the corresponding string for printing them in unicode.
    ♣ ← "\u2663", ♦ ← "\u2666", ♥ ← "\u2665", ♠ ← "\u2660".
    
    Warning: You should always print a card's face value and suit right-justified to two spaces. This means that you should always spend two characters on printing a face value (note that suits have always length 2).

    In other words, for example, when you print out 2♣, you need to print a whitespace and 2 to print out the face value (since 2 is a single-digit number):

    string clubs = "\u2663";
    cout << " " << 2 << clubs << endl;  // always spend two characters on printing a face value 
    
    When you print out 10♦, you can just print 10 without adding a whitespace (since 10 is a two-digit number):
    string diamonds = "\u2666";
    cout << 10 << diamonds << endl;  // always spend two characters on printing a face value 
    

    Sample runs: red text represents user input.

    ~/$ ./p1
    Shuffle: [n | u <seed>]: n  
     2♣
     3♣
     4♣
     5♣
    
    10♠
     J♠
     Q♠
     K♠
     A♠
    ~/$ ./p1
    Shuffle: [n | u <seed>]: u 2019  
     7♣
     3♠
     3♦
     8♠
    
     K♠
     Q♠
     7♠
     K♦
     A♠
    ~/$ ./p1
    Shuffle: [n | u <seed>]: u 2018  
    10♥
    10♠
     K♥
     J♠
    
     8♦
     8♥
     5♦
     J♣
     2♥

Submit

~/bin/submit -c=SI204 -p=proj02 p1m.cpp p1.cpp

Part 2 [Up to 70pts] Deal

Copy p1.cpp to p2.cpp

A correct part 2 solution will create and shuffle a deck as in Part 1 (but not print the deck), and then simulate dealing cards to a "Player" and a "Dealer" as follows:

  1. Deal one card to "Player," one to "Dealer", one to "Player", and one to Dealer (so both have two cards). Then display the hands (as shown below).
  2. Deal one card to "Player," then one card to "Dealer". Then display the hands (once again as shown below).
  3. Deal one card to "Player," then one card to "Dealer". Then display the hands (once again as shown below).
  4. Deal one card to "Player," then one card to "Dealer". Then display the hands (once again as shown below).
Note that the "top" of the deck is the first card printed in Part 1, and you must deal from the top of the deck (in the Old West, people got shot for violating this rule!). Thus, your program's output really must match what is shown below for the given examples.

Programming Tip:
  1. You really want to have your program simulate dealing a card to a hand:
    • You should be able to point to a variable or two and say "those represent the deck."
    • You should be able to point to a variable or two and say "those represent the player's hand."
    • You should be able to point to a variable or two and say "those represent the dealer's hand."
  2. Make sure you do a good job of putting things in functions.
    • One monolithic main function is not OK, even if it works! Well, in fact, it will be very very difficult to make things work with only a main function.
Sample runs: red text represents user input.
~/$ ./p2
Shuffle: [n | u <seed>]: n

 Player Dealer
|  2♣  |  3♣  |
|  4♣  |  5♣  |

 Player Dealer
|  2♣  |  3♣  |
|  4♣  |  5♣  |
|  6♣  |  7♣  |

 Player Dealer
|  2♣  |  3♣  |
|  4♣  |  5♣  |
|  6♣  |  7♣  |
|  8♣  |  9♣  |

 Player Dealer
|  2♣  |  3♣  |
|  4♣  |  5♣  |
|  6♣  |  7♣  |
|  8♣  |  9♣  |
| 10♣  |  J♣  |
 
~/$ ./p2
Shuffle: [n | u <seed>]: u 2019

 Player Dealer
|  7♣  |  3♠  |
|  3♦  |  8♠  |

 Player Dealer
|  7♣  |  3♠  |
|  3♦  |  8♠  |
|  8♣  |  5♠  |

 Player Dealer
|  7♣  |  3♠  |
|  3♦  |  8♠  |
|  8♣  |  5♠  |
| 10♦  |  9♠  |

 Player Dealer
|  7♣  |  3♠  |
|  3♦  |  8♠  |
|  8♣  |  5♠  |
| 10♦  |  9♠  |
|  A♦  |  8♦  |
~/$ ./p2
Shuffle: [n | u <seed>]: u 103 

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
|  A♥  |  7♦  |

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
|  A♥  |  7♦  |
|  J♦  |  3♦  |
  

Now let's play something that actually looks like a game. Below is an example of how a game might play out (red text represents user input). Your output and the way you get input from the user must match what's shown here.
~/$ ./p2
Shuffle: [n | u <seed>]: u 103  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
Round 1 Player's turn
hit or stand? [h/s] h  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |      |
Round 1 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
Round 2 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
Round 2 Dealer's turn
hit or stand? [h/s] h  
 
 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
|      |  A♥  |
Round 3 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
|      |  A♥  |
Round 3 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  7♥  |  Q♦  |
|  K♥  |  2♠  |
|  J♣  |  4♣  |
|      |  A♥  |
|      |  7♦  |    






Submit

~/bin/submit -c=SI204 -p=proj02 p1m.cpp p1.cpp p2.cpp

Part 3: Best scores [Up to 80pts]

Copy p2.cpp to p3.cpp. A correct part 3 solution should behave exactly like a correct part 2 solution, except that in each round the current "best scores" for both Player and Dealer hands should be shown. The "best score" for a hand is computed according to the following rules.
  1. Add up points for each card in the hand as follows. Call this number total.
  2. If the hand has no aces, "best score" for the hand is simply total.
    For example, the best score for Q♠ 9♥ is 10 + 9 = 19.
  3. If there are aces in the hand, compute the best scores as follows:
  4. Finally, declare a winner at the end of the three rounds: "Player wins" or "Dealer wins". The winner is the highest score without going over 21. If a tie, print "Tie".

Sample run: red text represents user input.
~/$ ./p3
Shuffle: [n | u <seed>]: u 92  

 Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
Player 18, Dealer 18
Round 1 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
Player 18, Dealer 18
Round 1 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
|      |  6♠  |
Player 18, Dealer 14
Round 2 Player's turn
hit or stand? [h/s] s  
 
  Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
|      |  6♠  |
Player 18, Dealer 14
Round 2 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
|      |  6♠  |
|      |  4♥  |
Player 18, Dealer 18
Round 3 Player's turn
hit or stand? [h/s] h  
 
 Player Dealer
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
|  2♣  |  6♠  |
|      |  4♥  |
Player 20, Dealer 18
Round 3 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer     
|  Q♦  |  A♣  |
|  8♣  |  7♣  |
|  2♣  |  6♠  |
|      |  4♥  |
|      |  9♠  |
Player wins


 



Submit

~/bin/submit -c=SI204 -p=proj02 p1m.cpp p1.cpp p2.cpp p3.cpp

Part 4 [Up to 100pts] Full Blackjack!

Copy p3.cpp to p4.cpp. A part 4 solution functions similar to a part 3 solution with the following two exceptions:

Further, the following must be implemented:

  1. Instead of being read in from the user, the dealer choice of whether to hit or stand is now made by your program following the simple rule: the dealer always hits when his current "best score" is less than 17, and always stands otherwise.
  2. The game proceeds until one of the following events takes place:
  3. When the game ends, you must determine if it ended in a tie or a winner. A tie is when both the Player and the Dealer have the same "best score":
Sample run: red text represents user input
~/$ ./p4
Shuffle: [n | u <seed>]: u 901  

 Player Dealer
|  Q♣  |  **  |
|  K♣  |  7♦  |
Round 1 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  Q♣  |  **  |
|  K♣  |  7♦  |
Round 1 Dealer's turn
hit or stand? [h/s] s  

 Player Dealer           
|  Q♣  |  A♦  |
|  K♣  |  7♦  |
Player wins









Sample run: red text represents user input
~/$ ./p4
Shuffle: [n | u <seed>]: u 500  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
Round 1 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
Round 1 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
|      |  2♠  |
Round 2 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
|      |  2♠  |
Round 2 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
|      |  2♠  |
|      |  3♦  |
Round 3 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  K♠  |  **  |
|  7♣  |  2♣  |
|      |  2♠  |
|      |  3♦  |
Round 3 Dealer's turn
hit or stand? [h/s] s  
      
 Player Dealer
|  K♠  |  Q♠  |
|  7♣  |  2♣  |
|      |  2♠  |
|      |  3♦  |
Tie. Playing again.

 
 Player Dealer
|  8♣  |  **  |
|  A♦  |  J♥  |
Round 1 Player's turn
hit or stand? [h/s] s  

 Player Dealer
|  8♣  |  **  |
|  A♦  |  J♥  |
Round 1 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  8♣  |  **  |
|  A♦  |  J♥  |
|      |  A♥  |
Round 2 Player's turn
hit or stand? [h/s] h  

 Player Dealer
|  8♣  |  **  |
|  A♦  |  J♥  |
|  6♥  |  A♥  |
Round 2 Dealer's turn
hit or stand? [h/s] h  

 Player Dealer
|  8♣  |  5♦  |
|  A♦  |  J♥  |
|  6♥  |  A♥  |
|      | 10♣  |
Dealer busts, player wins

Optional: Pause for dramatic effect after each time the dealer makes a decision. To do so, make the call sleep(1). This will require #include <unistd.h>. This "sleeps" your program for 1 second.

Submit

~/bin/submit -c=SI204 -p=proj02 p*.cpp

Extra Credit [Up to 110pts]

The extra credit solution will not even be considered unless the Part 4 Full credit solution functions correctly.

Name your code px.cpp

Deadline

Part 1

The full project

Submit

Important: Be sure to fill out the coversheet and hand in a physical copy!

~/bin/submit -c=SI204 -p=proj02 p*.cpp 

How projects are graded / how to maximize your points

Things to keep in mind to receive full marks:
  1. Submit all required items (as described above) on time.
  2. Make sure the form of your output matches the form of the example output in all ways. Of course the cards you see and decisions dealer and player make will vary from the examples you see.
  3. Make sure you follow the SI204 Style Guide. This means proper indentation, use of whitespace within lines of code, logical organization of chunks of code,
  4. Make sure you do a good job of putting things in functions. One monolithic main function is not OK, even if it works! The quality of your overall design will play a role, and a large part of that boils down to using functions well.
Most important: Look at the the grading sheet.