|
Assignment 18
Task
Write a simple Blackjack game.
Concepts: Review of class-related concepts
Steps
The point of this assignment is to reuse your PlayingCard and Deck classes from A17 in a different context.
Your Blackjack game must follow the standard rules (such as described at Wikipedia or Pagat), but without betting or any of the advanced options that accompany betting. Instead, the game should simply track how many hands the player has won, tied, and lost.
Cards have a value equal to their face value except for face cards (which are worth 10 points each) and Aces (which are worth either 11 or 1). The value of an Ace should be 11, unless this would cause the hand value to exceed 21; in that case, the Ace instead counts as 1. Note that there should be no artificial limit on the number of cards or Aces that can be in a hand (as long as the total hand value is still <= 21.)
For each hand/round, your game should do the following:
- Deal two cards (one of which is "face down") to the computer dealer and two to the human user. (The dealer's face down card should be revealed at the end of the game, or at the beginning of the dealer's turn, whichever comes first.)
- If both dealer and player have hands totaling 21 at this point, the game is a tie ("push"). If only one or the other has 21, then that player wins the game. Otherwise, the game continues with the player's move.
- The player may continue to choose to hit (draw another card) or stand (stop drawing) as long as their hand totals 21 or less. The game should show the player's hand total during this process. If the player exceeds 21, they lose the game.
- If the player stands with 21 or fewer points, the dealer then plays. The dealer must always hit as long as its hand value is 16 or less. If 17 or above, the dealer must stand. If the dealer's hand exceeds 21 at this point, the player wins. (You may have the dealer hit a soft 17, if you wish.)
- At this point, both players have stood. The player with the higher-scoring hand wins, or the game may be a tie.
- Display how many games have been won/tied/lost so far and ask the player if they'd like to play another hand.
Design Requirements
- I'm going to grade this assignment manually, so input and output formatting is largely up to you. Just make sure that you explain to the user what the game controls are.
- Each time you ask the user for input, you must display the current total value of their hand. Display the dealer's total by the end of the round/hand. (This is the minimum; you may display the total more often than this.)
- You must handle Aces automatically and correctly. That is, do not ask the player to choose whether an Ace counts as 1 or 11. And you must change the value of a previous Ace if necessary. For example, a hand of Ace + 5 has a value of 16. If the player hits and gets a 9, then it has a value of 15. Do not convert all the Aces in the hand, but only as many as necessary. For example, if the previous hit has given another Ace (rather than a 9), then the hand total would have been 17 (not 7!).
- Your deck must not run out of cards--create a new deck either when you run out of cards or at the beginning of each game (depending on whether you want to be able to practice your card-counting skills).
- You must write a helper class named
BlackjackHand . This class must have at least the following methods:
public BlackjackHand()
This constructor creates an emtpy hand.
public void add(PlayingCard card)
Adds the given card to this hand.
public int getValue()
Returns the current point value of this hand according to the rules of Blackjack.
These are the minimum methods; you may write others as you find useful.
Sample Output
As mentioned, your output format is up to you. But this sample run of 6 games shows one way you could format things that meets the requirements:
Welcome to Blackjack!
DEALER shows: 5 of Spades
PLAYER hand: 7 of Clubs, Jack of Clubs = 17
[H]it or [S]tand? s
DEALER: 5 of Spades, 10 of Hearts = 15
+ 7 of Hearts = 22 BUST!
You WIN.
[Won: 1. Tied: 0. Lost: 0] Play again (y/n)? y
DEALER shows: Ace of Diamonds
PLAYER hand: 2 of Diamonds, 3 of Diamonds = 5
[H]it or [S]tand? h
+ 9 of Clubs = 14
[H]it or [S]tand? h
+ 7 of Diamonds = 21
[H]it or [S]tand? s
DEALER: Ace of Diamonds, 4 of Diamonds = 15
+ Queen of Spades = 15
+ 5 of Diamonds = 20
You WIN.
[Won: 2. Tied: 0. Lost: 0] Play again (y/n)? y
DEALER: Ace of Hearts, King of Clubs = 21
PLAYER: 6 of Diamonds, 8 of Diamonds = 14
Dealer has 21... You LOSE.
[Won: 2. Tied: 0. Lost: 1] Play again (y/n)? y
DEALER shows: Queen of Diamonds
PLAYER hand: King of Diamonds, 9 of Diamonds = 19
[H]it or [S]tand? s
DEALER: Queen of Diamonds, 3 of Hearts = 13
+ 10 of Diamonds = 23 BUST!
You WIN.
[Won: 3. Tied: 0. Lost: 1] Play again (y/n)? y
DEALER shows: Jack of Diamonds
PLAYER hand: Queen of Hearts, 8 of Spades = 18
[H]it or [S]tand? s
DEALER: Jack of Diamonds, 3 of Spades = 13
+ 2 of Hearts = 15
+ Ace of Clubs = 16
+ 5 of Clubs = 21
You LOSE.
[Won: 3. Tied: 0. Lost: 2] Play again (y/n)? y
DEALER shows: 5 of Hearts
PLAYER hand: 6 of Hearts, 3 of Clubs = 9
[H]it or [S]tand? h
+ 8 of Hearts = 17
[H]it or [S]tand? h
+ 9 of Hearts = 26 BUST!
You LOSE.
[Won: 3. Tied: 0. Lost: 3] Play again (y/n)? n
Thanks for playing!
What to Submit
Upload your UsernameA18.java file to Tamarin. Remember that the file must include all the classes needed for your program to run.
Grading [10 points]
- 1 - Compiles + Coding Standards
- Your program compiles successfully (no errors). Your code follows Java coding standards.
- 0.5 - Helper classes
- Your game uses a
PlayingCard and Deck .
- 2.5 - BlackjackHand
- Your wrote a BlackjackHand class with the constructor (0.5) and methods--add (0.5) and getValue (1.5)--as specified above. The getValue method handles aces correctly.
- 0.5 - Instructions
- Your game explains the controls to the user (that is, how to hit, how to stand, and how to play again).
- 2 - Cards are displayed.
- You display all the cards that make up each hand (1.5). The dealer's face down card should not be shown until after the player cannot hit again, but must be shown by the end of the game (08 Nov 2009 Update: though see FAQ below) (0.5).
- 0.5 - Hands are totaled.
- The game should display the correct total value of each hand. It should tell the player the value of her hand each time she is asked to hit or stand. It should reveal the dealer's total at the end of the game.
- 1 - Follows the game rules
- Dealer hits on 16 or less, and stands on 17 or higher; doesn't let a player hit after exceeding 21 points, etc.
- 1.5 - Determines win, lose, or tie
- Correctly determines who won each game (1.0) and keeps running statistics (0.5) that are displayed each time the player is asked if they want to play again.
- 0.5 - No crashes
- Your program does not crash from invalid input or from running out of cards.
FAQs
- The BlackjackHand class is hard to use as it is now. For example, I can't print out what's in the hand!
- You're welcome (nay, encouraged!) to add additional methods to this class as you find helpful. Some ideas you may want to consider:
public void clear()
Empties this hand. (This is an alternative to having to create a new hand for each game.)
public String toString()
Returns a String representation of this hand (very handy for printing!).
public PlayingCard get(int index)
Returns the card at the given 0-based index within this hand.
public BlackjackHand(boolean isDealer)
Overloading the constructor. If isDealer is true, the first card is considered "face down" and is not included when toString() is called.
public void revealHoleCard()
Affects only a dealer hand (see constructor above). Later calls to toString() will include the full hand.
Note that you probably won't need all of these; or you may come up with ideas that aren't on this list.
- If a new BlackjackHand is supposed to be empty, what do I do in the constructor?
- It depends on how you declare you instance variable(s) on whether there's anything you need to do in the constructor. For example, if you delcare an ArrayList like this:
private ArrayList<PlayingCard> cards;
then in the constructor, you'll need to initialize this instance variable, like this:
this.cards = new ArrayList<PlayingCard>();
On the other hand, you could do all this at once, when you declare the instance variable:
private ArrayList<PlayingCard> cards = new ArrayList<PlayingCard>();
Now there's probably nothing left to do in the constructor. In this case, you can leave the constructor body empty. (Remember that if you don't write a constructor at all, Java will insert an empty, default constructor for you. So it may be possible to meet the assignment requirements without writing a BlackjackHand constructor at all!)
- I don't really like the method name
getValue() for the BlackjackHand . Something like getTotal() seems more natural to me. Can I change it?
- No, it's required that you have a getValue() method. However, after you write getValue(), you could do something like this:
public int getTotal() {
return this.getValue();
}
Now you have two methods that do the same thing--getValue and getTotal--but all the code is only in one of them. This way, you can meet the requirements but still have the method name you want, and you still only need to write (and maintain) the actual point-totalling code in one place.
- Do we have to show the dealer's hole card if the user gets a blackjack (but the dealer does not) or when the user busts on a hit?
- True, in these situations the dealer's face down card does not really impact the game outcome. So, no, you are not required to show the dealer's face down card in these cases. In short, if the game result depends on the dealer's hand value, you have to show what cards where in that hand.
- How do I correctly compute the total value of a hand?
- The tricky part about totalling the hand is determining the value of the Aces: sometimes they're worth 11 and sometimes they're only worth 1.
At first, it seems that you can just check if treating the value of an Ace as 11 will cause the hand to bust. If so, treat it as a 1 instead. For example, consider a 5+A+3+A hand. While going through the summing loop, the total for the hand would be: 5, then 16 (since 5+11 is less than 21), then 19, then 20 (since 19+11 would be more than 21, so treat this Ace as a 1 instead.)
However, this approach breaks down if it is not an Ace itself that would cause the hand to exceed 21. Consider this hand: 3+A+8. Here, your total would be 3, then 14, then 22. Here, the 8 incorrectly caused a bust. Instead, you should have gone back and converted the earlier Ace from an 11 to a 1.
In short, the problem is that you can't determine whether the Ace should be 1 or 11 at the time you're summing the values. Instead, you need to know the sum of the entire hand before you can determine which value each Ace should have.
What I recommend is just treating each Ace a 11 in your totalling loop. But, in the loop, also count how many Aces are in the hand. Then, after you have the total, if it's over 21 and you counted some Aces in the hand, keep subtracting 10 from the hand total (basically converting previously totalled Aces from 11 to 1) until you either run out of Aces to convert or the hand total drops below 21 again.
- How can I test if my BlackjackHand is calculating the correct total?
- Create a BlackjackHand and add some specific cards to it. Then call the getValue() method to see if it's correct. Here's one such test:
BlackjackHand test = new BlackjackHand();
test.add(new PlayingCard(5, PlayingCard.CLUBS));
test.add(new PlayingCard(PlayingCard.ACE, PlayingCard.CLUBS));
test.add(new PlayingCard(PlayingCard.ACE, PlayingCard.HEARTS));
test.add(new PlayingCard(8, PlayingCard.CLUBS));
System.out.println("Total [should be 15]: " + test.getValue());
- How do I use the BlackjackHand class?
- You need to create one hand for the player and one for the dealer. Then, each time you draw a card, you put it into one hand or the other. Here is an example of the start of a blackjack game:
Deck deck = new Deck(); //open a fresh, shuffled deck
BlackjackHand player = new BlackjackHand();
BlackjackHand dealer = new BlackjackHand();
//deal two cards to each hand
dealer.add(deck.draw());
player.add(deck.draw());
dealer.add(deck.draw());
player.add(deck.draw());
//see if either hand has a natural 21
if (dealer.getTotal() == 21 && player.getTotal() == 21) {
//game over: tie
}else if (dealer.getTotal() == 21) {
//game over: player lost
}else if (/* ??? */) {
//game over: player won
}
//player's move: keep asking whether to hit or stand
- If I put the cards into the hands, then how can I print them out?
- One way: write a
toString() method for your BlackjackHand. Then you can print the whole hand at once. (The first FAQ hints at some ways you could write your BlackjackHand so as not to include the first card of a dealer's hand in the toString() results. You could also overload the toString() method.)
Another way: each time you draw a card from the deck, assign it to a PlayingCard variable long enough to print it, and then put it into a hand.
A mix: If you wrote toString() to simply always display the whole hand, you could handle the dealer's initial hand "manually". Thus, the following example is a blending of two approaches: printing each card before adding it to the hand for the dealer and just using toString() for the player.
//deal and print dealer's initial hand
dealer.add(deck.draw()); //"face-down", so don't print
PlayingCard faceUp = deck.draw();
System.out.println("DEALER shows: " + faceUp);
dealer.add(faceUp);
//deal and print player's initial hand
player.add(deck.draw());
player.add(deck.draw());
System.out.print("PLAYER hand: " + player.toString());
Sysetm.out.println(" = " + player.getValue());
|