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 JTextField to the strip of controls along the bottom of the applet. The user can enter the bet in this JTextField. 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 JTextField 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 JTextField uneditable while the game is in progress. If betInput is the JTextField, you can make it editable and uneditable by the user with the commands betAmount.setEditable(true) and betAmount.setEditable(false).

In the paintComponent() 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. So, in the constructor for the canvas class, you should not call doNewGame(). You might want to display a message such as "Welcome to Blackjack" before the first game starts.


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 JTextField 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 JTextField is created with the command "betInput = new JTextField("10",5);". 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 JTextField is designed to hold. The preferred size of the JTextField is computed based on this number of characters. If the JTextField 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 JTextField 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 JTextField will be sized to fit its contents, "10", and it will look too small.

The BlackjackCanvas class contains two new 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 it did in the old program.

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. A call to one of these routines eliminates one of the calls to setGameinProgress(false) in the old program.

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 BlackjackCanvas 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 paintComponent() 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 paintComponent() 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 paintComponent() 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.
       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 BlackjackGUI3 extends JApplet {
    
       /* Declare 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 BlackjackCanvas. */
          
       JButton hit, stand, newGame;
       
       JTextField betInput;  // A JTextField to hold the user's bet.

       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) );
          
          hit = new JButton( "Hit!" );
          stand = new JButton( "Stand!" );
          newGame = new JButton( "New Game" );
             
          betInput = new JTextField("10",5);

          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);
          
          hit.addActionListener(board);
          buttonPanel.add(hit);
          
          stand.addActionListener(board);
          buttonPanel.add(stand);
          
          newGame.addActionListener(board);
          buttonPanel.add(newGame);
          
          /* Add the bet input box to the panel along with a label. */
    
          buttonPanel.add( new JLabel( " 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);
       }
       

       // --- 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.

          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.


          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);
             usersMoney = 100;
             message = "Welcome to Blackjack!  You start with $100.";
             setGameInProgress(false);
          }


          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();
          }


          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.
             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);
             }
          }

          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 should be impossible, since the Hit button is disabled
                    // when the game is not is progress, but it doesn't hurt to check.
                message = "Click \"New 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 16 points.  Then the 
                  // winner of the game is determined.
             if (gameInProgress == false) {
                message = "Click \"New 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 "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) {
                     // This should not 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;
             }

             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 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);

             // 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);
                
             // Show the message at the bottom of the panel.

             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 paintComponent 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 paintComponent();


          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 HighLowGUI3
    
    

[ Exercises | Chapter Index | Main Index ]