Workshop: A Little Lotto Madness

To see how Swing's event-handling classes work in a Java program, you will finish LottoMadness, the lottery simulation that was begun during Hour 14.

By the way

See what you missed if you didn't read Hour 14? It may have been my finest hour.


At this point, LottoMadness is just a graphical user interface. You can click buttons and enter text into text boxes, but nothing happens in response. In this workshop, you will create LottoEvent, a new class that receives user input, conducts lotto drawings, and keeps track of the number of times you win anything. Once the class is complete, you will add a few lines to LottoMadness so that it makes use of LottoEvent. It is often convenient to divide Swing projects in this manner, with the graphical user interface in one class, and the event-handling methods in another. The purpose of this app is to assess the user's chances of winning a six-number lotto drawing in a lifetime. Screenshot shows a screen capture of the program as it continues to run.

Screenshot Running the LottoMadness app.

Java ScreenShot


Instead of using probability to figure this problem out, the computer will take a more anecdotal approach: It will conduct drawing after drawing after drawing until you win. Because the 6-out-of-6 win is extremely unlikely, the program also will report on any combination of three, four, or five winning numbers. The interface you created includes 12 text fields for Lotto numbers and two check boxes labeled Quick Pick and Personal. Six of the text fields are disabled from input, and they will be used to display the winning numbers of each drawing. The other six text fields are for the user's choice of numbers. If the user wants to select six numbers manually, he should select the Personal check box. If he selects the Quick Pick box instead, six random numbers will appear in the text fields. Three buttons control the activity of the program: Stop, Play, and Reset. When the Play button is clicked, the program starts a thread called playing and generates Lotto drawings as fast as it can. Clicking the Stop button stops the thread, and clicking Reset clears all fields so the user can start all the number-crunching over again. The LottoEvent class implements three interfaces: ActionListener, ItemListener, and Runnable. The first two are needed to listen to user events generated by the buttons and check boxes on the app. The program does not need to listen to any events related to the text fields because they will be used strictly to store the user's choice of numbers. The user interface handles this function automatically. The class requires the use of two packages from the Java class library: the main Swing package, javax.swing, and Java's event-handling package, java.awt.event. The class has two instance variables:

  • gui, a LottoMadness object
  • playing, a Thread object that will be used to conduct continuous lotto drawings

The gui variable will be used to communicate with the LottoMadness object that contains the project's graphical user interface. When you need to make a change to the interface or retrieve a value from one of its text fields, you will use the gui object's instance variables. For example, the play instance variable of LottoMadness represents the Play button. To disable this button in LottoEvent, the following statement can be used:

gui.play.setEnabled(false);


The following statement can be used to retrieve the value of the JTextField object got3:

String got3value = gui.got3.getText();


Listing 15.2 contains the full text of the LottoEvent class.

