The magazine of the Melbourne PC User Group
Karel Heads For the Stars
Trevor Gosbell |
|
|
Trevor Gosbell gives us the concluding episode of this fascinating series on
programming with Java |
In the previous episodes we made increasingly sophisticated square-making
programs. In this final episode we will leave boring squares behind and make a
star shape. To do this, we will make two new robot classes - one that makes a x
and one that makes a +. Put the two patterns on top of each other and you get a
crisscross shape (Figure 1), which the generous and/or imaginative reader will
agree looks a bit like a star (but you might need to squint a bit).
As usual, all of the code in this article can be downloaded from the author's
Web page on the Melb PC server - http://member.melbpc.org.au/~tgosbell.
Make a Shape (any shape)
Firstly, we need to make ourselves an improved base model robot. We don't want
to reinvent the turnRight() method for every new robot class we write, so let's
make a more capable robot that we can use as the basis for others. See
Listing 1
on this page for the ShapeMaker class. There's not too much new here so
hopefully you get the general idea from the inline comments. |

Figure 1 |
Listing 1
import kareltherobot.*;
// ShapeMaker adds some extra
// moves to your basic Robot
public abstract class ShapeMaker
extends Robot
{
public ShapeMaker(int street,
int avenue,
Direction facing,
int beepers)
{
super(street, avenue,
facing, beepers);
}
// To turn right, turn
// left three times
public void turnRight()
{
turnLeft();
turnLeft();
turnLeft();
}
// To turn around,
// turn left twice
public void turnAround()
{
turnLeft();
turnLeft();
}
// Move one space forward
// and one to the right
public void diagonalRight()
{
move();
turnRight();
move();
turnLeft();
}
// Move one space forward
// and one to the left
public void diagonalLeft()
{
move();
turnLeft();
move();
turnRight();
}
// Every ShapeMaker type robot
// must define a makeShape() method
public abstract void makeShape();
} |
However there is one important difference - this is an "abstract" class. See
that the class definition includes the keyword abstract:
public abstract class ShapeMaker
The definition for makeShape() is also abstract.
public abstract void makeShape();
What's more, this method doesn't have any code. All of the other methods have a
block of code between curly brackets {like this}. But the makeShape() method is
missing this.
You might be surprised to learn that we can't use ShapeMaker to make robots, but
we can still extend it to make new classes. Because it is an abstract classes,
any class that extends ShapeMaker must provide its own makeShape() method. That
might seem a bit pointless, but we'll soon see that it's actually quite useful.
Listing 2 shows our first use of ShapeMaker. In this case, a call to the
makeShape() method causes the robot to make four diagonal arms, then return to
the centre. makeShape() calls the makeDiagonal() method to actually make the
diagonals.
Listing 2
public class XMaker
extends ShapeMaker
{
public XMaker(int street,
int avenue,
Direction facing,
int beepers)
{
super(street, avenue,
facing, beepers);
}
public void makeShape()
{
makeDiagonal();
turnLeft();
makeDiagonal();
turnLeft();
makeDiagonal();
turnLeft();
makeDiagonal();
turnOff();
}
public void makeDiagonal()
{
diagonalRight();
putBeeper();
diagonalRight();
putBeeper();
diagonalRight();
putBeeper();
turnAround();
diagonalRight();
diagonalRight();
diagonalRight();
}
} |
A Test Run
It's time to make a test run - the robot's task is in
Listing 3. Compile this,
make a new Example04 object and run the task() method - the results should look
like Figure 2.
|
Listing 3
import kareltherobot.*;
public class Example04
implements RobotTask
{
public void task()
{
World.reset();
World.setTrace(false);
World.setVisible(true);
// make a new ShapeMaker name
ShapeMaker asterisk = null;
// make a new XMaker robot
// and call it asterisk
asterisk = new XMaker(5, 5, West, 13);
// asterisk makes its shape
asterisk.makeShape();
}
|

Figure 2. |
Have another look at Listing 3. This should look pretty familiar by now, except
for a one odd thing. Notice that the name asterisk is of ShapeMaker type:
ShapeMaker asterisk = null;
But a few lines later we use the name asterisk on an XMaker type robot:
asterisk = new XMaker(5, 5, West, 13);
They don't match! Surely this can't work?
|
Actually it can. Figure 3 might help to clarify. The solid line arrows show the
"extends" relationship between the robot classes: XMaker extends
ShapeMaker. (Incidentally, ShapeMaker extends Robot, but that isn't shown here.)
in object-oriented terminology, this is often referred to as an "is-a"
relationship, that is: XMaker is a ShapeMaker, and ShapeMaker is a
Robot.
Through inheritance all of the methods of Robot are available in ShapeMaker, and
the Robot methods plus the methods added by ShapeMaker are available in
XMaker.
So it is entirely true the robot made by the command
new XMaker(5, 5, West, 13)
is a ShapeMaker, so it's OK to use give it the ShapeMaker-type name
asterisk. |

Figure 3 |
But here's the rub - because asterisk is a ShapeMaker type name, it knows only
about methods that ShapeMaker supplies. XMaker adds the makeDiagonal() method
but if we try to get asterisk to use it, the program will not compile. You
should try this - change the call
asterisk.makeShape();
to this
asterisk.makeDiagonal();
and when you try to compile, you will get the error message "cannot resolve
symbol - method makeDiagonal()". Fortunately we don't need to call
makeDiagonal()
directly, so it's OK for asterisk to use XMaker.
Get Cross
We're only halfway there. We have a x and now we need a +. One thing I noticed
about XMaker is that it takes an awfully long time to walk up and down four
diagonals. I think we can make the cross much faster if we get a team of four
robots to make one arm each. So we'll need a LineMaker robot template (Listing
4), in which the makeShape() method simply makes a straight line of three
beepers.
So far so good. Now we need to put them together to make a cross - enter
CrossMaker (Listing 5). Firstly notice that CrossMaker extends LineMaker, and as
mentioned above this means that CrossMaker is a LineMaker class (we're just
adding some extra stuff to the standard LineMaker).
| |
Listing 4
public class LineMaker
extends ShapeMaker
{
public LineMaker(int street,
int avenue,
Direction facing,
int beepers)
{
super(street, avenue,
facing, beepers);
}
public void makeShape()
{
move();
putBeeper();
move();
putBeeper();
move();
putBeeper();
move();
turnOff();
}
}
|
|
Listing 5
public class CrossMaker
extends LineMaker
{
public CrossMaker(int street,
int avenue,
Direction facing,
int beepers)
{
super(street, avenue, West, 3);
}
public void makeShape()
{
LineMaker firstLine =
new LineMaker(5, 5, North, 3);
LineMaker secondLine =
new LineMaker(5, 5, East, 3);
LineMaker thirdLine =
new LineMaker(5, 5, South, 3);
firstLine.makeShape();
secondLine.makeShape();
thirdLine.makeShape();
super.makeShape();
}
}
|
|
The teamwork strategy is found in the makeShape() method. Here the CrossMaker
type robot will get three of its LineMaker mates to make the first three lines
firstLine.makeShape();
secondLine.makeShape();
thirdLine.makeShape();
These three robots are made facing North, East, and South respectively, so a
call to their makeShape() method causes them to march off making lines in those
three directions.
Now what about the fourth arm of this cross? We could have used a fourth
LineMaker robot, but CrossMaker already is a LineMaker so we should put it to
good use. Here's how:
super.makeShape();
Super Methods
This section is a bit mind twisting, so feel free to skip it if you like.
It's puzzling. If CrossMaker is a LineMaker, why can't we just call its
makeShape() method just like the other three LineMaker robots? In other words,
why doesn't this line look like the following?
makeShape();
|
Figure 4 may help explain. Both CrossMaker and LineMaker have makeShape()
methods. So there is a potential dilemma here - if we call makeShape() inside
CrossMaker which method gets activated? Actually, it's quite simple - Java
always activates the "nearest" method. From inside CrossMaker the
CrossMaker.makeShape() method is closer than the LineMaker.makeShape() method. |

Figure 4 |
In listing 5 the problem is that we don't want this behaviour - we actually do
want to activate the LineMaker.makeShape() method. As you might expect, there is
a way around the rule - we just say that we want the method that is in the
superclass. And what is the superclass of CrossMaker? LineMaker of course! So
when we say
super.makeShape();
It means, ignore the closest makeShape() method and go for the one in the
superclass.
A star at last!
Finally we get to make a complete star. Listing 6 shows the revised Example04
class and Figure 5 shows the relationship of all the classes.
|
Listing 6
import kareltherobot.*;
public class Example04
implements RobotTask
{
public void task()
{
World.reset();
World.setTrace(false);
World.setVisible(true);
// make a new ShapeMaker name
ShapeMaker asterisk = null;
// make a new XMaker robot
// and call it asterisk
asterisk = new XMaker(5,5,West,13);
// asterisk makes its shape
asterisk.makeShape();
// make a new CrossMaker robot
// and call it asterisk
asterisk=new CrossMaker(5,5,West,3);
// asterisk makes its shape again
asterisk.makeShape();
}
|

Figure 5 |
|

Figure 6 |
It should come as no surprise that we have been able to reuse asterisk to name
the CrossMaker robot. Both XMaker and CrossMaker inherit from
ShapeMaker, so
they must have the makeShape() method (if they didn't the program would not
compile).
This is the great power of abstract classes - that they remove a lot of
decision-making from the program. Tell your robot to makeShape() and it goes
ahead and makes its shape, whatever that may be. It would be possible to make
subclasses of ShapeMaker that produce all sorts of shapes and patterns in the
makeShape() method and asterisk could be used to refer to all of them without
needing to know what the shape actually is.
Compile the program, make a new Example04 object and run it's task() method and
the output should look like Figure 1. Mission accomplished!
Wrap Up
In this episode we covered the following points:
- Extending a class creates an "is-a" relationship between the subclass and the
superclass.
- Abstract classes can't be instantiated to objects, they can only be extended by
other classes.
- Abstract methods exist to ensure that subclasses include those methods.
- The super keyword is used to force method calls to go to the superclass.
Karel in
Review
As this is the last in the series on Karel J Robot, let’s take a look at
where we’ve been.
Coding, compiling and running
- Use of BlueJ for writing, compiling, and running our robot programs.
- Different programs can produce the same results.
- Comments can be included in code using the // symbol.
Object Concepts
- A class is a blueprint for the objects it produces, and many objects can
be produced from one class.
- In the last episode we also discovered that abstract classes are
blueprints for other classes.
- A subclass inherits the functionality of the superclass.
- A constructor is a special method that is called automatically each time
an object is made based on a class.
Errors
- Syntax errors are errors of spelling or grammar, and run-time errors are
errors in the meaning of the program.
|
Reprinted from the March 2004 issue of PC Update, the magazine of Melbourne PC User Group, Australia
|