Solution for
Programming Exercise 6.8
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to
the following exercise from this on-line
Java textbook.
Exercise 6.8:Write a Blackjack applet that lets the
user play a game of Blackjack, with the computer as the dealer. The applet should draw
the user's cards and the dealer's cards, just as was done for the graphical HighLow
card game in Section 6.6. You can use the source code for
that game, HighLowGUI.java, for some ideas about
how to write your Blackjack game. The structures of the HighLow applet and the Blackjack
applet are very similar. You will certainly want to use the drawCard() method
from that applet.
You can find a description
of the game of Blackjack in Exercise 5.5.
Add the following rule to that description: If a player takes five cards without
going over 21, that player wins immediately. This rule is used in some casinos. For your
applet, it means that you only have to allow room for five cards. You should assume that
your applet is just wide enough to show five cards, and that it is tall enough to show
the user's hand and the dealer's hand.
Note that the design of a GUI Blackjack game is very different from the design of the
text-oriented program that you wrote for Exercise 5.5. The user should play the
game by clicking on "Hit" and "Stand" buttons. There should be a "New Game" button that
can be used to start another game after one game ends. You have to decide what happens
when each of these buttons is pressed. You don't have much chance of getting this right
unless you think in terms of the states that the game can be in and how the state can change.
Your program will need the
classes defined in Card.java, Hand.java,
BlackjackHand.java, and Deck.java.
Here is a working version of the applet:
Discussion
The main applet class for this exercise is identical to the applet class
for the HighLowGUI game,
except that it uses a BlackjackCavas instead of a
HighLowCanvas. So, the real work of this program is writing the
BlackjackCanvas class. All the programming for the game
is in this class.
In the HighLow game, there is one "hand," which holds all the cards that
have been dealt. Blackjack is a two-player game, so there are two hands, one for
the player and one for the dealer. These hands are of type BlackjackHand.
So, we need instance variables
BlackjackHand dealerHand; // The dealer's cards.
BlackjackHand playerHand; // The user's cards.
We also need a deck and a boolean-valued instance variable,
gameInProgress, to keep track of the two basic states of the game:
Is a game in progress, or are we between games. Finally, there
is a message variable, which holds the string that is shown
at the bottom of the game board.
The paintComponent() method uses the information in the dealerHand, playerHand,
message, and gameInProgress variables. The reason it needs to
look at the gameInProgress variable is that when a game is in progress, one
of the dealer's cards is drawn face down, so the user can't see it. Once the game is
over, the card is drawn face up so the user can see what the dealer was holding.
Note that there is no point in the program where I say, "turn the dealer's
first card face up"! It happens automatically because the state of the game
changes, and the paintComponent() method checks the state when it draws the applet.
If the game is over, the card is face up. This is nice example of state-machine
thinking.
Note that writing the paintComponent() method required some calculation.
The cards are 80 pixels wide and 100 pixels tall. Horizontally, there is a gap
of 10 pixels between cards, and there are gaps of 10 pixels between the
cards and the left and right edges. (The total width needed for the applet,
466, allows for five 80-pixel cards, six 10-pixel gaps, and two 3-pixel borders
along the edges: 5*80 + 6*10 + 2*3 = 466.) The N-th card, counting from 0,
has its left edge at 10+90*N. It might be easier to see this as
10+80*N+10*N, 10 pixels on the left plus N 80-pixel cards,
plus N 10-pixel gaps between cards. Vertically, I allow 30 pixels
for each string, "Dealer's Cards" and "Your Cards". This puts the
top of the first row of cards at y=30. Allowing 100 pixels for
that row of cards and 30 pixels for the string "Your Cards", the top of
the second row of cards is at 160. Given all this, you should be able
to understand the paintComponent() method. Allowing 100 pixels for the
second row of cards and 30 pixels for the message at the bottom of the
board, we need a height of 290 pixels for the canvas. I set the overall
height of the applet to 346 to allow 6 pixels for a border and 50 pixels for
the panel that contains the buttons. (This might be too much, but the
sizes of buttons can vary from one platform to another, and I want to be safe.)
In this GUI version of Blackjack, things happen when the user clicks
the "Hit", "Stand", and "New Game" buttons. The applet handles these events
by calling the routines doHit(), doStand(), and doNewGame().
Each of these routines has responsibility for one part of the game of Blackjack.
Note that each routine starts by checking the state of the game to make sure
that it is legal to call the routine at this time. If gameInProgress
is true, the user can legally click "Hit" or "Stand". If gameInProgress
is false, the user can legally click "New Game". If the user made an illegal move,
an error message is stored in the message variable, and repaint() is called
so the user will see the new message. This is similar to the way the three buttons
in HighLowGUI are handled.
The doNewGame() routine has to set up a new game. This means
creating the deck and hands, shuffling the deck and dealing two cards into
each hand. At this point, the first time I wrote the game, I just set
gameInProgress to true, to record the fact that the state of the
game has changed. Later, I realized that the doNewGame() routine
also has to check whether one of the players has Blackjack, since there is
really no other place where this can be done. If one of the players has
Blackjack, the game is over, so gameIsProgress has to be false,
and the only action that the user can take at that point is to
click the "New Game" button again. (Note that the doNewGame()
routine is also called by the constructor of the BlackjackCanvas
class. This sets up the first game, when the applet is first created,
so the user doesn't have to click on the "New Game" button to start
the first game.)
When the user clicks "Hit", if the game is in progress, we deal a card
into the user's hand. At this point, the state of the game might have
changed. If the user has over 21, the user loses and the game is over.
If the user has taken 5 cards without going over 21, the user wins and the
game is over. In either of these cases, the value of the state variable
gameInProgress becomes false. Otherwise, gameInProgress
retains the value true, and the game will continue. Since
gameInProgress is true, the user again has the choice of
clicking "Hit" or "Stand". (Note that there is no loop in the program
that says "while the user continues to hit." The progress of the game is driven
by events.)
Finally, when the user clicks "Stand", the game is definitely over,
so gameInProgrss is set to false. However, before the game can
end, the dealer gets to draw cards and a winner is determined. This
all has to be done in the doStand() routine. Then, the
applet is repainted to show the final state of the game.
The Solution
/*
In this applet, the user plays a game of Blackjack. The
computer acts as the dealer. The user plays by clicking
"Hit!" and "Stand!" buttons.
The programming of this applet assumes that the applet is
set up to be about 466 pixels wide and about 346 pixels high.
That width is just big enough to show 2 rows of 5 cards.
The height is probably a little bigger than necessary,
to allow for variations in the size of buttons from one platform
to another.
*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BlackjackGUI extends JApplet {
public void init() {
// The init() method creates components and lays out the applet.
// A BlackjackCanvas occupies the CENTER position of the layout.
// On the bottom is a panel that holds three buttons. The
// BlackjackCanvas object listens for events from the buttons
// and does all the real work of the program.
setBackground( new Color(130,50,40) );
BlackjackCanvas board = new BlackjackCanvas();
getContentPane().add(board, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground( new Color(220,200,180) );
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
JButton hit = new JButton( "Hit!" );
hit.addActionListener(board);
buttonPanel.add(hit);
JButton stand = new JButton( "Stand!" );
stand.addActionListener(board);
buttonPanel.add(stand);
JButton newGame = new JButton( "New Game" );
newGame.addActionListener(board);
buttonPanel.add(newGame);
} // end init()
public Insets getInsets() {
// Specify how much space to leave between the edges of
// the applet and the components it contains. The background
// color shows through in this border.
return new Insets(3,3,3,3);
}
// --- The remainder of this class consists of a nested class ---
class BlackjackCanvas extends JPanel implements ActionListener {
// A nested class that displays the card game and does all the work
// of keeping track of the state and responding to user events.
Deck deck; // A deck of cards to be used in the game.
BlackjackHand dealerHand; // Hand containing the dealer's cards.
BlackjackHand playerHand; // Hand containing the user's cards.
String message; // A message drawn on the canvas, which changes
// to reflect the state of the game.
boolean gameInProgress; // Set to true when a game begins and to false
// when the game ends.
Font bigFont; // Font that will be used to display the message.
Font smallFont; // Font that will be used to draw the cards.
BlackjackCanvas() {
// Constructor. Creates fonts and starts the first game.
setBackground( new Color(0,120,0) );
smallFont = new Font("SansSerif", Font.PLAIN, 12);
bigFont = new Font("Serif", Font.BOLD, 14);
doNewGame();
}
public void actionPerformed(ActionEvent evt) {
// Respond when the user clicks on a button by calling
// the appropriate procedure. Note that the canvas is
// registered as a listener in the BlackjackGUI class.
String command = evt.getActionCommand();
if (command.equals("Hit!"))
doHit();
else if (command.equals("Stand!"))
doStand();
else if (command.equals("New Game"))
doNewGame();
}
void doHit() {
// This method is called when the user clicks the "Hit!" button.
// First check that a game is actually in progress. If not, give
// an error message and exit. Otherwise, give the user a card.
// The game can end at this point if the user goes over 21 or
// if the user has taken 5 cards without going over 21.
if (gameInProgress == false) {
message = "Click \"New Game\" to start a new game.";
repaint();
return;
}
playerHand.addCard( deck.dealCard() );
if ( playerHand.getBlackjackValue() > 21 ) {
message = "You've busted! Sorry, you lose.";
gameInProgress = false;
}
else if (playerHand.getCardCount() == 5) {
message = "You win by taking 5 cards without going over 21.";
gameInProgress = false;
}
else {
message = "You have " + playerHand.getBlackjackValue() + ". Hit or Stand?";
}
repaint();
}
void doStand() {
// This method is called when the user clicks the "Stand!" button.
// Check whether a game is actually in progress. If it is,
// the game ends. The dealer takes cards until either the
// dealer has 5 cards or more than 16 points. Then the
// winner of the game is determined.
if (gameInProgress == false) {
message = "Click \"New Game\" to start a new game.";
repaint();
return;
}
gameInProgress = false;
while (dealerHand.getBlackjackValue() <= 16 && dealerHand.getCardCount() < 5)
dealerHand.addCard( deck.dealCard() );
if (dealerHand.getBlackjackValue() > 21)
message = "You win! Dealer has busted with " + dealerHand.getBlackjackValue() + ".";
else if (dealerHand.getCardCount() == 5)
message = "Sorry, you lose. Dealer took 5 cards without going over 21.";
else if (dealerHand.getBlackjackValue() > playerHand.getBlackjackValue())
message = "Sorry, you lose, " + dealerHand.getBlackjackValue()
+ " to " + playerHand.getBlackjackValue() + ".";
else if (dealerHand.getBlackjackValue() == playerHand.getBlackjackValue())
message = "Sorry, you lose. Dealer wins on a tie.";
else
message = "You win, " + playerHand.getBlackjackValue()
+ " to " + dealerHand.getBlackjackValue() + "!";
repaint();
}
void doNewGame() {
// Called by the constructor, and called by actionPerformed() if
// the use clicks the "New Game" button. Start a new game.
// Deal two cards to each player. The game might end right then
// if one of the players had blackjack. Otherwise, gameInProgress
// is set to true and the game begins.
if (gameInProgress) {
// If the current game is not over, it is an error to try
// to start a new game.
message = "You still have to finish this game!";
repaint();
return;
}
deck = new Deck(); // Create the deck and hands to use for this game.
dealerHand = new BlackjackHand();
playerHand = new BlackjackHand();
deck.shuffle();
dealerHand.addCard( deck.dealCard() ); // Deal two cards to each player.
dealerHand.addCard( deck.dealCard() );
playerHand.addCard( deck.dealCard() );
playerHand.addCard( deck.dealCard() );
if (dealerHand.getBlackjackValue() == 21) {
message = "Sorry, you lose. Dealer has Blackjack.";
gameInProgress = false;
}
else if (playerHand.getBlackjackValue() == 21) {
message = "You win! You have Blackjack.";
gameInProgress = false;
}
else {
message = "You have " + playerHand.getBlackjackValue() + ". Hit or stand?";
gameInProgress = true;
}
repaint();
} // end newGame();
public void paintComponent(Graphics g) {
// The paint method shows the message at the bottom of the
// canvas, and it draws all of the dealt cards spread out
// across the canvas.
super.paintComponent(g); // fill with background color.
g.setFont(bigFont);
g.setColor(Color.green);
g.drawString(message, 10, getSize().height - 10);
// Draw labels for the two sets of cards.
g.drawString("Dealer's Cards:", 10, 23);
g.drawString("Your Cards:", 10, 153);
// Draw dealer's cards. Draw first card face down if
// the game is still in progress, It will be revealed
// when the game ends.
g.setFont(smallFont);
if (gameInProgress)
drawCard(g, null, 10, 30);
else
drawCard(g, dealerHand.getCard(0), 10, 30);
for (int i = 1; i < dealerHand.getCardCount(); i++)
drawCard(g, dealerHand.getCard(i), 10 + i * 90, 30);
// Draw the user's cards.
for (int i = 0; i < playerHand.getCardCount(); i++)
drawCard(g, playerHand.getCard(i), 10 + i * 90, 160);
} // end paint();
void drawCard(Graphics g, Card card, int x, int y) {
// Draws a card as a 80 by 100 rectangle with
// upper left corner at (x,y). The card is drawn
// in the graphics context g. If card is null, then
// a face-down card is drawn. (The cards are
// rather primitive.)
if (card == null) {
// Draw a face-down card
g.setColor(Color.blue);
g.fillRect(x,y,80,100);
g.setColor(Color.white);
g.drawRect(x+3,y+3,73,93);
g.drawRect(x+4,y+4,71,91);
}
else {
g.setColor(Color.white);
g.fillRect(x,y,80,100);
g.setColor(Color.gray);
g.drawRect(x,y,79,99);
g.drawRect(x+1,y+1,77,97);
if (card.getSuit() == Card.DIAMONDS || card.getSuit() == Card.HEARTS)
g.setColor(Color.red);
else
g.setColor(Color.black);
g.drawString(card.getValueAsString(), x + 10, y + 30);
g.drawString("of", x+ 10, y + 50);
g.drawString(card.getSuitAsString(), x + 10, y + 70);
}
} // end drawCard()
} // end nested class BlackjackCanvas
} // end class BlackjackGUI
[ Exercises
| Chapter Index
| Main Index
]