Assignment 3
Zapem!: An Animated Game
In lecture, we discussed & illustrated the use of inheritance & polymorphism. In this programming assignment, we store a list of shapes, called Critter objects, in an indentifier typed as a List & implemented as an ArrayList. An animation is achieved by periodically moving & drawing each element of the list, polymorphically. This programming assignment, beyond using features & classes that you have used before, gives you Java programming experience with the following:
- Java
interface
keyword & the use of interfaces MouseListener
interfacejavax.swing.Timer
classList
interfaceArrayList
classMath.random
method- Javadoc tools
Specification
The Zapem! Game Description
The object of the game is to zap critters that are moving in a square 2D field. The time taken to zap all the critters is displayed at the bottom of the screen. The critter field, in fact, is a 2D torus (a donut): Its east border connects to its west border & it south border connects to its north border. The application's layout consists of the critter field positioned above a control panel comprising 3 GUI items:
- JButton: New Game - clicking it starts a new game.
- JLabel: Describles the text field to its right, which reports how long it took to finish the most recently completed Zapem! game.
- JTextField: Contains the duration of the most recently completed Zapem! game. This time is expressed in seconds with 3 significant digits to the right of the decimal point.
The overall application consists of 4 classes:
- App - integrates the 3 components below.
- Game - models the game;
- View - responsible to present a view of the game - the field of critters;
- ControlPanel - responsible for displaying the GUI elements & implementing their behavior.
The App Class
This class is given in its entirety. Please study it & investigate the program & organization choices that it reflects.
import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JFrame; /** * * @author Pete Cappello */ public class App extends JFrame { private final ControlPanel controlPanel; private final Game game; private final View view; App() { game = new Game(); view = new View( game, game.getImage() ); controlPanel = new ControlPanel( view, game ); setTitle( "Zap 'em" ); setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); add( view, BorderLayout.CENTER ); add( controlPanel, BorderLayout.SOUTH ); Dimension dimension = new Dimension( Game.IMAGE_SIZE, Game.IMAGE_SIZE + controlPanel.getHeight() ); setSize( dimension ); setPreferredSize( dimension ); setVisible( true ); } /** * Run the Graphics Virtual Machine application. * @param args unused */ public static void main( String[] args ) { App app = new App(); } }
The View Class
The class extendsjavax.swing.JPanel
,
has 2 attributes, a Game object & a java.awt.Image
object, both of which are set in its sole constructor, & are unchanged thereafter.
Its 1 method is paintComponent
, a JPanel method that it overrides.
This method's body performs 2 actions:
- Invoke the paintComponent method of its superclass;
- invoke the
graphics.drawImage
method in order to display the Game object's entireImage
, to which itsImage
reference is set.
The ControlPanel Class
This class has several attributes:
- View
- Game
JButton
- for the new game;JLabel
- labels the text field contents;JTextField
- text field holds the duration of a completed game;javax.swing.Timer
- used to animate, it controls the timing between repaints of the View.
The animation is controlled by a Timer object, which causes window action events to occur every n milliseconds, where n is set at the time the Timer object is constructed. To have the ControlPanel use this Timer, 2 things are required:
- The ControlPanel class must implement the
ActionListener
interface; - The ControlPanel must register itself as the Timer's ActionListener.
actionPerformed
method (see code below).
Please see the textbook, Java tutorial, or Java 7 API to see details & examples of using a Swing Timer.
// put import statements here /** * * @author Pete Cappello */ public class ControlPanel extends JPanel implements ActionListener, MouseListener { private final static int PAUSE = 150; // milliseconds private final View view; private final Game game; private final JButton newGameButton = new JButton( "New Game" ); private final JLabel gameDurationLabel = new JLabel( " Game duraion in seconds:" ); private final JTextField gameDurationTextField = new JTextField(); private final Timer animationTimer; private long gameStartTime; ControlPanel( View view, Game game ) { this.view = view; this.game = game; setLayout( new GridLayout( 1, 3 ) ); add( newGameButton ); add( gameDurationLabel ); add( gameDurationTextField ); animationTimer = new Timer( PAUSE, this ); gameDurationTextField.setEditable( false ); initialize(); } private void initialize() { //------------------------------------------ // contoller TEMPLATE CODE for each action //------------------------------------------ // If you are running Java 8, use lambda expressions // newGameButton.addActionListener( // ( ActionEvent actionEvent ) -> { newGameButtonActionPerformed( actionEvent ); } // ); // If you are not running Java 8, uncomment the code below newGameButton.addActionListener( new ActionListener() { @Override public void actionPerformed( ActionEvent actionEvent ) { newGameButtonActionPerformed( actionEvent ); } }); // register this as the listener for mouse events in the View JPanel view.addMouseListener( this ); } // _____________________________ // controller for each action // _____________________________ private void newGameButtonActionPerformed( ActionEvent actionEvent ) { // set the text field to the empty string; // record the start time of the game; // restart the Timer // start the game. } /** * Implementation of ActionListener of Timer * @param e unused */ @Override public void actionPerformed( ActionEvent e) { // 1. move and draw the critters (after blanking their previous positions) // 2. repaint the View // 3. if the game is over, // a. stop the Timer // b. get the current time, using System.currentTimeMillis // c. compute the duration of the game // d. set the TextField to this value, in seconds, with 3 significant digits to the right of the decimal point. } // implementation of MouseListener @Override public void mouseClicked( MouseEvent event ) { // your implementation of a listener for mouse click events goes here: // 1. stop the timer; // 2. invoke the Game.processClick method with the x & y coordinates of the mouse click; // 3. start the timer; // 4. repaint the view component. } @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} }
Game
This class has at least the following attributes:
Image
- the image of the Critter field- Critter - critterFactory used to make Critter objects, see description below of method makeCritter.
List<Critter>
- list of critter objects in the game.
Inside the class skeleton given below, comments indicate where your code should go & what it should do. Although not shown in the skeleton, you need to supply an Image getter. You also may need other methods. I did. But, I want you to think about it. You will need some way to answer the question "Do the mouse click coordinates lie within the body of this Critter object?"
Inside the Game class, declare a private Critter class, to be used only be the Game class. It has the following attributes:
- Color critter's color
int
x cordinateint
y coordinateint
delta x - the amount that it moves in the x dimension on each moveint
delta y - the amount that it moves in the y dimension on each move
- The minimum amount of the absolute value of a delta is the 4th root of the image size.
- The maximum amount of the absolute value of a delta is the square root of the image size.
- The delta is randomly chosen to be positive or negative.
Critter's makeCritter (factory) method constructs & returns a Critter object, randomly selected from the 3 subclasses: SquareCritter, RoundCritter, & WowCritter.
// imports go here /** * Zapem game model * * @author Peter Cappello */ public class Game { public final static int IMAGE_SIZE = 600; // other symbolic program constants go here private final Image image; private final Critter critterFactory; private List<Critter> critterList; private boolean isOver; Game() { // * construct the Image object, of size IMAGE_SIZE // * paint the image all white for its initial display // * instantiate critterFactory, a Critter [subclass] object, that is used as a "factory" to create Critter objects. } public void start() { // 1. put the game in the state that it is not over. // 2. construct an ArrayList of 3 new Critters. // For each Critter that you create, use the critterFactory to randomly select a subclass of Critter to instantiate. } public void draw() { // 1. blank the previous image (otherwise, you get a "trail") // 2. move then draw each critter. } public void processClick( int x, int y ) { // for each critter, if the mouse click is "in" the critter, remove it from the critter list. // if such a removel makes the list empty, put the game in a "game over" state. } abstract private class Critter { // declare its attributes Critter makeCritter() { // construct a critter, selected randomly from the 3 Critter subclasses } private Critter() { // set attributes with suitably random values. } abstract void draw( Graphics graphics ); void move() { // Move the object by applying deltas to its coordinates. // Be careful to implement the wrap-around properly // (consider what happens when applying the % operator to a negative number). } } // Subclass Critter with a square critter private class SquareCritter extends Critter { // your implementation goes here. } // Subclass Critter with a round critter private class RoundCritter extends Critter { // your implementation goes here. } // Subclass Critter with a round critter whose color is randomly chosen each time it is drawn private class WowCritter extends Critter { // your implementation goes here. } }
Feel free to add helper methods to your classes that enhance their readability.
We will test your application by playing your game.
Possible Application Enhancements
- Make the pause time, the number of critter in a game, & the critter size parameters that can change (e.g., settable by the user).
- Give the game levels, where the higher levels are more difficult by adjusting the parameters above.
- Make more & better Critter subclasses.
- Make a Critter subclass that is an image.
- Introduce sound, especially when a click is made, giving different sounds when the click results in a hit vs. a miss.
- Introduce Critter subclasses where critters do not just go in a straight line (around the torus).
- Change the torus so that it is a verrtical cylinder. When a Critter attempts to go north of the north boundary, it reflects south (its y-delta changes sign). Ditto for critters attempting to go south of the south border.
- Other ideas?
Design Notes
- The Critter makeCritter factory method should be static. However, inner classes may not have static methods. It may be better to have Critter be a top-level class with a static factory method.
Rubric
Value | Aspect |
---|---|
1 | Compiles & executes. |
5 | Overall control is correct; Critters are constructed, moved, & displayed correctly; . |
1 | Javadoc is complete, generated, & adheres to Javadoc guidelines. |
3 | Style |
10 | Total |
For style, we want:
- Consistent application of some indentation policy
- Carefully chosen, meaningful identifier names
- Thoughtful comments, where needed, expressed in concise, precise English.
- Use of symbolic program constants in place of literals;
- Organization adheres to DRY principle.