Monte-Carlo Simulation: Blackjack

The main purpose of this project is to give you practice building classes in Java. To do this, we'll simulate a simple version of the card game Blackjack. The objects in a card game include: a Card, a Deck, a Hand, and Game. You'll make a class for each one, connecting them together as appropriate. We'll also start to make use of a few of the many Java library classes.


Project Checklist: Before submitting your project, make sure you have completed all of the following!

Tasks

For this assignment you will implement classes to represent a card, a hand, a deck, and the game Blackjack. The result will be a text-based version of the game that runs in the terminal. You are free to make the game more complex as an extension.

The purpose of the assignment is not to create a Blackjack game for people to play, however, but to study the properties of Blackjack, given a particular rule set, when played over many hands. You are using CS to study the properties of a system defined by a set of rules.

For this project, we have provided small testing programs that will test the functionality of your code. In future projects, you will write these tests yourselves, but we felt that for the first two projects it would be nice if we gave you good starting examples.

More information about Blackjack.


Basic Blackjack Rules

The link above contains an extensive description of the full rules of Blackjack. For this project, you are required to implement only a simplified rule set. You can implement more of the rules as part of project extensions.

There will be only two players in the game: the player and the house. In our simplified rule set, an ace is worth 11 points. In an actual game, an ace can be either 1 or 11. Play should use the following procedure.

  1. The player and the house should each receive two cards dealt from a deck
  2. The player should play their entire turn before the dealer.
    • The player should calculate the value of their hand (the sum of the card values) and choose whether to take a card (hit) or stop at the current value (stand).
    • If the player chooses to hit, then if the new value of their hand is greater than 21, the player loses (busts) and the game is over. If the value is less than 21, the player repeats the process until they stand or bust.
  3. If the player did not bust, the dealer plays.
    • The dealer will continue to take a card (hit) until the value of their hand is 17 or greater.
    • If the dealer busts, then the player wins.
  4. If player and dealer both avoided a bust, then the hand with the highest value wins the game. If both hands are of equal value, then the hand is a tie (push).

When playing Blackjack, the deck is usually not rebuilt and shuffled between each game. Instead, the deck is rebuilt and shuffled when it gets below a certain number of cards. For example, you could implement the rule that when there are fewer than 26 cards (out of 52), then the deck should be rebuilt to 52 cards and shuffled. Therefore, shuffling should not be done every game, but should be an action that is conditioned on the number of cards left in the deck. How often the deck is shuffled will affect game play because it affects the probabilities of certain cards appearing.


The Card Class

Download the following two files: Card.java and CardTests.java. In this section you will complete the java class called Card, which should hold all information unique to the card. For this assignment, it needs to hold only the value of the card, which must be in the range 2-11 (or 1 - 11 if you implement aces with optional values of 1 or 11). The Card class should have the following methods.

When you have completed the methods above, run the CardTests program by compiling it and executing java -ea CardTests in your terminal/cmd.

The Hand Class

Download the following two files: Hand.java and HandTests.java. In this section, complete the java class called Hand, which should hold a set of cards. You can use an ArrayList (import java.util.ArrayList) to hold the Card objects. The class should have at least the following methods.

When you have completed the methods above, run the HandTests program by compiling it and executing java -ea HandTests in your terminal/cmd.

The Deck Class

Download the following two files: Deck.java and DeckTests.java. Create a java class called Deck, which should hold a set of cards and be able to shuffle and deal the cards. You should use an ArrayList to hold the cards. The class should support the following methods.

When you have completed the methods above, run the DeckTests program.

The Blackjack Class

Download the following file: BlackjackTests.java. In this section create a class called Blackjack (no skeleton file for you this time! Do your best to make it clean) that implements a simple version of the card game. The class will need to have fields for a Deck, a Hand for the player, a Hand for the dealer, and a field for the number of cards below which the deck must be reshuffled. The main function for the Blackjack class should implement one complete game.

Use the main function of your Blackjack class to test the above functions. Make sure they are working properly by dealing two hands and then calling the playerTurn and dealerTurn methods, printing out the game state and making sure the rules are correctly implemented.

Create a method to play a single game.

The attached BlackJackTests program is extremely bare-bones. Update it to have better tests and explain how/what you chose to do in your report.

Storing Game Output

Once you have completed the above four classes, generate a printout of three different games. You can send output to a file using the greater than symbol on the command line. For example, the command

java Blackjack > mygames.txt

would play a game and send it to the file mygames.txt. Please include these in the results section of your report!

Simulation

Make one more class called Simulation. This class should have only a main function that executes 1000 games of Blackjack. The code should create and re-use a single Blackjack object. It should keep track of how many games the player wins, how many the dealer wins, and how many are pushes. Print out the total in the end both as raw numbers and as percentages. Please include this in the results section of your report!


Reflection Questions

