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
- 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
- to use general purpose C++ resources online (though such
use must be documented).
For this project, you are
not allowed to do the following:
-
Resources that might specifically address this project, say looking through
code that implements blackjack or other card games, are not allowed.
- 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:
-
The only help you may receive on
a project is from your instructor or the other SI204
instructors and SI204 MGSP leader, and that help must be clearly cited. In turn, you
cannot provide help to any SI204 students on this
project.
-
In no circumstance - project, homework or lab - may you copy
code and submit it as your own work. That is the definition
of plagiarism.
-
For projects, you may not look at other
people's project code nor may you show your own code to
others.
-
You can look at online (or other electronic or print) resources for general purpose C++
programming, but not for help writing card game programs
or anything else that is specific to the functionality
required of your project. So, for example, you may not look
at code for a program that plays a card game.
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:
- 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.
-
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
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.
-
Initially, you should create your deck of cards so that the card numbers in your deck are ordered numerically in increasing order:
- Start with the 2 of clubs up to the ace of clubs;
- then all the diamonds;
- then all the hearts;
- 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♠
|
-
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
(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
-
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:
- 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).
- Deal one card to "Player," then one card
to "Dealer". Then display the hands (once again as shown below).
- Deal one card to "Player," then one card
to "Dealer". Then display the hands (once again as shown below).
- 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:
-
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."
-
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.
- At first, Player and Dealer will both be dealt two cards (as we did above).
- But then the program will proceed to ask the Player and Dealer in turns
which of the following they want:
- "hit": In this case, they get dealt another card into their hand, or
- "stand": In this case, their hand stays the same.
[Note: In this project, the player or dealer can "stand"
one round, then "hit" the next — which is not allowed in
real blackjack.]
- The player goes first, and the program should go through three rounds
(i.e. the Player and the Dealer both get three turns).
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.
- Add up points for each card in the hand as follows.
Call this number total.
- Aces are worth 1 point
- Kings, queens and jacks are worth 10 points
- Numbered cards (i.e. 2 though 10) are worth their number in points.
- 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.
- If there are aces in the hand, compute the best scores as follows:
- If the total is greater than 11, "best score" is simply total.
For example, the total for 8♥ A♥ J♠ A♣ is 8 + 1 + 10 + 1 = 20. Since the
total is greater than 11, the best score is 20.
- Otherwise (i.e., if total is less than or equal to 11), the "best
score" is total + 10.
For example, the total for 7♥ A♥ is 7 + 1 = 8. Since the total is at most 11, the best
score is 8+10 = 18.
- 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:
- The Player and Dealer "best scores" are not printed out at any point.
- The Dealer's top (first) card is only shown as
** until the end of
the game. This way, the Player has some uncertainty about what to do.
Further, the following must be implemented:
- 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.
- The game proceeds until one of the following events takes place:
- The Player "busts", which means having a "best score" greater than 21.
- The Dealer busts.
- Both Player and Dealer stand in consecutive turns. That is, Player stands then Dealer stands, or Dealer stands then
Player stands.
- 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
-
In between rounds (i.e. just before printing out the current hands), make the
call
system("clear"); to clear the terminal window, so that only
the current state of the game is showing at any given time.
-
Your extra credit solution should play the game over and over again. Each time
the player finishes a game, the program should ask whether the player wants to
play again or quit.
-
Each play the player must make a $5 bet. If the player
wins, the player gets the bet plus some amount of winnings back. You may
decide what the payouts are (could be as simple as getting your bet plus $5
back). You must report the players winnings / losses at the end of each round.
-
The player must have the option of withdrawing from a game (not the table, just
that game) after the initial two cards are dealt to player and dealer, but
before any other play has taken place. Withdrawing means half the bet is
returned to the player, but the other half goes to the house.
Deadline
Part 1
- You must pass tests on steps 1-4 of Part 1 by 2359 on Friday, March 9.
- Late Penalty: 3A, where A is the number of days late for part 1 (1 minute late = 1 day late).
The full project
- Due 2359 on Monday, March 26.
- Penalty: normal class late policy (15% for 1 day)
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:
- Submit all required items (as described above) on time.
-
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.
-
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,
-
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.