This exercise is intended to improve your understanding of Javaand gain a little more experience with the class librariesbefore you start Assignment 2.
The primary objective of Assignment 2 is to give you someexperience of classes and inheritance. You will be creatingclasses that can be used with supplied framework code. Yourclasses will all implement a standard interface - they all dothe same things, they just have their own individual ways ofperforming.
These preparatory exercises include:
Inheritance allows a programmer to exploit similarities in behavioursto simplify a program's design. In the simplest case, the programmer defines an interface
that specifiesthe common functionality and then creates a number of classes that all implement this interface.
In more complex cases, you can have an elaboratehierarchy that may have an interface or an abstract base classspecifying the essential commonalities together with subclasses and/orpartially implemented abstract classes that have code implementingsome or all of the inheritted functions and which defineadditional more specialized functionality.
The example for the exercise is a little program that maintains a "farmyard" where there may be many different kinds of animal. All animalseat, make noises, etc etc. The example here is a simplified version of the oneused for Perl OO.
The essence of being an "animal" in this program is defined via the interfaceanimal
:
public interface animal { void getResources(); void identifySelf();}
(An "animal" is something that can identify itself. There is also agetResources method; some animals usedby the program need to do things like load sound files whenthey are created.)
Actually, this is NOT a good example of inheritance (sorry Perl guys,but you really have messed this up). The implementations defined forthe different kinds animals are essentially all the same - there are no realdifferences in behaviour! Each kind of animal identifies itself in the sameway - it plays a sound file (or prints a messageif there is no sound file). Of course, the sounds differ - cows "moo",sheep "baah" (or vice versa if you get the sound files mixed up). But thecode is essentially the same. These "classes" differ parametrically - theyhave different sound file parameters. One should NOT define differentclasses in such cases; a single class with some data members to holdthe parametric data will suffice. However, if you were really trying to simulatea farmyard, then there would be many additional behaviours defined forthe classes. These would have different implementations (think of behaviours -methods of the class - that express how the different kinds of animals eat or move).
The program is a little "menu select" style program offering commandsthat allow a user to add animals to the farmyard, or inspect the farmyardallowing animals to identify themselves. The "farmyard" is the heterogeneouscollection of polymorphic references; it is simply a java.util.Vector holding references to different animals. The dynamic binding part in the "doVisit" functionwhere each animal in the farmyard gets to identify itself.
The application is provided as a compressed NetBeans project(in /share/cs-pub/csci213/Lab2). The main project folder includesa "sounds" folder with a number of sounds in .au
format.(The AudioClip class in the Java libraries can be used to play audio files in ".au", ".wav", ".midi", and ".aiff" sound formats.It cannot play most of the other sound formats. With ".wav" files, itis limited to simply "PCM" encoding - no compression, no stereo, nohigh sampling rates. Most of the sound files that you find onthe Internet cannot be played. There are converters that willconvert a high quality sound file to (a much larger) ".au" filethat can be played.) The files making up the application areshown in NetBeans Files view:
The main program is:
public class A2a { private static Vector<Animal> theFarmYard; public static AudioClip getSound(String name) { // code discussed below ... } private static void doVisit() { if(theFarmYard.size()==0) System.out.println("Silence, the farmyard is empty"); else { System.out.println("Each animal in farm will identify itself and play sound"); System.out.println("Multiple sounds may play concurrently"); for(Animal a : theFarmYard ){ a.identifySelf(); } } }(The highlighted loop in doVisit is where we get "polymorphism" and "dynamic binding". The object reference variable
Animal a
is polymorphic - many shaped - sometimes referencing a "Cow" object,sometimes a "Rooster" object etc. The identifySelf
methodcall involves dynamic binding. At run-time, the Java system determineswhether to call Cow.identifySelf
or Rooster.identifySelf
.)private static void doCreate(BufferedReader input) { // code discussed below } private static void menuSelect(BufferedReader input) { try { System.out.println("Commands:" + "\nc\tCreate animal from specified class" + "\nv\tVisit farmyard" + "\nq\tQuit"); for(;;){ System.out.println("->"); String line = input.readLine(); if("q".equals(line)) break; else if("v".equals(line)) doVisit(); else if("c".equals(line)) doCreate(input); else { System.out.println("Don't understand, see commands as above"); } } } catch(Exception e) { System.out.println("Failed because " + e); System.exit(1); } } public static void main(String[] args) { try { BufferedReader input = new BufferedReader( new InputStreamReader(System.in)); theFarmYard = new Vector<Animal>();(
Vector<Animal> theFarmYard
is the heterogeneous collection;it contains Animals
of different (hetero) kinds (genus).)menuSelect(input); System.out.println("finished"); } catch(Exception e) { System.out.println("Failed because " + e); } }}
Two Animal classes are defined in the NetBeans project supplied - Roosterand Cow. The Navigator/Inheritance view can show hierarchies -not that interesting in this trivial case:
As shown in the diagram, class Rooster inherits from the Animalinterface and, as always, from java.lang.Object.
The Rooster class is:
public class Rooster implements Animal { private static int counter = 0; private static AudioClip theSharedSound; private int myId; /** Creates a new instance of Rooster */ public Rooster() { myId = ++counter; } public void getResources() { if(theSharedSound==null) theSharedSound = A2a.getSound("Rooster.au"); } public void identifySelf() { System.out.println("Rooster #" + myId + " crowing"); if(theSharedSound!=null) theSharedSound.play(); } }
Each rooster gets its own identifier. Only one copy of thesound resource gets loaded; these roosters all crow thesame way.
The program has to be able to create animals that are instances of thedifferent classes. Obviously, this part of the code has to know about thedifferent possible classes. An initial version of the code is:
private static void doCreate(BufferedReader input) { try { // Modify this to create your animal types System.out.print("Enter class of animal:"); String line = input.readLine().trim(); if(line.equals("Rooster")) { Rooster aRooster = new Rooster(); aRooster.getResources(); theFarmYard.add(aRooster); System.out.println("done"); } else if(line.equals("Cow")) { Cow aCow = new Cow(); aCow.getResources(); theFarmYard.add(aCow); System.out.println("done"); } else System.out.println("Don't know how to create a " + line); } catch(Exception e) { System.out.println("Failed because " + e); System.exit(1); } }
This code can be extended as more animal sub-classes are defined.
Really it is better to separate such code out from the main program sothat there are no explicit references to the different animal subclasses. Thiscan be done by defining a separate helper "factory" class that has a static methodlike "createAnimal". This factory class would be something along the following lines:
public class AnimalFactory { public static Animal createAnimal(BufferedReader input) { ... }}
The main line code could simply make an invocation on this static classfunction.
Java can handle sound files in some formats. The code for loading soundsis all supplied by classes in the standard Java libraries. However, the requiredfunctions are distributed among a number of classes and the coding is a bitlow level. The following fragment packages the code:
public static AudioClip getSound(String name) { String baseName = System.getProperty("user.dir"); String fileURL = "file:" + baseName + "/sounds/" + name; AudioClip theClip = null; try { theClip = Applet.newAudioClip( new java.net.URL(fileURL)); } catch(Exception e) { System.out.println("Failed to load sound '" + name +"'"); System.out.println(e); System.exit(1); } return theClip; }
This static function loads a file from a "sounds" subdirectory ofthe current working directory(by default, the working directory of a NetBeans program is themain project directory). Althougha function rather than a complete class, this is a fragment of reusable code.You can cut and paste it into some helper class in any Java program whereyou need to load sound files. Here, for convenience, it wasadded to the A2a main class.
Assignment 2 involves some graphical and, optionally, audio output.Instances of the classes that you develop for A2have to display themselves. The different classes could definedifferent display code, though in this year's assignment thatpart of the code is probably the same for all your classes (andso should be defined in a base class).
The lectures will not have started exploring the Java classes for constructing graphical user interfaces. That does notmatter. All the complex parts of the graphical output are suppliedfor you.
But graphics applications do allow for simple experimentationwith hierarchies. In this laboratory class, you should try defininga hierarchy of different shape classes whose instancescan be displayed graphically. Your main code will workentirely in terms of collections of shapes; at run timeyou can have rectangle, circle, polygon etc shapes.
Java has two graphics libraries: java.awt (AWT) and javax.swing (Swing). Both use the same Graphics
class for application specific graphicaloutput. Both provide collections of standard graphical components (windows,buttons, textfields, etc).A programusing AWT may be a little faster; a Swing program probablylooks better. At the base level, java.awt.Component, theyshare some commonalities. Many of their subclasses are alsosimilar; for example there isa java.awt.TextField class for text input that matches with the javax.swing.JTextFieldclass. You pick whichever library is more convenient (don't try to mix them, itdoesn't always work). Some exercises and assignments use AWT, some use Swing.
A graphical user interface (GUI) will consist of:
The hard part of writing a GUI is the creation of all the objects and allocationof specific visual areas to these objects. Integrated development environments (IDE)often have visual editors that allow you to construct the GUI using "point-and-click"style operations (the IDE then creates the necessary Java code). The actual layout of different elements within a window isthe most tiresome part; you have to consider issues such as what happens whenthe window is resized (which, if any, of the elements should grow or shrink - andby how much).
Once a GUI has been constructed, it is easy to use. Does it have an "actionbutton"? You don't need to be concerned about how or when the button gets drawn,or how it appears when the mouse cursor moves over it. All you need to dois arrange for an instance of one of your classes to be told to handle an"action event" that gets generatedwhen there is a mouse-click on the button.
Do you have a text inputfield? Again, you don't need to be concerned about how it appears, how ithandles input and editing of character data. Your code simply asks the textfieldfor the input text data when these data are needed.
The only point where you must write graphic related code is where your programmust display some application specific data. In these situations, a part ofthe GUI display is reserved as an area where your data will be shown. If you areusing java.awt your GUI will have an instance of a specialized java.awt.Canvas; withjavax.swing your GUI will contain an instance of an application defined subclassof javax.swing.JPanel. These specialized classes are quite standard intheir forms. The GUI object(Canvas or JPanel) occupies a defined area of the window (relying on standardbehaviours as defined in the Java library code). It will have an extra data memberthat is a reference to your "data object" and an extra function that allows your codeto set this data member. The paint
function for the class isoverridden so that the request to perform the "painting" can be passed to your data object.
Actual output to the window ("painting") is done using an instance of theclass java.awt.Graphics. The Graphics
class defines a number of simple functionsfor drawing lines, ovals, rectangles, and some complex functionsfor drawing polygons and even images (read from "gif" or "jpg" files). Thesefunctions include:
void drawString(String str, int x, int y)
; draws thespecified string starting at the specified x,y location.void drawLine(int x1, int y1, int x2, int y2)
; drawsa line between the two specified points.void drawOval(int x, int y, int width, int height)
; drawsthe outline of an oval (ellipse) that fits within a rectangle definedby the arguments.void fillRect(int x, int y, int width, int height)
; drawsa filled rectangle using the currently selected "color"void setColor(Color c)
; changes the currently selectedcolor.(Smart students will notice that java.awt.Graphics isan interface not a class. One cannot instantiate an interface. Themethods declared in an interface are not defined. So, how can a Graphicsobject exist and draw rectangles etc? The way it works is that the JavaAWT code linked into your program creates an instance of some class thatimplements Graphics. When running on a PC, you would get a "PCGraphics"object, on a Macintosh you would get a "MacGraphics", and you wouldget instance of other classes on Solaris or Linux systems.)
(The coordinate system for Graphics is not the same as that which your learnt atschool! Coordinate 0,0 is the topleft corner, and the "y" value increasesas you go down, not up.)
Colors (sic) are instances of the class java.awt.Color. A color is definedin terms of the intensities of its red, green, and blue components. The color (255,0,0)is pure red; the color (255, 165, 0) is orange; the color (255, 255, 224) is a lightyellow; and color (65, 105, 225) is "royal blue".
The code supplied for this exercise builds a GUI that has two panels asshown below. There is a "data panel"for display of application data, and a "control panel" that contains an action button.Each time the action button is clicked, the data panel gets redrawn with the dataslightly changed (the coloured rectangle changes its size and colour).
The code comprises the following classes:
class A2ex
; the main program that constructs the GUI anda "data object" (the code also includes helper functions to loadsounds and images - these functions are not used in the code supplied)class A2ExData
; a very simple data class that hasa few data members and implements the paint function that drawsthe image of the data;class A2ExGUI
; builds the display with its two panels;class ControlPanel
; the control panel, holds an action button;class DataPanel
; occupies most of the window, linked to thedata object, arranges for the data object to paint itself.All display classes are specialized subclasses of javax.swing.JPanel. Thecontrol and data panels have links to the data object. The control panelneeds a link to tell the data object to change itself whenever the action buttonis activated. The display panel needs a link to the data object so that itcan forward the paint command.
The ControlPanel's "pedigree", as shown in the Navigator "inheritance view",is a little more interesting than the previous inheritance example:
As shown in the diagram:
(Single inheritance through the extends sequence, multiple interfaces -not the same as C++ multiple inheritance.)
If you want to know what any of the base classes or interfaces does, movingthe mouse over the entry in the inheritance panel will cause a pop-up toappear with information.
The data object, instanceof A2ExData, has to have a link back to the display panel. Whenever thedata object is changed, it needs to request a "repaint" operation. (Innormal usage, a data object requests a "repaint" operation and expects toreceive a subsequent command to "paint"; the paint operation will be scheduledby code in the Java graphics libraries. It is only in specialcircumstances, such as animations, that a data object goes ahead andpaints directly to the GUI.)
The code for these classes is:
class A2Expublic class A2ex { private static Toolkit toolkit; private static Component displayer; public static Image getImage(String name) { // Not used in the example String baseName = System.getProperty("user.dir"); String fileName = baseName + "/images/" + name; Image anImage = toolkit.getImage(fileName); MediaTracker waitForIt = new MediaTracker(displayer); waitForIt.addImage(anImage, 1); try { waitForIt.waitForID(1); } catch (InterruptedException ie) { } return anImage; } public static AudioClip getSound(String name) { // Not used in the example String baseName = System.getProperty("user.dir"); String fileURL = "file:" + baseName + "/sounds/" + name; AudioClip theClip = null; try { theClip = Applet.newAudioClip( new java.net.URL(fileURL)); } catch(Exception e) { System.out.println("Failed to load sound '" + name +"'"); System.out.println(e); System.exit(1); } return theClip; } public static void main(String[] args) { try { // Ignore this Java hacking that defines // an "anonymous inner class". It is just // a short cut way of generating code // that handles "Window" events coming from // the operating system. WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; // Create the "data object" A2ExData someData = new A2ExData(); // Create the window JFrame aFrame = new JFrame("A2Exercise"); aFrame.addWindowListener(l); // Create the GUI display that goes in the window // and link it to the data object A2ExGUI displayComponent = new A2ExGUI(someData); // set references needed by helper functions that // can be used to load sound and image files displayer = displayComponent; toolkit = displayer.getToolkit(); // Organize the GUI aFrame.getContentPane().add(displayComponent); aFrame.setSize(new Dimension(640,640)); // Get it displayed aFrame.setVisible(true); // Main program now ends! // Don't worry, there is a new thread running in // the awt graphics library. it is this thread // that keeps the program running. } catch(Exception e) { System.out.println("Failed because : " + e) ; } } }class A2ExData
public class A2ExData { // Data object - would normally own some // collection of shapes; here there is only // one shape - the colored rectangle defined // by the private data members that set its (r,g,b) color // etc private DataPanel myDataPanel; private int dx; private int dy; private int rcol; private int gcol; private int bcol; private Random rgen; public A2ExData() { // Create some data. // In your "shapes" version, you should create a // LinkedList<Shape> and then create // a random collection of Shape objects of // different kinds rgen = new Random(); dx = 0; dy = 0; rcol = rgen.nextInt(255); gcol = rgen.nextInt(255); bcol = rgen.nextInt(255); } public void paint(Graphics g) { // In your "shapes" version, you will iterate through // a collection of shapes telling each to paint itself System.out.println("Painting the data object"); Dimension d = myDataPanel.getSize(); g.clearRect(0,0,(int)d.getWidth(),(int)d.getHeight()); g.setColor(new Color(rcol,gcol,bcol)); g.fillRect(40,40,60+dx,80+dy); g.setColor(Color.BLACK); } public void setLinkToDataPanel(DataPanel dp) { // When the data are changed, they have to // be redrawn; need to inform display - so // first need a link to the display myDataPanel = dp; } public void paintAgain() { // Change the data and request a repaint //System.out.println("Told to change data and repaint"); dx++; dy+=2; rcol = rgen.nextInt(255); gcol = rgen.nextInt(255); bcol = rgen.nextInt(255); myDataPanel.repaint(); // Request redraw after change if(dx>400) { dx = 0; dy = 0; } // In your shapes version, you will modify individual // shape objects or you could change the contents of // the collection }}class A2ExGUI
public class A2ExGUI extends JPanel { // Define the contents of Graphical User Interface // Placing a control panel on right and a larger data // display panel on left private DataPanel dp; private ControlPanel cp; private A2ExData myData; public A2ExGUI(A2ExData someData) { myData = someData; dp = new DataPanel(myData); cp = new ControlPanel(myData); cp.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(gbl); // Layout is 8x8; a 6x8 area for data display and a 2x8 area for controls // Data gets all of the x- expansion gbc.weightx = 100; gbc.weighty = 100; gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 8; gbc.gridwidth = 6; gbc.insets = new Insets(2,2,2,2); gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.BOTH; add(dp, gbc); gbc.weightx = 0; gbc.weighty = 100; gbc.gridx = 6; gbc.gridy = 0; gbc.gridheight = 8; gbc.gridwidth = 2; gbc.insets = new Insets(2,2,2,2); gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.BOTH; add(cp, gbc); }}class DataPanel
public class DataPanel extends JPanel { private A2ExData myData; public DataPanel(A2ExData theData) { myData = theData; myData.setLinkToDataPanel(this); } public void paint(Graphics g){ myData.paint(g); }}class ControlPanel
public class ControlPanel extends JPanel implements ActionListener { private A2ExData myData; private JButton paintButton; public ControlPanel(A2ExData theData) { myData = theData; GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(gbl); // Column header gbc.weightx = 0; gbc.weighty = 0; gbc.gridx = 0; gbc.gridy = 0; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.NONE; add(new JLabel("Control Panel"), gbc); // action button gbc.weightx = 0; gbc.weighty = 0; gbc.gridx = 0; gbc.gridy = 1; gbc.gridheight = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.NONE; paintButton = new JButton("Paint again"); add(paintButton, gbc); paintButton.addActionListener(this); // Filler panel to take space if display enlarged gbc.weightx = 0; gbc.weighty = 100; gbc.gridx = 0; gbc.gridy = 2; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.BOTH; add(new JPanel(), gbc); } public void actionPerformed(ActionEvent aev) { // User action - request a new set of data and new display myData.paintAgain(); } }
Basic graphic tasks
Shapes hierarchy
In this exercise you extend the graphics example.
paint
and move
memberfunctions and a set of shape subclasses that can paint different shapes(e.g. MyRect implements Shape, MyPoly implements Shape etc).You will have met this example in lectures.You can play with my version:
java -jar SpaceInvaders.jar
You should find the SpaceInvaders.jar file in /share/cs-pub/csci213/Lab2.
Code for the framework parts is supplied as a Windows zip file( available in /share/cs-pub/csci213/Lab2). This zip file contains the NetBeansproject.
The supplied framework builds a graphical user interface (GUI)and organizes the mechanism for running the game. The GUI displayis shown above. The display comprises a"game panel" and a "control panel". The game panel shows a"gun" that can be controlled by the player and the waves of attackingspace invaders of various types. The control panel holds a couple oftext output fields that contain regularly updated data with theplayers "score" and "status" and an action button that may be usedto start the game, or restart the game after the player's "gun" hasbeen destroyed by invaders.
The code comprises the following classes and interfaces:
class GameProgram
; the main function that creates theGame object, the GUI, links things up, and displays the interface. (Theclass also defines static utility functions for loading sound and image files.)class A2GUI
; this specialized subclass of javax.swing.JPanelbuilds the overall GUI with its GamePanel and ControlPanel components.class ControlPanel
; this specialized subclass of javax.swing.JPanelholds the text output fields and the action button. It has application specificmember functions that handle mouse clicks on the action button, update notificationsfrom the Game (telling it that it needs to reset the contents of output textfields), etc.class GamePanel
; another specialized subclass of javax.swing.JPanel. Thisdefines a display area where the Game will be drawn. It passes on paint requests to theGame. It also arranges for the Game object to be notified of mouse movements (thesechange the position of the "gun") and mouse click events (these represent triggeringfire from the "gun").class Game
; during play, the game object will own a heterogeneous collection ofpolymorphic references to various forms of space invader. It has a "run" function defined thatruns the game. This function has a loop that at regular intervals updates the position ofthe gun, handles any gun fire action, allows the invaders to move, and which sorts outminor issues like game score and status.class InvaderFactory
this class defines static methods thatare used by the Game object to obtain new invaders to replace any destroyed by gunfire.interface SpaceInvader
; the interface defining the signatures of thefunctions that are common to all class of SpaceInvader.SpaceInvader subclasses
; one supplied, others to be written for this exercise.has been generated for the supplied classes.
In this version of Invaders:
while(gun's status > 0){ checkInvaders - replace any invaders that were destroyed in last cycle adjustGunLocation - based on mouse motion events recorded separately fireGun - based on occurrence of mouse click recorded separately; this operation may damage or destroy an invader located above the gun (the first invader hit takes all damage and shields other invaders); for each invader in collection invader move check if invader is still "alive" - if not, remove it from the collection (some invaders die on hitting the ground, others may have finite lifetimes) check whether gun destroyed by this invader, terminate main loop as appropriate get control panel to update score and status values request a repaint of the game panel delay a little before next cycle through this loop}
Define, implement, and document some additional invader classesand integrate them with the framework code supplied.
The following are suggestions for possible invaders, but you can invent others withmore complex behaviours:
GIF/PNG/JPG images can be loaded and used for invaders. Additionalsounds can be loaded - only need to load each sound once - and usedwhen invaders move. Don't overdo sound effects oryour game will be intolerable. Invaders can obtain a reference to theGamePanel from the Game; this could be used if special graphicaleffects are required (effects similar to the gun fire effect). Thisreference to the GamePanel will be needed by any invader classes thatchoose to use "gif" or "jpeg" images rather than graphic drawingactions. Again, don't overdo the advanced graphical effects forthey can slow your game or otherwise make it unplayable.
Your explorations of the Java libraries should have introduced you toJavaDoc documentation. JavaDoc relies on a highly constrainedway of adding comments to your code; the javadoc
program readsyour code, finds these special comments, and generates HMTL reference pagesusing the same stylesheets as used for the Java libraries. JavaDoc istedious to use manually - but once again, an IDE can make things simple.
In the NetBeans/Project pane, right-click on the class that is tobe documented and pick Tools/Auto comment from the pop-up menu.The pane used to display code will be hidden by a new pane that allows youto add comments to a class.
Normally, only public members (and public classes) aredocumented. The editing display should show the class and those membersthat require documentation. A red check mark by an entry means that ithas no documentation; a yellow check mark indicates that documentation isin some way incomplete; a green check mark flags entries for whichJavaDoc style comments have been added.
Pick each entry in turn and supply the documentation. The class entryshould get a brief description of the role of the class. Functions shouldget documentation describing their role and detailing any input parameters andreturned result.After selecting a function that is to be documented, use the AutoCorrect button to add @param and @return tags (initially empty).
Compose JavaDoc documentation describing your subclasses of SpaceInvader.
Once you have added comments to all your classes, you can generate the HTML pages by selecting the project and using NetBean's Build/Generate JavaDocfor ...
menu option.