Please answer the following 2 questions. Put any written responses numbered in the Reflection section of your report, and include any code with your other code files. This section is worth a substantial portion of your project grade, and generally includes the type of more conceptual exercise you can expect to see on exams, so it's good preparation for that!

Question 1: Design and write a better test for the Shuffle method

In this question, you'll pick up where we left off in the Lab with the ShuffleTests.java file. There is a design portion of this where you will write a few sentences telling us about your test, and an implementation portion of this where you will implement your test.

Design: Write a few sentences describing how your test works and how it addresses the problem with the test you wrote for the lab. Specifically, why did dumbShuffle pass that test and not this new and improved one? You should comment on what types of mistakes this test would catch, and if applicable, what types of mistakes it might not catch.

Implementation: Add a new method to that file called shuffleTestsReflection and write the better test for the shuffle method you designed above. The dumbShuffle method from the lab should not pass your new and improved test, but the algorithm you used to shuffle your Deck class in the project should pass your new test. Note: the easiest way to show this is to add a method to DumbShuffle.java called smartShuffle and copy over your algorithm from the project. You'll need to convert the Card types to Integer types for consistency.

Question 2: Design and Write a better ArrayList class

This assignment will primarily focus on the ArrayList data structure. Recall that in this structure, we maintain an underlying array (throughout this homework, I'll refer to that array as arr), and keep track of the number of items currently stored in arr (which I'll refer to as size). In lecture, we have utilized an add method that roughly looked something like the following:

def add(item):
    if arr is filled:
        resizeArray(size * 2) // this doubles the capacity of arr
    
    set arr[size] = item
    size += 1

This method had a number of benefits:

One drawback that we saw from the structure of our ArrayList class was that it would be inefficient to insert at the beginning of the list. This was because we would have to shift all the previously stored items in the list down by one: looping over all the items we had previously seen just to add one new item! As a result, the code would look something like:

def addFirst(item):
    if arr is filled:
        resizeArray(size * 2) // this doubles the capacity of arr
    
    for i in size -> 1: 
        arr[i] = arr[i - 1]
    arr[0] = item
    size += 1

As it turns out, there are ways around this predicament. Your goal for this assignment is to develop an ArrayList that implements the following methods without a single looping mechanism (no for/while loops):

To help get you going, we've started a file for you.

For some guidance, we have the following hints:

When you think you've got it, you can use this tester (which we will also use) to see if you were successful. In your report, explain how the ArrayList you implement in this question achieves an O(1) runtime for each of the above methods.

Extensions

Projects are your opportunity to learn by doing. They are also an opportunity to explore and try out new things. Rather than list out every activity for a project, the defined part of each project will be about 85% of the assignment. You are free to stop there and hand in your work. If you do all of the required tasks and do them well, you will earn a B/B+.

To earn a higher grade, you can undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. Extensions are your opportunity to customize the assignment by doing something of interest to you. Each week we will suggest some things to try, but you are free to choose your own.

A common question is "how much is an extension worth?" The answer to that question depends on the extension, how well it is done, and how far you pushed it. As teachers, we prefer to see one significant extension, done well, where you explore a topic in some depth. But doing 2-3 simpler extensions is also fine, if that's what you want to do. Choose extensions that you find interesting and challenging.

The following are a few suggestions on things you can do as extensions to this assignment. You are free to choose other extensions, and we are happy to discuss ideas with you. For full credit, extensions need to be discussed in the report adequately. For example, if you chose the second extension below, I would expect that you compared the results of the simulations with the original rules and the updated rules and reasoned about the why whatever changes did or did not take place.

  1. Create a new method in your Blackjack class called playerTurnInteractive() that uses input from the terminal to control the player's actions. Then create a new class Interactive that lets you play Blackjack on the terminal.
  2. Add in more of the rules for Blackjack. For example, an Ace (value 11 in the default case above) can have the value 1 or 11, whichever is more advantageous. If you add in the Ace rule, then you will also want to take into account a real Blackjack (2 cards totaling 21) when evaluating the winning hand. A Blackjack beats a 21 with more than 2 cards. See how these rules affect the simulation results.
  3. Make your game use 6 decks, and reshuffle if only 1 deck is left. Play the game 1000 times, and observe how many games the player wins, how many the dealer wins, and many are pushes. Compare with using only a single deck.
  4. Run the simulation with different decision rules for the player and see how it affects the outcome percentages over many games (>= 1000).
  5. Add a type of betting strategy to the simulation and see if the player can win money even while losing more games than winning.
  6. Try running the simulation with different numbers of games and see how variable the results are. For example, run the simulation using M games (e.g. M = 100) and do this N times (e.g. N = 10). Then calculate the standard deviation of the results. Then you can plot the standard deviation versus the number of games (M) in the simulation to see how the results stabilize as you use a larger number of games.

Congrats, you're done with everything except for your project report where you will communicate your findings from all that code! Make sure to write up your report in the project report file in the Google Classrooms assignment. The project report is an important part of the work in this project, so don't forget to do it!