HW4 Solutions Page
Return to
homework page.
UML Diagram of HW4
________________ 1 1___________________ 1 *______________
| hw4 |<#>----|WorldPanel |<#>-----|Creature |
|______________| |_________________| |____________|
| addCreature()+--- |creatures | |animate() |
| | | |_________________| |____________|
| | | |+animate() | | |___________
| | | |+paintComponent()+----| | |Victim |
| | | |-eatVictim() +----| | |__________|
| | | |_________________| |___________ | |animate() |
| updateWorld()| _|________________________ / Both use | | |__________|
|_+____________|/ Calls CreatureFactory's | | Iterator's| |
| | create() method to make | | to iterate| |__________
| | a Creature. Then calls | | over | |Monster |
| | WorldPanel's addCreature| | LinkedList| |_________|
| | method with this | | creatures | |animate()|
| | Creature. | |___________| |_________|
| |_________________________|
_|___________________
/ |
| Calls WorldPanel's |
| animate() method |
| Called by a Timer |
| on hw4 each second |
|____________________|
The Factory pattern is used in the class CreatureFactory's
create method to have one centralized place to do the creation of
all the Creature's. I didn't use an AbstractFactory class which I extended
in my example.
The Strategy pattern was used in the animate() part of the diagram
concerning the WorldPanel and the abstract Creature class
and its two concrete subclasses Monster and Victim. The animate()
method of the WorldPanel calls the animate method of the Creature's
it has on it.
The Iterator pattern is used in WorldPanel's paintComponent and
eatVictim methods to cycle through the LinkedList of Creature's.
Here are the two GIF files I used in writing HW4:
// hw4.java
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
/**
A monster vs victims simulator
Run from the command line as:
java hw4
@author Chris Pollett
@version 1.0
*/
public class hw4 extends JFrame
{
/**
Creates our frame, sets up buttons, WorldPanel, listeners, sets up
and starts animation Timer.
*/
public hw4()
{
super("HW4 - Monster vs. Victims Simulation");
containerSetup();
timerSetup();
}
/*
Sets up the contentPane of our frame for the simulation.
Sets background to gray, initializes buttons and listeners.
Sets up WorldPanel.
*/
void containerSetup()
{
Container c = getContentPane();
c.setLayout(new BorderLayout());
c.setBackground(Color.gray);
world= new WorldPanel();
c.add(world, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
JButton monsterButton = new JButton("Add Monster");
monsterButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
addCreature("Monster");
}
}
);
buttonPanel.add(monsterButton);
JButton victimButton = new JButton("Add Victim");
victimButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
addCreature("Victim");
}
}
);
buttonPanel.add(victimButton);
c.add(buttonPanel, BorderLayout.SOUTH);
}
/*
Sets up the timer used to control the animation of the world
*/
void timerSetup()
{
timer=new Timer(REFRESH_RATE, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
updateWorld();
}
}
);
timer.start();
}
/*
Called when either the Add Monster or Add Victim button
pressed. Get x and y of where user wants creature then puts
a new Creature there.
*/
void addCreature(String type)
{
timer.stop();
int x;
int y;
String xString = JOptionPane.showInputDialog("Enter x value of "
+ type +":");
if( xString == null ) return;
else x = Integer.parseInt(xString);
String yString = JOptionPane.showInputDialog("Enter y value of "
+ type +":");
if( yString == null ) return;
else y = Integer.parseInt(yString);
Creature creature = CreatureFactory.create(type, x, y);
world.addCreature(creature);
repaint();
timer.start();
}
/*
Called by timer's ActionListener to update the animation
and redraw the screen.
*/
void updateWorld()
{
/* these two lines are the reverse of what's described in the
HW spec, but I believe this order makes more sense.
If you do it either way you should receive full credit.
*/
world.animate();
repaint();
}
/*
creates our application frame and sets closing behavior.
*/
public static void main(String args[])
{
hw4 frame = new hw4();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.show();
}
Timer timer; // used to control the animation
WorldPanel world; // where the simulation takes place
public static final int REFRESH_RATE = 1000; /* number of mS between
updates of animation */
}
//WorldPanel.java
import java.awt.*;
import javax.swing.*;
import java.util.*;
/**
Panel on which monster vs victim simulation carried out
*/
public class WorldPanel extends JPanel
{
/**
Initializes the list of creatures and set the background to gray
*/
public WorldPanel()
{
creatures = new LinkedList();
setBackground(Color.gray);
}
/**
Adds a creature to the WorldPanel
@param c - Creature we are adding.
*/
public void addCreature(Creature c)
{
creatures.add(c);
}
/**
moves each of the Creature's on our panel
*/
public void animate()
{
Iterator iter = creatures.iterator();
Creature c;
while(iter.hasNext())
{
c = (Creature)iter.next();
c.animate();
}
}
/**
Draws the WorldPanel. Called after repaint events.
@param g - Graphics context on which drawing done.
*/
public void paintComponent(Graphics g)
{
g.setColor(Color.blue);
for(int i = 0; i < DEFAULT_X_TILES; i++)
{
for(int j = 0; j < DEFAULT_Y_TILES; j++)
{
g.fillRect(i*TILE_X_SIZE, j*TILE_Y_SIZE,
RECT_X_SIZE, RECT_Y_SIZE);
}
}
eatVictims();
Creature c;
Iterator iter = creatures.iterator();
while(iter.hasNext())
{
c = (Creature)iter.next();
c.drawCreature(g, c.x*TILE_X_SIZE, c.y*TILE_Y_SIZE);
}
}
/**
This function is called by JFrame this panel lives on
to determine the size is should set aside for this component.
@return - preferred Dimension of this panel
*/
public Dimension getPreferredSize()
{
return new Dimension(DEFAULT_X_TILES*TILE_X_SIZE,
DEFAULT_Y_TILES*TILE_Y_SIZE);
}
/*
Used to delete any Victim that's on the same square as a Monster
*/
void eatVictims()
{
Iterator iter = creatures.iterator();
Iterator iter2;
Creature c;
Creature c2;
/* first we mark Victims to be deleted
(hard to use remove() when have more that one iterator
active on a LinkedList.)
*/
iter = creatures.iterator();
while(iter.hasNext())
{
c = (Creature)iter.next();
if(c instanceof Monster)
{
iter2 = creatures.iterator();
while(iter2.hasNext())
{
c2 = (Creature)iter2.next();
if(c2 instanceof Victim && c2.x == c.x && c2.y ==c.y)
{
c2.x = -1; // mark Victim
}
}
}
}
iter = creatures.iterator();
while(iter.hasNext())
{
c = (Creature)iter.next();
if(c.x == -1) iter.remove(); // now delete
}
}
LinkedList creatures; // list of creatures on WorldPanel
public static final int DEFAULT_X_TILES = 20; //number of horizontal tiles
public static final int DEFAULT_Y_TILES = 20; //number of vertical tiles
public static final int TILE_X_SIZE = 20; // x pixels set aside for a tile
public static final int TILE_Y_SIZE = 20; // y pixels set aside for a tile
public static final int RECT_X_SIZE = 16; // x pixels used to draw tile
public static final int RECT_Y_SIZE = 16; // y pixels used to draw tile
}
//CreatureFactory.java
/**
Factory class for Creature objects use in Monster vs Victim simulation
*/
public class CreatureFactory
{
/**
This function is used to make the Creature's specified by
the parameters.
@param type - the type of the creature
@param x - the x tile of the creature
@param y - the y tile of the creature
@return Creature - the creature we create
*/
public static Creature create(String type, int x, int y)
{
if(type.equals("Monster"))
return new Monster(x,y);
if(type.equals("Victim"))
return new Victim(x,y);
return null;
}
}
//Creature.java
import javax.swing.*;
import java.awt.*;
/**
Encapsulates the notion of a creature that can appear in our
Monster vs Victim simulation. i.e., a Monster or a Victim
*/
public abstract class Creature
{
/**
Create a Creature object at x, y with image from the file named s
@param s - String of filename with image of Creature
@param x - horizontal coordinate of tile the creature lives on
@param y - vertital coordinate of tile the creature lives on
*/
public Creature(String s, int x, int y)
{
this.x = x;
this.y = y;
image = new ImageIcon(s);
}
/**
Draws the current Creature to the specified graphics context
at the specified location.
@param g - the Graphics object on which we are drawing
@param x - the x position in pixels at which we draw the creature
@param y - the y position in pixels at which we draw the creature
*/
public void drawCreature(Graphics g, int x, int y)
{
image.paintIcon(null, g, x, y);
}
/**
This method is overriden to update the creatures position
according to some rule.
*/
public abstract void animate();
public int x; // the x tile the creature is on
public int y; // the y tile the creature is on
protected ImageIcon image; // the image used to draw the creature
}
//Monster.java
/**
Used to draw, animate, and keep track of a Monster's in our
Monsters vs. Victims simulation
*/
public class Monster extends Creature
{
/**
creates a monster at tile (x,y)
*/
public Monster(int x, int y)
{
super("monster.gif", x, y);
}
/**
In one turn a monster moves -2, -1, 0, 1, 2 horizontally
-2, -1, 0, 1, 2 vertically
If it goes off the world it wraps to the other side.
*/
public void animate()
{
x += ((int)(Math.random()*X_DELTA)) - X_OFFSET;
if(x < 0 ) x = WorldPanel.DEFAULT_X_TILES-1;
if(x >= WorldPanel.DEFAULT_X_TILES ) x = 0;
y += ((int)(Math.random()*Y_DELTA)) - Y_OFFSET;
if(y < 0 ) y = WorldPanel.DEFAULT_Y_TILES-1;
if(y >= WorldPanel.DEFAULT_Y_TILES ) y = 0;
}
public static int X_DELTA = 7; // in a turn x changes by at most a value < 3
public static int X_OFFSET = 3;// subtracted from amount x changes
public static int Y_DELTA = 7; // in a turn y changes by at most a value < 3
public static int Y_OFFSET = 3;// subtracted from amount y changes
}
//Victim.java
/**
Used to draw, animate, and keep track of a Victim's in our
Monsters vs. Victims simulation
*/
public class Victim extends Creature
{
/**
creates a Victim at tile (x,y)
*/
public Victim(int x, int y)
{
super("victim.gif", x, y);
}
/**
In one turn a Victim moves -1, 0, 1 horizontally
-1, 0, 1 vertically
If it goes off the world it wraps to the other side.
*/
public void animate()
{
x += ((int)(Math.random()*X_DELTA)) - X_OFFSET;
if(x < 0 ) x = WorldPanel.DEFAULT_X_TILES-1;
if(x >= WorldPanel.DEFAULT_X_TILES ) x = 0;
y += ((int)(Math.random()*Y_DELTA)) - Y_OFFSET;
if(y < 0 ) y = WorldPanel.DEFAULT_Y_TILES-1;
if(y >= WorldPanel.DEFAULT_Y_TILES ) y = 0;
}
public static int X_DELTA = 3; // in a turn x changes by at most a value < 3
public static int X_OFFSET = 1;// subtracted from amount x changes
public static int Y_DELTA = 3; // in a turn y changes by at most a value < 3
public static int Y_OFFSET = 1;// subtracted from amount y changes
}