Comp202: Principles of Object-Oriented Programming II
Fall 2006 -- Hangman: The View and the Controller   


Quick links to supporting pages:  Main page, BodyParts, MVC, Sounds, WordList


The process below is designed to have you gain skills in good modular coding practices.   DO NOT SKIP TESTING SECTIONS!!   A few minutes spent stub coding and testing will save you hours in the long run.

The instructions below purposely omit specifics on how to accomplish some tasks. You are expected to read the Java API documentation to learn what methods are available to accomplish your purposes.

The Controller

By now you should have completed the hangman model: HangmanGame.  The controller serves to wire to model and the view together.  To help you test your view code, we provide you with the complete code for the controller.

package controller;

import model.*;
import view.*;
import javax.swing.*;
import java.awt.*;

/**
 * The controller that instantiates and connects the HangmanGame (the model)
 * and the HangmanGUI (the view) via adapters.
 * The controller also instantiates the adapters.
 * The adapters are implemented as anonymous inner classes.
 * @stereotype controller
 */
public class HangmanController extends JApplet{

    private HangmanGUI _gui;
    private HangmanGame _hangman;

    /**
     * Instantiates the HangmanGame, HangmanFrame, and connects them together
     * using anonymous inner class defined adapters.
     */
    public void init() {
        _gui = new HangmanGUI(this,
            new IGameControlAdapter() {
                public String guess(char c) {
                    return _hangman.guess(c);
                }

                public String  reset() {
                    return _hangman.reset();
                }
            },
            new IPaintAdapter()  {
                public void paint(Graphics g) {
                    _hangman.paint(g);
                }
            });

        _hangman = new HangmanGame(new IGameAdapter() {
                public void won() {
                    _gui.won();
                }

                public void lose(String answer) {
                    _gui.lose(answer);
                }
            });

        _gui.reset();
    }

    public static void main(String[] args) {
        JFrame frame = new AFrame("Hangman Application") {
            protected void initialize() {
                HangmanController controller = new HangmanController();
                controller.init();
                getContentPane().add(controller, "Center");
                setSize(400, 400);
                setVisible(true);
            }
        };
        frame.validate();
    }

}

As defined in the above, the controller can be run both as an Applet and as a stand-alone application.  Do not concern yourself with Applet right now.  Once you have the view, HangmanGUI, written, you can compile and run the above controller as an GUI application.  The details for the view is described below.

The View

Before getting started, it is suggested that you peruse the following informational links:

MVC design pattern: http://www.exciton.cs.rice.edu/JavaResources/DesignPatterns/MVC.htm

Inner Classes: http://www.exciton.cs.rice.edu/JavaResources/Java/innerclasses.htm

Observer-Observable design pattern: http://www.exciton.cs.rice.edu/JavaResources/DesignPatterns/ObserverObservable.htm

GUI's in Java: http://www.exciton.cs.rice.edu/JavaResources/Java/gui.htm

To see a demo of what your GUI, as outlined below, should look like, see the Hangman demo.

