Prerequisites
-
Primitive types
-
Simple arithmetic operations
-
Conditionals
-
Loops
-
Scanner class
Learning Objectives
-
Problem solving
-
Building interactive programs, that is, getting input from the user
-
Using conditionals and loops to guide the program behavior
Introduction

Game Rules
The goal of the game is for the player to obtain a hand of cards that beats the dealer in one of the following ways:
-
Get 21 points on the first two cards, this is called a BlackJack. Note that if the dealer gets a BlackJack (21) on the first two cards, no matter what, the dealer wins.
-
Reach a final score higher than the dealer without exceeding 21
-
Let the dealer draw additional cards until her hand exceeds 21
At the beginning of the game, the player is dealt two cards and the dealer takes two cards. The value of their cards are added together to update each of their scores. Face cards (King, Queen and Jack) are counted as 10 points. Ace cards are counted as 11 and any other cards are counted as the numeric value shown on the card. If the player scores 21 on the first two cards, and the dealer doesn’t, he gets a BlackJack and immediately wins. If the dealer gets a BlackJack, the player loses, no matter the score.
After receiving their first two cards, if the game continues, the player has the option of getting additional cards, one by one, which is called a hit. If the player scores higher than 21 at any given point is called a bust and results in an immediate loss. Every time the player hits, the dealer must also hit, unless her total score is 17 or more. Once the dealer reaches or exceeds 17, she can’t take any more cards.
Once the player has decided he doesn’t want to hit anymore, the round has ended. The player wins by not busting and having a total higher than the dealer’s. The dealer loses by busting at the end of the round or having a lesser hand than the player who has not busted. If the player and dealer have the same total, this is called a push and it is considered a tie.
Discussion: From description to code
From the Blackjack description, we know that we need to represent in Java two “objects”, that is:
-
a card, that represents a single card in a 52 deck.
-
the deck of 52 cards
Representing a card
Card
, provided to you.
-
/** * Card * * Represents a card in a traditional 52 deck * * @author Maria Pacheco, pachecog@purdue.edu * @version June 17, 2016 */ public class Card { private String suit; private String rank; /** * Creates a new card of the given suit and rank * @param suit of the card * @param rank of the card */ public Card(String suit, String rank) { this.suit = suit; this.rank = rank; } /** * Gets the rank of the card * @return the rank of the card */ public String getRank() { return this.rank; } /** * Gets the suit of the card * @return the suit of the card */ public String getSuit() { return this.suit; } }
The class has two fields (also called data members or instance variables) – you will learn later what the keyword private
means -, representing the suit and the rank of a card. Both of them are of type String
.
The class has a constructor public Card(String suit, String rank)
that creates an instance of the card; note the two parameters of the constructor and how the constructor assigns the argument values to the two data members.
The Card class also provides two methods (so-called ‘getters’):
-
public String getRank()
-
public String getSuit()
that return the rank/suit of the card at hand, respectively.
Representing a deck of cards
Deck
class, also provided to you.
- Deck.java
-
import java.util.Random; /** * Deck * * Represents a traditional deck of 52 cards * * @author Maria Pacheco, pachecog@purdue.edu * @author Lorenzo Martino, lmartino@purdue.edu * @version June 17, 2016 */ public class Deck { private Card [] deck; private String[] suits = {"Club", "Diamond", "Heart", "Spade"}; private String[] ranks = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"}; /** * Creates a new deck and initialize it */ public Deck() { deck = new Card [52]; deckInit(); } /** * Draw a card from the deck. * It selects the card randomly and "eliminates" it from the deck by setting to null * the corresponding entry in the Deck array. * @return the random card */ public Card drawCard() { Random rand = new Random(); while (true) { int pos = rand.nextInt(deck.length); if (deck[pos] == null) continue; else { Card ret = deck[pos]; deck[pos] = null; return ret; } } } /** * Initialize the deck to the 52 cards, * one of each rank for each suit */ public void deckInit() { int i = 0; for (String suit: suits) { for (String rank: ranks) { deck[i] = new Card(suit, rank); i += 1; } } } }
We need to represent all 52 cards in the Deck. To this end we used an array of 52 Cards. An array is a type of ordered container of elements of the same type. For example, an array of integers or an array of String
s (More on arrays on 6/27 and 6/28 lectures). Even if you were just introduced to the array, note how Java allows us to define an array that contains not only instances of Java primitive types (such as integer, double) or Java reference types (String), but also instances ofuser defined types, such as Card:
private Card [] deck;
Note also that the statement above only declares the variable deck, but it does not allocate any space for the elements of the array.
We also used two other fixed size arrays to represent the possible values of a card Suit and Rank:
private String[] suits = {“Club”, “Diamond”, “Heart”, “Spade”};
private String[] ranks = {“Ace”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”, “10”, “Jack”, “Queen”, “King”};
To complete our work we need to assign each of the possible 52 card instances to each element of the array deck
. This is done by the Deck constructor (public Deck()
) that:
-
allocates the 52 entries of the array deck, and
-
assigns a card to each of the entries above by calling the
public void deckInit()
method.
What are the real-life operation(s) that can be done with a deck of cards and that we want to simulate in our Java program? Usually a deck of cards is shuffled before the game, and the player (or the dealer) draws a card from the deck.
The result of shuffling a deck of cards and of drawing a card from the deck is a random one (that is, their result can not be predicted in advance). If you look at the Deck public Card drawCard()
method, you can see how the random result is achieved by basically generating a random number in the range 0-51 (int pos = rand.nextInt(deck.length);
) and returning the card at the corresponding index of deck array. Arrays indices start from 0. Note also how the selected entry in the deck array is set to null
to simulate the fact that the extracted card is no longer available in a subsequent draw.
Simulating the BlackJack game
First of all, we need a deck of cards and to keep track of the players’ and dealer’s score. We also need to know who won the round, or if a Push result occurred. To this end we need instance variables.
Look at the BlackJack class skeleton given to you.
- BlackJack.java
-
public class BlackJack { private static final int PLAYER = 1; private static final int DEALER = 2; private static final int PUSH = 3; private static final String HIT_CARD_MESSAGE = "Draw another card? (Y/N): \n"; public Deck deck; public Scanner scanner; public int winner; public int playerScore; public int dealerScore; public BlackJack() { deck = new Deck(); // TO-DO: initialize other instance variables } /** * Takes a card from the deck and returns its rank * @param name of the person taking the card * @return the rank of the card */ public String takeCard(String name) { Card card = deck.drawCard(); printCard(card, name); return card.getRank(); } /** * Prints the given card */ public void printCard(Card card, String name) { System.out.println(name + ": " + card.getSuit() + " " + card.getRank()); } public int getValue(String rank) { // TO-DO } public void initGame() { // TO-DO } public boolean hitAnotherCard() { // TO-DO } public void playerHit() { // TO-DO } public void dealerHit() { // TO-DO } public boolean initScoreCheck() { // TO-DO } public void checkRoundWinner() { // TO-DO } public void playRound() { // TO-DO } /** * Main method to execute a round of BlackJack */ public static void main(String[] args) { BlackJack game = new BlackJack(); game.playRound(); } }
Now, are there operations that must be performed before actually playing the BlackJack round? Well, we need to create the deck of cards and initialize the player’s and dealer’s scores. Where we can do that? These operations can be done in the BlackJack constructor.
Now let’s try to figure out how to design and implement the simulation of a BlackJack round. If we look at the game (and round description), we can see that the very first thing that must be done in a round is to give the player and the dealer the first two cards.
Then additional things have to be done, for example, see if there is a blackjack, check if the player wants to hit, hitting cards and declare a winner. This task separation leads us to our design of the structure of the solution structure, basically by defining defining different methods for each task. For example:
-
do very first things (i.e. give the player and the dealer the first two cards).
-
check if the player or the dealer got a BlackJack.
-
ask the player if she wants to draw another card.
-
drawing cards
-
checking who is the round winner, or if the round ended in Push.
-
putting it all together: playing a round!
You may wonder if this structure is unique. The answer is no; a given problem can be solved in different ways, and hence one could devise a different structure (that is, a different decomposition into tasks) and, correspondingly, different methods.
Implementation Details
-
Card: It represents a single card in a 52 deck. This class is given to you, do not alter it.
-
Deck: It represents a deck of 52 cards. This class is given to you, do not alter it.
-
BlackJack: It implements the logic of the game. You will implement the following methods. The description of these methods is given in the subsequent section.
-
public BlackJack()
-
public int getValue(String rank)
-
public void initGame()
-
public boolean hitAnotherCard()
-
public void playerHit()
-
public void dealerHit()
-
public boolean initScoreCheck()
-
public void checkRoundWinner()
-
public void playRound()
-
BlackJack class
-
Instance variables: The class BlackJack keeps the status of the current game. To be able to do this, we will have the following instance variables. Instance variables are declared inside a class but outside methods and are accessible to all methods in the class. Initial values to this variables are assigned in the
constructor
of the class, more details will be provided below.
* DO NOT declare these instance variables to be private for this project. If you don’t know what that means, don’t worry about it for now.
Description | Type | Variable Name | Observation |
---|---|---|---|
Deck of cards | Deck |
deck |
You won’t handle it |
Player score | int |
playerScore |
|
Dealer score | int |
dealerScore |
|
Game winner | int |
winner |
|
I/O Scanner | Scanner |
scanner |
Used to read input. |
-
String constants: In a method of this class, you are required to print a specific message to the standard output. In order to follow a common convention throughout this project and to facilitate auto-grading, you MUST use the HIT_CARD_MESSAGE string constant. It is provided in the skeleton, please do not alter it.
-
Int constants: In some methods of this class, you are required to update the winner variable. PLAYER, DEALER and PUSH are constants used for this, you MUST use them, as they will be used for testing purposes.
-
Scanner: For testing purposes, we need the Scanner class to be an instance variable. Use it and do not re-declare it.
// constant for printing messages private static final String HIT_CARD_MESSAGE = "Hit another card? (Y/N): \n"; // constants to update the winner variable private static final int PLAYER = 1; private static final int DEALER = 2; private static final int PUSH = 3;
System.out.printf()
. DO NOT use System.out.println() even if no formatting is needed. It will add an extra newline. We need this for testing purposes.Example: System.out.printf(HIT_CARD_MESSAGE);
If you want to print messages in other methods, you may use
System.out.println()
.
-
public BlackJack()
: This method has the same name as that of the class and is called aconstructor
. It creates an object instance of the class andinitializes the instance variables of the newly created object. The constructor, being a method, can have input parameters. In our case it doesn’t have any. Think about what the value of our instance variables (playerScore
anddealerScore
) should be when a game is created. Remember to initialize the scanner.
public BlackJack() { this.deck = new Deck(); // TO-DO: initialize other instance variables }
Playing one round is quite complex, so let’s create a set of auxiliary methods:
-
public int getValue(String rank)
: This method returns the value of the card. The value of a card depends solely on its rank. Face cards (King, Queen and Jack) are counted as 10 points. Ace cards are counted as 11 and any other cards (2, 3, …) are counted as the numeric value shown on the card. Take a look at the provided classCard
for the list of possible ranks.
-
public void initGame()
: In this method, the player and dealer take two cards each and their scores are updated.
Use the method public String takeCard(String name)
provided in the BlackJack class to draw the cards. This method returns the rank of the card as aString
. It receives a name as an input, for example takeCard(“Player”)
When the player draws a card or takeCard(“Dealer”)
when the dealer draws a card, this method will print the name and the card to the output.
-
public boolean hitAnotherCard()
: This method prompts the player to ask if she wants to draw another card. This is done using the string constantHIT_CARD_MESSAGE
. When this method is called, the following message should be displayed
Hit another card? (Y/N):
This method returns true
if the player enters Y, or false
if the player enters N. If the player enters something else then the method prompts again. It will continue prompting until a valid option is entered.
-
public void playerHit()
: In this method the player takes one card and his score is updated.
-
public void dealerHit()
: In this method the dealer takes one card and his score is updated, only if his score allows him to keep drawing cards. Take a look at the game rules.
-
public boolean initScoreCheck()
: This method is called after each player takes two cards. It checks if the player or the dealer have a BlackJack and, if either of them does, returns true. It returns false otherwise. Additionally, this method must update the winner in case of a BlackJack.
-
public void checkRoundWinner()
: This method is called at the end of the round. It checks the scores and updates the winner accordingly. The three possible scenarios are: the dealer wins, the player wins or there is a push.
-
public void playRound()
: This method implements the logic of one round of the game. It uses the auxiliary methods (i.e., the other methods that you have written). You may use additional print statements to guide your understanding and make the execution of the game more natural. Please refer to the game rules and the execution examples provided below for details.
The BlackJack
class contains a main method that you can use to run and test your program.
Output Examples
Example 0: Pushing the game
Starting the round... ============ Player: Heart 7 Player: Spade Queen Player score: 17 ============ Dealer: Club Ace Dealer: Diamond 9 Dealer score: 20 ============ Draw another card? (Y/N): Y ============ Player: Heart 3 Player score after card: 20 ============ Draw another card? (Y/N): N PUSH Player score: 20, Dealer score: 20
Example 1: Busting the game
Starting the round... ============ Player: Heart Queen Player: Heart 6 Player score: 16 ============ Dealer: Heart 4 Dealer: Spade Queen Dealer score: 14 ============ Draw another card? (Y/N): Y ============ Player: Spade 8 Player score after card: 24 ============ PLAYER BUST! End of the round DEALER WINS! Player score: 24, Dealer score: 14
Example 2: Winning the game
Starting the round... ============ Player: Spade Jack Player: Diamond Queen Player score: 20 ============ Dealer: Heart 8 Dealer: Diamond Ace Dealer score: 19 ============ Draw another card? (Y/N): N PLAYER WINS! Player score: 20, Dealer score: 19
Example 3: Dealer loses by busting
Starting the round... ============ Player: Spade 3 Player: Club 5 Player score: 8 ============ Dealer: Heart 4 Dealer: Heart Ace Dealer score: 15 ============ Draw another card? (Y/N): Y ============ Player: Spade 6 Player score after card: 14 ============ Dealer: Spade Queen Dealer score after card: 25 ============ Draw another card? (Y/N): N PLAYER WINS! Player score: 14, Dealer score: 25
Example 4: Losing the game
Starting the round... ============ Player: Diamond 3 Player: Diamond 10 Player score: 13 ============ Dealer: Heart King Dealer: Club 6 Dealer score: 16 ============ Draw another card? (Y/N): N DEALER WINS! Player score: 13, Dealer score: 16
Example 5: Player BlackJack!
Starting the round... ============ Player: Diamond Ace Player: Club 10 Player score: 21 ============ Dealer: Diamond 5 Dealer: Club 7 Dealer score: 12 ============ BLACKJACK! PLAYER WINS! Player score: 21, Dealer score: 12
Example 5: Dealer BlackJack
Starting the round... ============ Player: Heart 8 Player: Diamond King Player score: 18 ============ Dealer: Heart Ace Dealer: Diamond Jack Dealer score: 21 ============ BLACKJACK! DEALER WINS! Player score: 18, Dealer score: 21
Test cases
Additionally, we provide a set of test cases for you to run as you develop your problem: Project01StudentTest. Use this test cases before making any attempts to submit to Vocareum.
This section provides you some explanations about the test cases and the messages they can produce.
The main purpose of a test case is to verify that the result(s) produced by the invocation of a certain method is/are the expected one(s).
Basically, for each method/constructor that you were asked to implement, we defined a certain number of test cases. A test case is a method that invokes the method to be tested and checks that it produces the expected result. The name of the test case method provides hints as to the method tested. The message of each test case follows the convention “Class::method message”. This is done to provide detailed information as to where is the test case failing.
Examples
BlackJack class: constructor test cases
-
which are the BlackJack fields?
-
how should they be initialized by the constructor?
Two test cases used: testConstructorPlayerScore()
and testConstructorDealerScore()
BlackJack class: getValue method test cases
-
public int getValue(String rank)
: This method returns the value of the card. The value of a card depends solely on its rank. Face cards (King, Queen and Jack) are counted as 10 points. Ace cards are counted as 11 and any other cards (2, 3, …) are counted as the numeric value shown on the card. Take a look at the provided classCard
for the list of possible ranks.
As the method has an input argument (the rank of a card), testing that it works properly means testing that for each possible value of the argument (that is, each possible card rank), it returns the expected value. The value of a card rank was also defined in the specifications:
“…Face cards (King, Queen and Jack) are counted as 10 points. Ace cards are counted as 11 and any other cards are counted as the numeric value shown on the card.”
As there are 13 possible different values for the card rank, we defined a corresponding number of test cases methods. They are those whose name begins with testGetValue.
Setup
-
Create a new project in IntelliJ (you can name it project01).
-
Download the
Card
,Deck
andBlackJack
classes and copy/move them to your IntelliJ project. -
Write the code of incomplete methods in the
BlackJack
class. -
Make sure to comply with coding standards
Grading Rubric
-
05% – Constructor:
Blackjack()
-
10% –
getValue
-
05% –
initGame
-
20% –
hitAnotherCard
-
05% –
playerHit
-
05% –
dealerHit
-
10% –
initScoreCheck
-
10% –
checkRoundWinner
-
25% –
playRound
-
05% – Coding Style
Submission Instructions
-
Before submitting test your code extensively by playing the game manually and through the test cases made available to you. Do that before uploading to Vocareum. Vocareum will ONLY provide you suggestions (hints) on where you could have possibly gone wrong. Don’t expect Vocareum to pinpoint your mistakes.
-
If you used IntelliJ IDE, make sure that your project doesn’t have a package name. To avoid this, set the package name to default while creating the project.
-
Submit the files: BlackJack,java, Card.java, and Deck.java to Vocareum.
-
You can submit to Vocareum at most 20 times.
-
The score of your project will be the score of your last submission.
-
Extra Credit: Optional
In a different module, implement a 1 or 11 version of BlackJack, for both the player and the dealer. Think of a smart way to have the dealer automatically choose the value of his Aces. You have the freedom to modify the methods, add extra methods, print statements, etc, anything you deem helpful.