The View and the Controller
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();
}
}
package app;
import model.*;
import view.*;
import controller.*;
import javax.swing.*;
public class HangmanApp{
private static void createAndShowGUI() {
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();
}
/**
* Runs as a stand-alone GUI application with a main frame that contains
* a HangmanController JApplet.
* @param nu not used.
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
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.
- Create all the classes in the view package as
per the documentation. This includes all initializations of fields. Some
things to note:
- You'll need to import these packages: java.awt; java.awt.event, and
javax.swing.
- 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.
- 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.
- 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.
- This is a beautiful technique for creating custom component painting
without having to define a new class for the job.
- 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.
- See the documentation for the code as to how to write this.
- 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.
- Check that the code for HangmanGUI compiles!
- Now compile HangmanController and HangmanApp
and run HangmanApp!
- Fill in jbInit():
- 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().
- Set the layout of the contentPane (contentPane.setLayout) to the borderLayout1
field.
- 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.
- Compile your code and run your controller program again.
- 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.
- Likewise, add the displayPanel to the center position and the wordPanel
to the south position.
- Compile your code and run your controller program again
-- you shouldn't see any difference.
- Set the resetBtn's text: resetBtn.setText("Reset");
- 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.
- Compile your code and run your controller program again
-- you should see the Reset button at the top center of the
frame.
- Set the text of the guessLbl to "Guess: ".
- Add the guessLbl to the controlPanel.
- Compile your code and run your controller program again
-- you should see the guessLbl to the right of the Reset button
- Set the preferred size of the inputTF to be 20x20: inputTF.setPreferredSize(new
Dimension(20, 20));
- Add the inputTF to the controlPanel.
- Compile your code and run your controller program again
-- you should see the inputTF to the right of the guessLbl.
- Set the text of the statusLbl to anything you want.
- Set the text of the wordLbl to anything you want.
- Add the statusLbl then the wordLbl to wordPanel.
- 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.
- 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.
- Set the background color of the displayPanel to be white. The Color
class has static fields for pre-defined colors: i.e. Color.white
- Compile your code and run your controller program again
-- you should see all three panels clearly now.
- 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.
- 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.
- 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.
- We can now use the reset button to test the other methods in the frame.
Here's what they should do:
- 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.
- 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.
- 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.
- won() : Sets the statusLbl's text to "You won! The answer is
" and optionally, call the announcer's userWon() method.;
- Put a dummy call to the win() method in the actionPerformed method
of the resetBtn's ActionListener.
- 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.
- Add an ActionListener to the inputTF that
- 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...
- Sets the text of the inputTF to an empty String.
- Repaints the frame.
- Test that code!
Whew! Finally, on to the controller....
Modify the HangmanGame
We have to do some modifications of our HangmanGame code:
- 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.
- 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.
- Move the current guess() code to the appropriate state.
- Add the dispatching code.
- Fill in the code for the other state.
- Modify the reset() method to insure that the game is back in the RunningState.
- 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:
- 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.
- Spaces in phrases should be displayed as spaces, not dashes.
- A correct guess should reveal all occurrences of that letter.
- An incorrect guess should result in one more body part being displayed on
the gallows.
- A repeated guess of the same letter should count as an incorrect guess.
- 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.
- 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.
- Once the game has been won or lost, no further guesses should be accepted
until the reset button is pressed.
- Pressing the reset button at anytime should reset the game with a new phrase
to be guessed.
(Go back to the main project page)