To create the view, you will need to create the HangmanGUI class. Follow the directions in order, testing at least as often as specified.

  1. Create all the classes in the view package as per the documentation. This includes all initializations of fields. Some things to note:
    1. You'll need to import these packages: java.awt; java.awt.event, and javax.swing.
    2. The Announcer class and anything that refers to it is optional as it only makes sense for systems that can play sounds. Announcer.java and its two attendant WAV files can be downloaded here.
    3. First be sure to declare all the fields and methods of HangmanGUI before proceeding on to filling in the method bodies. The field initializers are also specified in the documentation.
    4. The use of the anonymous inner class to override JPanel for the displayPanel enables the scaffold to be drawn as well as commands the model, via the IPaintAdapter, to draw the body parts on the screen. The Graphics object "g" is the screen itself with the origin in the upper left hand corner of the displayPanel.
      1. This is a beautiful technique for creating custom component painting without having to define a new class for the job.
      2. This technique also has the advantage that the modified JPanel can easily access any resources from the frame that it might need, without having to pass them in as parameters to a method or constructor. For instance, here, the modified panel has direct access the to IPaintAdapter the frame holds.
      3. See the documentation for the code as to how to write this.
    5. jbInit() is a method whose function is to initialize the GUI components. It is good programming style to isolate different aspects of the initialization process, so we want you to get into the habit now. "jb" refers to "JBuilder" which originally auto-generated the code from which this code is based.  This is the equivalent of the initialize() method given in the AFrame and all concrete subclasses shown in the recent lectures.
    6. Check that the code for HangmanGUI compiles!
    7. Now compile HangmanController and run it!
     
  2. Fill in jbInit():
    1. You'll need to refer to the content pane of the applet _applet a number of times, so instantiate a local variable of type Container called contentPane and initialize it to _applet.getContentPane(). 
    2. Set the layout of the contentPane (contentPane.setLayout) to the borderLayout1 field.
    3. Set the applet size to be 400 x 300 pixels. Write down what you think the syntax should be--you'll probably be correct. If not, go look it up in the Java API docs.
    4. Compile your code and run your controller program again.
    5. Add the control panel to the contentPane in the "north" position:

      contentPane.add(controlPanel, BorderLayout.NORTH);

      A BorderLayout will position GUI components around the edges of the container component (here the frame) as designated by north, south, east and west. A component can also be placed in the center of the container.

    6. Likewise, add the displayPanel to the center position and the wordPanel to the south position.
    7. Compile your code and run your controller program again -- you shouldn't see any difference.
    8. Set the resetBtn's text: resetBtn.setText("Reset");
    9. Add the resetBtn to the controlPanel using the JPanel's default FlowLayout:

      controlPanel.add(resetBtn);

      The FlowLayout puts components in a centered horizontal line in the container, here the controlPanel.

    10. Compile your code and run your controller program again -- you should see the Reset button at the top center of the frame.
    11. Set the text of the guessLbl to "Guess: ".
    12. Add the guessLbl to the controlPanel.
    13. Compile your code and run your controller program again -- you should see the guessLbl to the right of the Reset button
    14. Set the preferred size of the inputTF to be 20x20: inputTF.setPreferredSize(new Dimension(20, 20));
    15. Add the inputTF to the controlPanel.
    16. Compile your code and run your controller program again -- you should see the inputTF to the right of the guessLbl.
    17. Set the text of the statusLbl to anything you want.
    18. Set the text of the wordLbl to anything you want.
    19. Add the statusLbl then the wordLbl to wordPanel.
    20. Compile your code and run your controller program again -- you should see the wordLbl to the right of the statusLbl at the bottom center of the frame.
    21. When you convince yourself that the wordLbl and the statusLbl are correct, change their text to be set to a space: " ". You need a character in the labels so that the panel will size properly.
    22. Set the background color of the displayPanel to be white. The Color class has static fields for pre-defined colors: i.e. Color.white
    23. Compile your code and run your controller program again -- you should see all three panels clearly now.
    24. Add the ActionListener to the resetBtn. ActionListeners are the adapters that connect the button to its "view", which is the frame here. The listener architecture used by Java is also part of the Observer-Observable design pattern. The ActionListener's actionPerformed() method should call the reset() method.
      resetBtn.addActionListener(new java.awt.event.ActionListener() { 
      	public void actionPerformed(ActionEvent e) { 
      		reset(); 
      	} 
      }); 
      
      When the button is clicked the ActionListener's actionPerformed method will be called. The ActionEvent parameter will contain information about the call such as which component caused the event (the resetBtn here) and what the event was (a mouse click). We will rarely, if ever, need the information in the ActionEvent object because we already know all the pertinent information because we know which ActionListener is installed in which component.
    25. The reset() method should reset the statusLbl's text and tell the frame to "repaint()". For testing purposes, have it set the statusLbl's text to something you can see.
    26. Compile your code and run your controller program again -- see if the button works. If it does, put the reset() method back to what it is supposed to do.
    27. We can now use the reset button to test the other methods in the frame. Here's what they should do:
      1. lose(s) : Given a string, it should set the statusLbl to "You lost! The answer is ". The wordLbl's text should be set to t input parameter String. And if you're using it, it should call the announcer's userLost() method.
      2. Put a dummy call to the lose() method in the actionPerformed method of the resetBtn's ActionListener. Just use any old String as the input parameter.
      3. Test that the lose() method works by compiling, running and clicking the reset button. -- remove the dummy code when you're convinced that lose() works.
      4. won() : Sets the statusLbl's text to "You won! The answer is " and optionally, call the announcer's userWon() method.;
      5. Put a dummy call to the win() method in the actionPerformed method of the resetBtn's ActionListener.
      6. Test that the win() method works by compiling, running and clicking the reset button. -- remove the dummy code when you're convinced that win() works.
    28. Add an ActionListener to the inputTF that
      1. Checks if the length ( just called length() ) of the inputTF's text is greater than zero. If so, it sets the text of the wordLbl to some arbitrary String. We'll change this "stub" code in just a bit...
      2. Sets the text of the inputTF to an empty String.
      3. Repaints the frame.
      4. Test that code!

Whew! Finally, on to the controller....

Modify the HangmanGame

We have to do some modifications of our HangmanGame code:

  1. Add random answer capability: Modify the code of HangmanGame.makeAnswer() to randomly give one of at least 5 possible answers. The easiest way is to use an array of Strings and then to randomly pick on of the Strings using the Math.random() function:

    strArray[(int) (strArray.length*Math.random())]

    Math.random() returns a double between 0 and 1. Multiply by the length of the array and cast to an int to get a number between 0 and the length-1, inclusive. Retrieve that element from the array.

    Be sure to put what the possible answers are in your documentation, to help those poor labbies trying to run your code and win the game.

  2. Implement the Running vs. NonRunning states. The guess() method depends on whether or not the game is running or not, hence the guess() method is dispatched to the abstract state.
    1. Move the current guess() code to the appropriate state.
    2. Add the dispatching code.
    3. Fill in the code for the other state.
    4. Modify the reset() method to insure that the game is back in the RunningState.
    5. Modify the guess() code in the appropriate state, so that the game stops running when it should.

That's all folks!!

Checklist

Your game should now display full functionality:

  1. When the game first comes up by running "java HangmanController", the gallows should be empty and a random, completely "dashed" answer should be shown. The game should be running.
  2. Spaces in phrases should be displayed as spaces, not dashes.
  3. A correct guess should reveal all occurrences of that letter.
  4. An incorrect guess should result in one more body part being displayed on the gallows.
  5. A repeated guess of the same letter should count as an incorrect guess.
  6. If the player guesses all the letters before the figure on the gallows is completed, then there should be an indication that the player has won, including the correct answer.
  7. If the figure on the gallows is completed before all the letters are guessed, then there should be an indication that the player has lost, including the correct answer.
  8. Once the game has been won or lost, no further guesses should be accepted until the reset button is pressed.
  9. Pressing the reset button at anytime should reset the game with a new phrase to be guessed.

(Go back to the main project page)

 


Last Revised Thursday, 03-Jun-2010 09:52:20 CDT

©2006 Stephen Wong and Dung Nguyen