|
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.
- 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 run it!
- 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)
Last Revised
Thursday, 03-Jun-2010 09:52:20 CDT
©2006 Stephen Wong and Dung Nguyen