Listing 15.2. The Full Text of LottoEvent.java
 1: import javax.swing.*;
 2: import java.awt.event.*;
 3: 4: public class LottoEvent implements ItemListener, ActionListener,
 5: Runnable {
 6: 7: LottoMadness gui;
 8: Thread playing;
 9: 10: public LottoEvent(LottoMadness in) {
 11: gui = in;
 12: }
 13: 14: public void actionPerformed(ActionEvent event) {
 15: String command = event.getActionCommand();
 16: if (command == "Play") {
 17: startPlaying();
 18: }
 19: if (command == "Stop") {
 20: stopPlaying();
 21: }
 22: if (command == "Reset") {
 23: clearAllFields();
 24: }
 25: }
 26: 27: void startPlaying() {
 28: playing = new Thread(this);
 29: playing.start();
 30: gui.play.setEnabled(false);
 31: gui.stop.setEnabled(true);
 32: gui.reset.setEnabled(false);
 33: gui.quickpick.setEnabled(false);
 34: gui.personal.setEnabled(false);
 35: }
 36: 37: void stopPlaying() {
 38: gui.stop.setEnabled(false);
 39: gui.play.setEnabled(true);
 40: gui.reset.setEnabled(true);
 41: gui.quickpick.setEnabled(true);
 42: gui.personal.setEnabled(true);
 43: playing = null;
 44: }
 45: 46: void clearAllFields() {
 47: for (int i = 0; i < 6; i++) {
 48: gui.numbers[i].setText(null);
 49: gui.winners[i].setText(null);
 50: }
 51: gui.got3.setText("0");
 52: gui.got4.setText("0");
 53: gui.got5.setText("0");
 54: gui.got6.setText("0");
 55: gui.drawings.setText("0");
 56: gui.years.setText("0");
 57: }
 58: 59: public void itemStateChanged(ItemEvent event) {
 60: Object item = event.getItem();
 61: if (item == gui.quickpick) {
 62: for (int i = 0; i < 6; i++) {
 63: int pick;
 64: do {
 65: pick = (int) Math.floor(Math.random() * 50 + 1);
 66: } while (numberGone(pick, gui.numbers, i));
 67: gui.numbers[i].setText("" + pick);
 68: }
 69: } else {
 70: for (int i = 0; i < 6; i++) {
 71: gui.numbers[i].setText(null);
 72: }
 73: }
 74: }
 75: 76: void addOneToField(JTextField field) {
 77: int num = Integer.parseInt("0" + field.getText());
 78: num++;
 79: field.setText("" + num);
 80: }
 81: 82: boolean numberGone(int num, JTextField[] pastNums, int count) {
 83: for (int i = 0; i < count; i++) {
 84: if (Integer.parseInt(pastNums[i].getText()) == num) {
 85: return true;
 86: }
 87: }
 88: return false;
 89: }
 90: 91: boolean matchedOne(JTextField win, JTextField[] allPicks) {
 92: for (int i = 0; i < 6; i++) {
 93: String winText = win.getText();
 94: if ( winText.equals( allPicks[i].getText() ) ) {
 95: return true;
 96: }
 97: }
 98: return false;
 99: }
100: 101: public void run() {
102: Thread thisThread = Thread.currentThread();
103: while (playing == thisThread) {
104: addOneToField(gui.drawings);
105: int draw = Integer.parseInt(gui.drawings.getText());
106: float numYears = (float)draw / 104;
107: gui.years.setText("" + numYears);
108: 109: int matches = 0;
110: for (int i = 0; i < 6; i++) {
111: int ball;
112: do {
113: ball = (int)Math.floor(Math.random() * 50 + 1);
114: } while (numberGone(ball, gui.winners, i));
115: gui.winners[i].setText("" + ball);
116: if (matchedOne(gui.winners[i], gui.numbers)) {
117: matches++;
118: }
119: }
120: switch (matches) {
121: case 3:
122: addOneToField(gui.got3);
123: break;
124: case 4:
125: addOneToField(gui.got4);
126: break;
127: case 5:
128: addOneToField(gui.got5);
129: break;
130: case 6:
131: addOneToField(gui.got6);
132: gui.stop.setEnabled(false);
133: gui.play.setEnabled(true);
134: playing = null;
135: }
136: try {
137: Thread.sleep(100);
138: } catch (InterruptedException e) {
139: // do nothing
140: }
141: }
142: }
143: }


Save Listing 15.2 as LottoEvent.java and compile it into a class file. This class cannot be run as an app—it lacks a main() method. Instead, you will create an object of this class in LottoMadness later in this workshop, and work with that object. The LottoEvent class has one constructor: LottoEvent(LottoMadness). The LottoMadness object specified as an argument to the constructor identifies the object that is relying on LottoEvent to handle user events and conduct drawings. The following methods are used in the class to accomplish specific tasks:

  • Lines 46–57: The clearAllFields() method causes all text fields in the app to be emptied out. This method is handled when the Reset button is clicked.
  • Lines 76–80: The addOneToField() method converts a text field to an integer, increments it by one, and converts it back into a text field. Because all text fields are stored as strings, you have to take special steps to use some of them as numbers.
  • Lines 82–89: The numberGone() method takes three arguments—a single number from a Lotto drawing, an array that holds several JTextField objects, and a count integer. This method makes sure that each number in a drawing hasn't been selected already in the same drawing.
  • Lines 91–99: The matchedOne() method takes two arguments—a JTextField object and an array of six JTextField objects. This method checks to see whether one of the user's numbers matches the numbers from the current lotto drawing.

