Solution for
Programming Exercise 7.5
THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.
Exercise 7.5: Building on your solution to the preceding exercise, make it possible for the user to place bets on the Blackjack game. When the applet starts, give the user $100. Add a TextField to the strip of controls along the bottom of the applet. The user can enter the bet in this TextField. When the game begins, check the amount of the bet. You should do this when the game begins, not when it ends, because several errors can occur: The contents of the TextField might not be a legal number. The bet that the user places might be more money than the user has, or it might be <= 0. You should detect these errors and show an error message instead of starting the game. The user's bet should be an integral number of dollars. You can convert the user's input into an integer, and check for illegal, non-numeric input, with a try...catch statement of the form
try { betAmount = Integer.parseInt( betInput.getText() ); } catch (NumberFormatException e) { . . . // The input is not a number. // Respond by showing an error message and // exiting from the doNewGame() method. }It would be a good idea to make the TextField uneditable while the game is in progress. If betInput is the TextField, you can make it editable and uneditable by the user with the commands betAmount.setEditable(true) and betAmount.setEditable(false).
In the paint() method, you should include commands to display the amount of money that the user has left.
There is one other thing to think about: The applet should not start a new game when it is first created. The user should have a chance to set a bet amount before the game starts. In the constructor for the canvas class, you should not call doNewGame(). You might want to start the game with the message "Welcome to Blackjack".
Discussion
Here's my applet:
This applet builds on the solution of the preceding exercise. The text input box where the user enters his bet is treated much like the buttons in the preceding exercise. A reference to the input box is stored in an instance variable named betInput in the applet class. The applet class creates the TextField and adds it to the applet. The canvas class has access to the variable betInput since it is a nested class in the applet class. The setGameInProgress routine has charge of setting the text field to be editable or non-editable. It does this at the same time it sets the buttons to be enabled or disabled.
The TextField is created with the command "betInput = new TextField("10",6);". The first parameter in the constructor specifies the initial content of the text input box. This is meant as a reasonable value for the bet, but the user can change it if he wants to. The second parameter is important. It specifies the number of characters that the TextField is designed to hold. The preferred size of the TextField is computed based on this number of characters. If the TextField were being used in a context where it would be stretched to fit the available size, such as in a GridLayout, the preferred size would not be important. However, in this applet, the TextField is used with a FlowLayout, and it will appear at exactly its preferred size. If you leave out the second parameter in the constructor, the TextField will be sized to fit its contents, "10", and it will look too small.
The BJCanvas class contains two instance variables for managing the user's bets. One variable, usersMoney, records the amount of money that the user has. The other, betAmount, records the amount of the user's bet on the current game. The value of usersMoney is initialized to 100 in the constructor. At the end of a game, if the user wins, the betAmount is added to the user's money, and if the user loses, the betAmount is subtracted from the user's money. We have to decide what happens if the user runs out of money. One possibility would be to shut the game down, but that seems drastic since it's only play money anyway. So, if the value of usersMoney drops to zero, I give the user another $100 at the start of the next game.
The doNewGame() method starts by checking if usersMoney is 0. Then it checks the user's bet. It gets the bet amount using the code given in the exercise. If it gets past that, it checks whether the bet amount is legal. It must be between 1 and the amount of money that the user has available:
/* If user has run out of money, give the user a new $100 */ if (usersMoney == 0) usersMoney = 100; /* Get the user's betAmount from the bet input box. If an error is discovered, set an error message and exit the doNewGame() routine. */ try { betAmount = Integer.parseInt( betInput.getText() ); } catch (NumberFormatException e) { message = "The bet you entered is not a legal number!"; repaint(); return; } if (betAmount < 1) { message = "You have to bet at least $1."; repaint(); return; } if (betAmount > usersMoney) { message = "You don't have $" + betAmount + ". Enter a smaller bet."; repaint(); return; }After this, the doNewGame() routine just starts the game as usual.
When the game ends, the bet amount has to be adjusted. There are many points in the source code where the game ends. Rather than having the same lines of code at each point, I defined two new methods:
private void userWon(String str) { // Call this for convenience when the user wins. // Str is the message to display to the user. message = str; usersMoney += betAmount; setGameInProgress(false); } private void userLost(String str) { // Call this for convenience when the user loses. // Str is the message to display to the user. message = str; usersMoney -= betAmount; setGameInProgress(false); }Whenever something happens that causes the game to end, I call one of these routines. The parameter is the message that I want to display to the user.
One of the tricky parts of this assignment is to arrange things so that a game does not start as soon as the applet is created. It's no problem to take the "doNewGame();" statement out of the constructor in the BJCanvas class. Some initialization has to be done there instead:
usersMoney = 100; message = "Welcome to Blackjack! You start with $100."; setGameInProgress(false);However, I ran into some more NullPointerExceptions because the paint() method assumed that two hands of cards exist. If the dealerHand and playerHand variables are null, there is a problem. This just required some modifications in the paint() method to deal with this possibility. I decided to draw two face down cards for each player if the hands don't exist. (An alternative would be to have some really fancy introductory screen.)
I also added some code to the paint() method to display the user's money amount. To accommodate this, this version of the applet has to be about 30 pixels taller than the previous versions.
The Solution
Significant changes from BlackjackGUI2 are shown in red.
/* 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 user can make bets on the game. Initially, the user has $100. If the user runs out of money, the user will get another $100. Note that the bet amount cannot be edited when the game is in progress. The programming of this applet assumes that the applet is set up to be about 466 pixels wide and about 376 pixels high. That width is just big enough to show 2 rows of 5 cards, and some messages 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 java.applet.*; public class BlackjackGUI3 extends Applet { /* Create the buttons that the user clicks to control the game. These buttons are set up in the init() method and are used in the nested class BJCanvas. Also create a TextField to hold the user's bet. */ Button hit, stand, newGame; TextField betInput; public void init() { // The init() method lays out the applet using a BorderLayout. // A BJCanvas occupies the CENTER position of the layout. // On the bottom is a panel that holds three buttons. The // BJCanvas object listens for ActionEvents from the buttons // and does all the real work of the program. hit = new Button("Hit!"); // Create buttons TextField first to avoid stand = new Button("Stand!"); // a NullPointerException when newGame = new Button("Start Game"); // the BJCanvas constructor is called. betInput = new TextField("10",6); setBackground( new Color(130,50,40) ); setLayout( new BorderLayout(3,3) ); BJCanvas board = new BJCanvas(); // (uses nested class BJCanvas) add(board, BorderLayout.CENTER); Panel buttonPanel = new Panel(); // A panel to hold the controls. buttonPanel.setBackground( new Color(220,200,180) ); add(buttonPanel, BorderLayout.SOUTH); /* Add the buttons to the panel and set the canvas to listen for clicks on the buttons. */ hit.addActionListener(board); hit.setBackground(Color.lightGray); buttonPanel.add(hit); stand.addActionListener(board); stand.setBackground(Color.lightGray); buttonPanel.add(stand); newGame.addActionListener(board); newGame.setBackground(Color.lightGray); buttonPanel.add(newGame); /* Add the bet input box to the panel along with a label. */ buttonPanel.add( new Label( " Your bet:" ) ); betInput.setBackground(Color.white); buttonPanel.add(betInput); } // 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); } class BJCanvas extends Canvas 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. int usersMoney; // The amount of money the user has. int betAmount; // The amount user has bet on the current game. // This amount is recorded at the start of game. BJCanvas() { // Constructor. Creates fonts and shows an initial greeting. setBackground( new Color(0,120,0) ); smallFont = new Font("SansSerif", Font.PLAIN, 12); bigFont = new Font("Serif", Font.BOLD, 14); usersMoney = 100; message = "Welcome to Blackjack! You start with $100."; setGameInProgress(false); } public void setGameInProgress(boolean inProgress) { // This method should be called whenever the value of // the gameInProgress variable is changed. It changes // the value of the variable and also enables/disables // the buttons to reflect the state of the game. The // betInput TextField cannot be edited while the game // is in progress. gameInProgress = inProgress; if (gameInProgress) { hit.setEnabled(true); stand.setEnabled(true); newGame.setEnabled(false); betInput.setEditable(false); } else { hit.setEnabled(false); stand.setEnabled(false); newGame.setEnabled(true); betInput.setEditable(true); } } 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("Start Game")) doNewGame(); } private void userWon(String str) { // Call this for convenience when the user wins. // Str is the message to display to the user. message = str; usersMoney += betAmount; setGameInProgress(false); } private void userLost(String str) { // Call this for convenience when the user loses. // Str is the message to display to the user. message = str; usersMoney -= betAmount; setGameInProgress(false); } 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) { // This shouldn't be possible because the Hit button // is disabled when it is illegal to use it. However, // it's better to be safe. message = "Click \"Start Game\" to start a new game."; repaint(); return; } playerHand.addCard( deck.dealCard() ); if ( playerHand.getBlackjackValue() > 21 ) { userLost("You've busted! Sorry, you lose."); } else if (playerHand.getCardCount() == 5) { userWon("You win by taking 5 cards without going over 21."); } 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 21 points. Then the // winner of the game is determined. if (gameInProgress == false) { message = "Click \"Start Game\" to start a new game."; repaint(); return; } while (dealerHand.getBlackjackValue() <= 16 && dealerHand.getCardCount() < 5) dealerHand.addCard( deck.dealCard() ); if (dealerHand.getBlackjackValue() > 21) userWon("You win! Dealer has busted with " + dealerHand.getBlackjackValue() + "."); else if (dealerHand.getCardCount() == 5) userLost("Sorry, you lose. Dealer took 5 cards without going over 21."); else if (dealerHand.getBlackjackValue() > playerHand.getBlackjackValue()) userLost("Sorry, you lose, " + dealerHand.getBlackjackValue() + " to " + playerHand.getBlackjackValue() + "."); else if (dealerHand.getBlackjackValue() == playerHand.getBlackjackValue()) userLost("Sorry, you lose. Dealer wins on a tie."); else userWon("You win, " + playerHand.getBlackjackValue() + " to " + dealerHand.getBlackjackValue() + "!"); repaint(); } void doNewGame() { // Called by the constructor, and called by actionPerformed() if // the use clicks the "Start Game" button. Start a new game. // First, get the user's bet. (Exit if the input is not legal.) // 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) { // This shouldn't be possible. message = "You still have to finish this game!"; repaint(); return; } /* If user has run out of money, give the user a new $100 */ if (usersMoney == 0) usersMoney = 100; /* Get the user's betAmount from the bet input box. If an error is discovered, set an error message and exit the doNewGame() routine. */ try { betAmount = Integer.parseInt( betInput.getText() ); } catch (NumberFormatException e) { message = "The bet you entered is not a legal number!"; repaint(); return; } if (betAmount < 1) { message = "You have to bet at least $1."; repaint(); return; } if (betAmount > usersMoney) { message = "You don't have $" + betAmount + ". Enter a smaller bet."; repaint(); return; } /* Start a new game by creating a new deck and new hands and dealing two cards to each player. */ 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) { userLost("Sorry, you lose. Dealer has Blackjack."); } else if (playerHand.getBlackjackValue() == 21) { userWon("You win! You have Blackjack."); } else { message = "You have " + playerHand.getBlackjackValue() + ". Hit or stand?"; setGameInProgress(true); } repaint(); } // end newGame(); public void paint(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. g.setFont(bigFont); // Set font to use for messages. // Tell the user how much money he/she has. */ g.setColor(Color.yellow); if (usersMoney == 0) g.drawString("You're out of money! I'll give you another $100.", 10, getSize().height - 35); else g.drawString("You have $" + usersMoney, 10, getSize().height - 35); /* Print the message at the bottom of the screen. */ 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); /* Before the first game starts, dealerHand is null. We can't draw any cards if this is true, so draw 4 face-down cards and exit from the paint method. */ if (dealerHand == null) { drawCard(g, null, 10, 30); drawCard(g, null, 120, 30); drawCard(g, null, 10, 160); drawCard(g, null, 120, 160); return; } // 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 BlackjackGUI3
[ Exercises | Chapter Index | Main Index ]