CPSC 124, Fall 2001
Lab 11: Components and Layout

The goal of this lab is to create an applet like the one shown below. This applet lets the user play the dice game "Craps," with betting. From the programming point of view, it illustrates the use of LayoutManagers to lay out an applet. It shows how to use a TextField to get input from the user. And, of course, it gives us another example of "state machine" thinking in programming. To use the applet, the user enters the amount of his or her bet in the text input box, and clicks the "Place Bet" button. Then the user clicks the "Roll" button one or more times to play the game. On the first roll, the user wins the game if the roll is seven or eleven, and the user loses if the roll is two, three, or twelve. Any other value on the first roll becomes the "point" value, and the user keeps rolling, trying to "make the point." At this stage, if the user rolls a seven, the user loses. If the user rolls the point again before rolling a seven, the user wins. At the end of the game, the amount of the user's bet is added to or subtracted from the user's money, depending on whether the user won or lost.

Your program is due in class next Wednesday, November 14. Please turn in a printout of your applet source code, and add the applet to your Web site. You must put the applet on your Web page, since this applet is more complicated than usual. I won't be able to be sure that it works unless I can look at the applet in action.

As usual, the files for today's lab can be found in the directory /home/cs124/lab11, which you should copy into your account.


Creating Components

This is fairly complicated project. If you were doing it on your own, it might even make a reasonable final project. In the lab, I will walk you through the process of developing the program, and give you a lot of the code. The file Craps.java can be used as a starting point for the applet. It already defines all the instance variables that you need for the program. It includes an actionPerformed() method that you will not have to modify. And it has four empty methods -- init(), doPlaceBet(), doFirstRoll(), and doRollForPoint() -- that you have to fill in. The applet has no paint() method and does not need one. All the components in the applet take care of drawing themselves.

We begin with the init() method, which has to initialize the instance variables and lay out the applet.

Among the instance variables are seven variables that represent visual components in the applet. These include the dice, the two buttons, the text input, and three Labels that are used to show messages to the user. One of the three Labels is empty when the applet starts. There are two other Labels in the applet -- the red text at the top of the applet and the word "Amount:" at the bottom -- but these don't have to be instance variables because they don't change after the applet is initialized. The first step in the init() method is to create the components. You have to call a constructor for each object. For example: the Dice class has a constructor with no parameters, so you can simply create the dice with the statement

            dice = new Dice();

The constructor for a button takes the label on the button as a parameter. We also want to set the applet to listen for clicks on the button. So, betButton can be created with:

            betButton = new Button("Place Bet");
            betButton.addActionListener(this);

Do the same for rollButton. In addition, rollButton should be disabled when the applet starts, so the user can't click on it before placing a bet. This is done with the setEnabled() method:

            rollButton.setEnabled(false);

The TextField, betInput, should be initialized to contain a reasonable default input, such as "10". I also want to set the TextField to display a certain number of characters. This turns out to be essential in this particular layout, since if it is not set the TextField will have width zero, at least on some platforms, and will not appear at all! (This depends on the Layout manager used. In some positions in some layouts, a TextField will expand to fill the available space. In this applet, it will simply appear at its "preferred size", which depends on the number of characters that it is designed to display.) The default value and the number of characters are given as parameters to the constructor:

            betInput = new TextField("10",5);

The message labels, message1, message2, and message3, should be constructed with the strings that they display. The first message has the initial value "You have $100", so it can be constructed with

            message1 = new Label("You have $100");

The second message label initially says "Place your bet", and the third is initially the empty string. You will need the two additional labels that do not change after the applet is created. These can be local variables in the init() method. For the label at the top of the applet, I wanted to text to be centered in the label. For this, I used another form of the constructor that specifies the position of the text as a second parameter. You can create the labels as follows (but use your own name instead of "Fred"):

            Label tempLabel1 = new Label("Fred's Craps Game", Label.CENTER);
            Label tempLabel2 = new Label("Amount:");

Finally, you should assign an initial amount of money, such as 100, to the instance variable named money. This variable will represent the amount of money that the user currently has.

After you have initialized the variables, you might want to change some colors. You can set the foreground and background color of any component (except that you are stuck with the colors of the dice, since that class ignores the foreground and background color). When you don't set the color of a component, it has the same color as the container to which it is added. If you set a foreground color for the applet as a whole, it should be used for all the text in the applet. By the time we are finished, the applet background will only be visible around the edges of the applet and between components. You should probably set it to some dark color, such as setBackground(Color.gray). You could set the background color of tempLabel1, for example, with tempLabel1.setBackground(Color.white). (Note the difference between setting the background color of the applet and setting the background color of a component. For the component, you have to specify the component. For the applet, you just say "setBackground(c)" without specifying a component. Of course, this is actually equivalent to this.setBackground(c) and refers to the applet itself.)


Layout

Still continuing with the init() method, it's time to lay out the components in the applet. The layout uses two Panels to help organize all the components. One Panel holds the four components along the bottom of the applet. This Panel uses a default "FlowLayout", so the components in it just line up next to each other, using their preferred sizes. The other Panel holds the four labels the fill the upper right section of the applet. This Panel uses a GridLayout with four rows and one column, so the four labels in it each take up one-quarter of the Panel.