The actionPerformed() method of the app receives the action events caused when the user clicks Stop, Play, or Reset. The getActionCommand() method retrieves the label of the button that is used to determine which component was pressed. Clicking the Play button causes the startPlaying() method in Lines 27–35 to be called. This method disables four components so they do not interfere with the drawings as they are taking place. Clicking Stop causes the stopPlaying() method in Lines 37–44 to be called, which enables every component except for the Stop button. The itemStateChanged() method receives the user events triggered by the selection of the Quick Pick or Personal check boxes. The getItem() method sends back an Object that represents the check box that was clicked. If it's the Quick Pick check box, six random numbers from 1 to 50 are assigned to the user's lotto numbers. Otherwise, the text fields that hold the user's numbers are cleared out. The LottoEvent class uses numbers from 1 to 50 for each ball in the lotto drawings. This is established in Line 113, which multiplies the Math.random() method by 50, adds 1 to the total, and uses this as an argument to the Math.floor() method. The end result is a random integer from 1 to 50. If you replaced 50 with a different number here and on Line 65, you could use LottoMadness for lottery contests that generate a wider or smaller range of values. One thing to note about the LottoMadness project is the lack of variables used to keep track of things like the number of drawings, winning counts, and lotto number text fields. This element of user interface coding differs from other types of programs. You can use the interface to store values and display them automatically. To finish the project, load the LottoMadness.java file you created during the last hour into your word processor. You only need to add six lines to make it work with the LottoEvent class. First, add a new instance variable to hold a LottoEvent object, using the following statement:

LottoEvent lotto = new LottoEvent(this);


Next, in the LottoMadness() constructor, call the addItemListener() and addActionListener() methods of each user interface component that can receive user input:

// Add listeners quickpick.addItemListener(lotto);
personal.addItemListener(lotto);
stop.addActionListener(lotto);
play.addActionListener(lotto);
reset.addActionListener(lotto);


Listing 15.3 contains the full text of LottoMadness.java after you have made the changes. The lines you added are shaded—the rest is unchanged from the previous hour.

