There is only one list here and it can hold objects of type Obstacle or any class that inherits (extends) from Obstacle, in this case Mushroom and Tree.
The actual list does not differentiate between Mushroom and Tree but we can tell the difference in our code. Java provides two different mechanisms.
Mechanism 1
Java provides the keyword instanceof
to test the type of an object. Assume we have a random object from the list (obs
) like this.
Obstacle ob = obs.get( (int) random(obs.size()) );
as far as the program is concerned ob
is an Obstacle, but we know it can be of type Mushroom or Tree and we can test it like this
if(ob instanceof Mushroom){
// eat it
}
if(ob instanceof Tree){
// don't eat it
}
Mechanism 2 (Preferred)
Since we are using an object orientated (OO) language we can make use of inheritance and polymorphism. We have already used inheritance where Obstacle is a parent class with two child classes Mushroom and Tree. Now we need to use polymorphism
Lets consider adding a new method to our classes, in Mushroom we add
public boolean isEdible() {
return true;
}
and in Tree
public boolean isEdible() {
return false;
}
To make this work the method must also appear in the Obstacle class but do we return true or false? In this case neither, since we never want to create instances of Obstacle we make the class abstract like this
abstract public class Obstacle {
This will prevent an obstacle object being created so the statement obstacle = new Obstacle()
would be rejected by the compiler.
Now our class is abstract we can add an abstract method
abstract public boolean isEdible();
Notice that the method has no definition it is simply a declaration.
So now we get to the good bit - polymorphism.
Start again with a random object from our list
Obstacle ob = obs.get( (int) random(obs.size()) );
What gets returned when we try this
ob.isEdible()
Java will determine the actual class of ob
and call the method in that class. So if it is a Mushroom it returns true, if it is a Tree it returns false.
So time to see it in action, I have modified the previous sketch to include these changes. When the mouse button is clicked it calls a new method in the World class called collision()
which simply selects an obstacle at random and if edible removes it from the list.
World w;
public void settings() {
size(400, 400);
}
public void setup() {
w = new World(this);
w.makeObstacles(20, 40);
}
public void draw() {
w.render();
}
public void mouseClicked() {
w.collision();
}
public class World {
PApplet p;
ArrayList<Obstacle> obs = new ArrayList<Obstacle>();
public World(PApplet parent) {
p = parent;
}
// This is a dummy method to demonstrate polymorphism
public void collision() {
// Pick one obstacle at random from
Obstacle ob = obs.get( (int) random(obs.size()) );
// If edible eat it :-)
if (ob.isEdible()) {
obs.remove(ob);
print("A " + ob.getClass().getSimpleName().toLowerCase());
println(" was consumed after " + millis() + " milliseconds");
}
}
public void makeObstacles(int nbrTrees, int nbrMushrooms) {
for (int i = 0; i < nbrTrees + nbrMushrooms; i++) {
PVector next = findSuitablePosition(100);
if (next == null) {
break;
}
if (i < nbrTrees) {
obs.add(new Tree(p, next));
} else {
obs.add(new Mushroom(p, next));
}
}
}
private PVector findSuitablePosition(int maxTime) {
PVector pp;
boolean found;
int time = p.millis();
do {
found = true; // Assume it will not overlap
// Random position avoiding very edges of world
pp = new PVector(20 + p.random(p.width - 40), 20 + p.random(p.height - 40));
// See if it overlaps
for (Obstacle ob : obs) {
if (PVector.dist(ob.pos, pp) < 32) {
found = false; // overlap found
break;
}
}
} while (!found && p.millis() - time <= maxTime); // continue until no overlap
return found ? pp : null; // return nul if taking too long
}
public void render() {
p.pushMatrix();
p.pushStyle();
p.fill(200, 255, 200);
p.rect(0, 0, p.width, p.height);
for (Obstacle ob : obs) {
ob.render();
}
p.popStyle();
p.popMatrix();
}
}
abstract public class Obstacle {
PApplet p;
PVector pos = new PVector();
int col;
int diam;
Obstacle(PApplet parent, PVector pos) {
p = parent;
this.pos.set(pos);
}
public void render() {
p.pushMatrix();
p.pushStyle();
p.noStroke();
p.fill(col);
p.translate(pos.x, pos.y);
p.ellipse(0, 0, diam, diam);
p.popStyle();
p.popMatrix();
}
abstract public boolean isEdible();
}
public class Tree extends Obstacle {
public Tree(PApplet p, PVector pos) {
super(p, pos);
diam = 16;
// Note you cannot use p.color(0,176, 0) bug in Processing
col = 0xFF00AA00; // darkish green
}
public boolean isEdible() {
return false;
}
}
public class Mushroom extends Obstacle {
public Mushroom(PApplet p, PVector pos) {
super(p, pos);
diam = 6;
// Note you cannot use p.color(0,176, 00
col = 0xFF667722; // some other colour
}
public boolean isEdible() {
return true;
}
}