You should create the two panels, set the layout manager for the second panel only, and add the components to the panels. You probably want to set the background color of the panel, which will be used for the components that it contains unless they have their own assigned background colors. For the second panel, this looks like:

            Panel bottomPanel = new Panel();
            bottomPanel.setBackground( new Color(230,230,230) );
            bottomPanel.add(rollButton);
            bottomPanel.add(betButton);
            bottomPanel.add(tempLabel2);
            bottomPanel.add(betInput);

The other panel is similar, except that you have to use something like "topPanel.setLayout(new GridLayout(4,1))" to change its layout manager. Now, you are ready to lay out the applet itself. The applet uses a BorderLayout for its main layout. Since this is different from the default layout manager, you have to set the layout manager of the applet itself:

            setLayout( new BorderLayout(3,3) );

You will add the two panels and the dice to the applet. When you add components to a border layout, you have to specify which position the component will occupy, using one of the constants BorderLayout.CENTER, BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST, or BorderLayout.WEST. For example, the dice go in the WEST position:

            add(dice, BorderLayout.WEST);

Put the bottom panel in the SOUTH position and the other panel in the CENTER position. (A BorderLayout should always have something in the CENTER. The other positions are optional.)

If you've gotten all this right, you should be able to compile and run the applet, and you should see all the components. Then, all you have to do is program what happens when the user clicks on a button! (The other user interaction -- changing the amount of the bet -- is handled automatically for you by the TextField.)

Before preceding, make sure that your applet compiles without error and that it is set up properly.


Managing Events and State

Like most event-driven programs, this applet is a state machine. That is, the response of the applet to user actions depends on the state of the applet, and the state can change when user actions are processed. I've decided that a Craps game can be in one of three major states:

  1. Waiting for a bet: Before the game can actually begin, the user must place a bet by adjusting the contents of the text input box (if desired) and clicking the "Place Bets" button.
  2. Waiting for the first roll: The first roll of the game is handled very differently from the rolls that might come after it, so I decided to make this into a separate state.
  3. Rolling for a point: If the user does not roll 2, 3, 7, 11, or 12 on the first roll, then the user's roll becomes the "point" value, and the user keeps rolling. This is the state that is in effect when the user is trying to roll some particular point value.

I declared constants WAIT_FOR_BET, WAIT_FOR_FIRST_ROLL, and ROLLING_FOR_POINT to represent the three possible states. There is also a variable named state which records the state by holding one of these three constants. When the user clicks a button, the state is used to help decide what action to take. This is already done in the actionPerformed method that I wrote for you. This method calls one of the three methods doPlaceBet(), doFirstRoll(), or doRollForPoint(), depending on the user's action and the state. You just need to fill in the inside of these three methods. In each case, you need to think about what should happen when the method is called and how the state can change. You might need to change the value of the state variable. You might need to enable or disable the buttons to properly reflect the state of the game. You will want to set the values of the message labels message1, message2, and message3 to tell the user what is going on. The values of the other instance variables, bet, point, and money, might also change.


Getting a Number from a TextField

We turn to the doPlaceBet() method. The first thing you need to do in this method is get the user's bet from the TextField and make sure that it is a legal value. Unfortunately, Java only provides a method for getting a string from a TextField:

            String betString = betInput.getText();

However, it also provides a way to convert a string to a number: Integer.parseInt(string). When you use this method, there is a possibility that the string is not a legal integer. In that case, an error of type NumberFormatException occurs. If you want your program to behave nicely, you should "catch" this error and report it to the user. This requires exception handling, a technique that is not covered until Chapter 9 of the text. Here is some code that you can use in doPlaceBet(). The meaning shouldn't be hard to figure out, but you don't have to fully understand it:

            String betString = betInput.getText();
            try {
               bet = Integer.parseInt(betString);
            }
            catch (NumberFormatException e) {
               message3.setText("Bet amount is not a number!");
               return; // Exit doPlaceBet() without further processing.
            }

If you get past this block of code, you have a number, bet, representing the user's desired bet. You should still test that this number is greater than 0 and less than or equal to the amount of money that the user has. If the bet amount is bad, you can post an error message and exit from the method. For example:

            if (bet <= 0) {
               message3.setText("Amount must be a positive number!");
               return;
            }

If the user's bet is valid, then the game can begin. The state should change to WAIT_FOR_FIRST_ROLL. Furthermore, the "Place Bet" button should be disabled and the "Roll" button should be enabled.

That's the end of the doPlaceBet() method. The game will continue later, in the doFirstRoll() method, when the user clicks the "Roll" button.

You might want to run your program at this time to check that the doPlaceBet() method works correctly.


Finishing the Program

To finish up the program, you still have to write doFirstRoll() and doRollForPoint(). Both of these methods need to roll the dice. The Dice class has an instance method for this purpose. Just call dice.roll(). This method returns the total showing on the dice:

            int roll = dice.roll();

(If you want to know more about the Dice class, read the source code, Dice.java.)

In doFirstRoll(), the user can have an immediate win (with a 7 or 11) or an immediate loss (with 2, 3, or 12). Otherwise, the roll becomes the value of the "point", and the game continues in a new state, ROLLING_FOR_POINT.

In doRollForPoint(), the user can lose by rolling a 7 and can win by rolling the point value again. Otherwise, the game continues in the same state.

Among the possibilities in the doFirstBet() and doRollForPoint() methods are that the user might win or lose the game. When this happens, you have to adjust the user's money as well as change the state and the messages. You might find it helpful to write methods void win() and void lose() to handle the details of winning and losing.

I leave you to finish up the last routines yourself.


David Eck, 8 November 2001