Listing 15.3. The Full Text of LottoMadness.java
 1: import java.awt.*;
 2: import javax.swing.*;
 3: 4: public class LottoMadness extends JFrame {
 LottoEvent lotto = new LottoEvent(this);
 6: 7: // set up row 1
 8: JPanel row1 = new JPanel();
 9: ButtonGroup option = new ButtonGroup();
 10: JCheckBox quickpick = new JCheckBox("Quick Pick", false);
 11: JCheckBox personal = new JCheckBox("Personal", true);
 12: // set up row 2
 13: JPanel row2 = new JPanel();
 14: JLabel numbersLabel = new JLabel("Your picks: ", JLabel.RIGHT);
 15: JTextField[] numbers = new JTextField[6];
 16: JLabel winnersLabel = new JLabel("Winners: ", JLabel.RIGHT);
 17: JTextField[] winners = new JTextField[6];
 18: // set up row 3
 19: JPanel row3 = new JPanel();
 20: JButton stop = new JButton("Stop");
 21: JButton play = new JButton("Play");
 22: JButton reset = new JButton("Reset");
 23: // set up row 4
 24: JPanel row4 = new JPanel();
 25: JLabel got3Label = new JLabel("3 of 6: ", JLabel.RIGHT);
 26: JTextField got3 = new JTextField("0");
 27: JLabel got4Label = new JLabel("4 of 6: ", JLabel.RIGHT);
 28: JTextField got4 = new JTextField("0");
 29: JLabel got5Label = new JLabel("5 of 6: ", JLabel.RIGHT);
 30: JTextField got5 = new JTextField("0");
 31: JLabel got6Label = new JLabel("6 of 6: ", JLabel.RIGHT);
 32: JTextField got6 = new JTextField("0", 10);
 33: JLabel drawingsLabel = new JLabel("Drawings: ", JLabel.RIGHT);
 34: JTextField drawings = new JTextField("0");
 35: JLabel yearsLabel = new JLabel("Years: ", JLabel.RIGHT);
 36: JTextField years = new JTextField("0");
 37: 38: public LottoMadness() {
 39: super("Lotto Madness");
 40: setSize(550, 270);
 41: setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 42: GridLayout layout = new GridLayout(5, 1, 10, 10); 
 43: setLayout(layout);
 44: // Add listeners
 quickpick.addItemListener(lotto);
 personal.addItemListener(lotto);
 stop.addActionListener(lotto);
 play.addActionListener(lotto);
 reset.addActionListener(lotto);
 51: 52: FlowLayout layout1 = new FlowLayout(FlowLayout.CENTER,
 53: 10, 10);
 54: option.add(quickpick);
 55: option.add(personal);
 56: row1.setLayout(layout1);
 57: row1.add(quickpick);
 58: row1.add(personal);
 59: add(row1);
 60: 61: GridLayout layout2 = new GridLayout(2, 7, 10, 10);
 62: row2.setLayout(layout2);
 63: row2.add(numbersLabel);
 64: for (int i = 0; i < 6; i++) {
 65: numbers[i] = new JTextField();
 66: row2.add(numbers[i]);
 67: }
 68: row2.add(winnersLabel);
 69: for (int i = 0; i < 6; i++) {
 70: winners[i] = new JTextField();
 71: winners[i].setEditable(false);
 72: row2.add(winners[i]);
 73: }
 74: add(row2);
 75: 76: FlowLayout layout3 = new FlowLayout(FlowLayout.CENTER,
 77: 10, 10);
 78: row3.setLayout(layout3);
 79: stop.setEnabled(false);
 80: row3.add(stop);
 81: row3.add(play);
 82: row3.add(reset);
 83: add(row3);
 84: 85: GridLayout layout4 = new GridLayout(2, 3, 20, 10);
 86: row4.setLayout(layout4);
 87: row4.add(got3Label);
 88: got3.setEditable(false);
 89: row4.add(got3);
 90: row4.add(got4Label);
 91: got4.setEditable(false);
 92: row4.add(got4);
 93: row4.add(got5Label);
 94: got5.setEditable(false);
 95: row4.add(got5);
 96: row4.add(got6Label);
 97: got6.setEditable(false);
 98: row4.add(got6);
 99: row4.add(drawingsLabel); 
100: drawings.setEditable(false);
101: row4.add(drawings);
102: row4.add(yearsLabel);
103: years.setEditable(false);
104: row4.add(years);
105: add(row4);
106: 107: setVisible(true);
108: }
109: 110: public static void main(String[] arguments) {
111: LottoMadness frame = new LottoMadness();
112: }


After you compile the LottoMadness class, run the app. If you are using the JDK, you can run it with the following command:

java LottoMadness


The app can test your lotto-playing skills for thousands of years. As you might expect, it's an exercise in futility—the chances of winning a 6-out-of-6 lotto drawing in a lifetime is extremely slim, even if you live as long as a biblical figure.

Did you Know?

The tutorial's website at http://www.java24hours.com contains a link to an applet version of the LottoMadness program. At the time of this printing, 5,079,684 drawings have been conducted, which equals 48,843 years of twice-weekly drawings. There have been 78,198 3-out-of-6 winners, 4,135 4-out-of-6 winners, 100 5-out-of-6 winners, and 1 6-out-of-6 winner. The first person to win this fictional lottery was Bill Teer on Aug. 14, 2000, more than four years after the applet went online. His numbers were 3, 7, 1, 15, 34, and 43, and it only took him 241,225 drawings (2,319.47 years) to win.


      
Comments