This is an individual project, to be completed on your own. It is considered dishonest either to read someone else’s solution or to provide a classmate with a copy of your work. Do not make the mistake of thinking that superficial changes in a program (such as altering comments, changing variable names, or interchanging statements) can be used to avoid detection. If you cannot do the work yourself, it is extremely unlikely that you can succeed in disguising someone else’s work. We are adamant that cheating in any form is not tolerated. Even the most trivial assignment is better not done than if you cheat to complete it.

Prerequisites

  1. Primitive types
  2. Simple arithmetic operations
  3. Conditionals
  4. Loops
  5. Scanner class

Learning Objectives

  1. Problem solving
  2. Building interactive programs, that is, getting input from the user
  3. Using conditionals and loops to guide the program behavior

Introduction

In this project you will simulate a game of BlackJack. You will implement the logic for a round; you will also provide the user with the option to play another round or exit the game.

Game Rules

Blackjack, also known as 21, is a very popular casino game between a player and a dealer. It is played with a deck of 52 cards. For this project, we will consider the simplest version of the game.

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:

  1. 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.
  2. Reach a final score higher than the dealer without exceeding 21
  3. 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

The rules of Blackjack were explained in the previous section. But now, how to go from the description of the rules to a Java program? As this is your very first project, we will give you some help.

From the Blackjack description, we know that we need to represent in Java two “objects”, that is:

  1. a card, that represents a single card in a 52 deck.
  2. the deck of 52 cards

Representing a card

Have a look at the class 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

Now have a look at the 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 Strings (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:

  1. allocates the 52 entries of the array deck, and
  2. 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

Now, let’s look at your job. We want to build the code that simulates a BlackJack round. This will be implemented in the BlackJack class.

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

This project consists of three classes:

  1. Card: It represents a single card in a 52 deck. This class is given to you, do not alter it.
  2. Deck: It represents a deck of 52 cards. This class is given to you, do not alter it.
  3. 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.
    1. public BlackJack()
    2. public int getValue(String rank)
    3. public void initGame()
    4. public boolean hitAnotherCard()
    5. public void playerHit()
    6. public void dealerHit()
    7. public boolean initScoreCheck()
    8. public void checkRoundWinner()
    9. 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 theconstructor 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.

DescriptionTypeVariable NameObservation
Deck of cardsDeckdeckYou won’t handle it
Player scoreintplayerScore
Dealer scoreintdealerScore
Game winnerintwinner
I/O ScannerScannerscannerUsed 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;

To print the HIT_CARD_MESSAGE String, use 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 a constructor. 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 and dealerScore) 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 class Card 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

Your project will be graded by running a certain number of test cases on the code you submitted. The test cases are run automatically by Vocareum whenever you make a submission.

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

The test case(s) check that the BlackJack constructor initialized the BlackJack fields properly. Properly means: according to the specification given in this handout. So you should ask yourself:

  1. which are the BlackJack fields?
  2. how should they be initialized by the constructor?

Two test cases used: testConstructorPlayerScore() and testConstructorDealerScore()

BlackJack class: getValue method test cases

Let’s recall the specification of the getValue method:

  • 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 class Card 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

  1. Create a new project in IntelliJ (you can name it project01).
  2. Download the CardDeck and BlackJack classes and copy/move them to your IntelliJ project.
  3. Write the code of incomplete methods in the BlackJack class.
  4. 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 the traditional BlackJack game, the Ace card value can be counted as either 1 or 11. For a player, this is easily implemented as we only need to prompt a question to the user to ask how he wants to add his Ace cards. However, in the case of the dealer, the decision has to be made automatically.

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.