The magazine of the Melbourne PC User Group
Teaching A Young Robot New Tricks - Part 2
Trevor Gosbell |
|
|
Trevor
Gosbell continues his captivating Java programming tutorial and presents
“Programming with Karel, Part 2.” |
In Part 1 of this article [see:
http://www.melbpc.org.au/pcupdate/2310/2310article9.htm], I left readers
with the task of modifying the example program to make the robot close the
L-shape into a square as shown in Figure 1. Listing 1 shows a solution to the
problem.
There is an awful lot of repetition here; in fact the following sequence
is repeated three times:
karel.putBeeper();
karel.move();
karel.move();
karel.move();
So of the eighteen instructions given to karel, twelve are covered by
this sequence. If there's one thing a programmer learns to hate, it's
repetition. Doing things reliably over and over again is something for
which computers were made and humans were not. |

Figure 1. Karel turning an L into a square. |
Wouldn't it be good if we could tell the robot once how to do something, then
later just say "do the same thing we told you to do before"? That would save a
lot of typing (and typos!). In this article we will do just that -we will make a
robot that knows how to make a square. But first we have to come to grips with
the concept of a class.
It's All Class
Listing 1.
import kareltherobot.*;
public class Example02 implements RobotTask
{
public void task()
{
World.reset();
World.setTrace(false);
World.setVisible(true);
Robot karel = new Robot(1, 2, East, 4);
karel.move();
karel.putBeeper();
karel.move();
karel.move();
karel.move();
karel.turnLeft();
karel.putBeeper();
karel.move();
karel.move();
karel.move();
karel.turnLeft();
karel.putBeeper();
karel.move();
karel.move();
karel.move();
karel.putBeeper();
karel.move();
karel.move();
}
} |
Consider the following line from Listing 1
Robot karel = new Robot(1, 2, East, 4);
We discussed this in Part 1 - it's the command for making a new robot. But we
need to be a bit more precise about it now.
Robot
is something called a "class". A class is a blueprint - it describes what the
robot is like and its capabilities such as moving, turning, and picking up and
putting down beepers. The capabilities of a class are known as "methods" and as
we have seen they are indicated with a pair of brackets immediately after the
method name.
When we issue the instruction
new Robot
an object is made according to the Robot class or blueprint. We give this new
object the name
karel. And we use the Robot class name
again to indicate that the name karel doesn't refer to a bicycle or any other
type of object -
karel
refers to an object that was made according to the
Robot
blueprint. As a Robot type of object,
karel
has all of the methods that are outlined in the
Robot
class.
The
Robot
class doesn't just appear out of thin air - it's stored in a package called
kareltherobot. The purpose of the first
line of Listing 1 is to "import" the
kareltherobot
package and make the
Robot
class available. It's handy to have the
kareltherobot
package with its
Robot
class because now we want to adjust our robot a bit and we need to alter the
plans. But we don't alter the original blueprint, we make a copy and add some
alterations to the copy.
Let's say we want a robot with an extra method called
makeSide()
that will replace the repetitive sequence of commands above. The
makeSide()
method would look like this:
public void makeSide()
{
putBeeper();
move();
move();
move();
}
If we can make this work, the eighteen instructions to
karel
in Listing 1 reduce to the following nine instructions:
karel.move();
karel.makeSide();
karel.turnLeft();
karel.makeSide();
karel.turnLeft();
karel.makeSide();
karel.putBeeper();
karel.move();
karel.move();
Extend Yourself
Or rather, extend your robot because that is how we teach this robot its new
trick. Listing 2 has the definition of a new class,
SquareMaker, that provides the
makeSide()
method.
Looking at it line-by-line, firstly we need to import
kareltherobot
- remember that's where we keep the class definition for the
Robot
class.
The next line is where we name the class
SquareMaker
and state that is extends
Robot. It's easy to see why the keyword
extend
is used: imagine you're touring display homes looking for a house when you find
one that's exactly right except it only has three bedrooms and you want four.
You would say to the builder build the house according to that plan and add
on an extra bedroom at the back. Now the builder isn't going to redraw the
whole plan from scratch - it makes much more sense to copy the original plan and
draw in the extra room. The end result is a new plan. And that's just what
happens here - we don't need to rewrite the whole Robot class rather we borrow
everything from the
Robot
class then add on an extra method or two to make a the new class:
SquareMaker
(see Figure 2).
|

Figure 2. Most of the methods in SquareMaker are inherited from Robot |
Jargon
There are a few jargon words used to describe the type of relationship between
SquareMaker
and
Robot:
subclass
SquareMaker is a "subclass" of
Robot
superclass
Robot is the "superclass" of
SquareMaker
inheritance
SquareMaker
"inherits" all the functionality of
Robot
by virtue of the
extends
command
We've already discussed the purpose of
makeSide()
but a question still remains. Previously whenever we've used the methods
move()and
putBeeper()
we have had to include the robot's name, like this:
karel.move();
Why don't we need to do that here?
Looking at Figure 2 you can see that all of the Robot methods are available
inside
SquareMaker. Because
makeSide()
is just another method in
SquareMaker, all of the other methods
(including
move()
and
putBeeper()) can be referenced directly.
Construction Zone
So much for
makeSide()
but what's this strange
SquareMaker()
method? It has two distinguishing features - it has the same name as the class
and there are a whole stack of words inside its brackets. Because
SquareMaker()
has the same name as the class we can tell that it's a special kind of method
called a constructor and it kicks-in automatically whenever a new object is made
from this class. Every class has a constructor that sets up things when a new
object. For example, the
Robot
class also has a constructor (which is called
Robot()) and it gets triggered on the
following line from Listing 1:
Robot karel = new Robot(1, 2, East, 4);
This gives us a clue to what the extra words in
SquareMaker()'s brackets are for. In the
code fragment above there are four things we tell the new robot - you may
remember from Part 1 that these are four things the robot needs to start: a
location (the first two numbers indicate the intersection by street and avenue
number), then the direction the robot faces, and finally the number of beepers
being carried. If you look at
SquareMaker() in Listing 2 you'll see
that's exactly the information that this method expects - int street ("int" is
short for integer), int avenue, Direction facing, and int beepers.
Let's look at the body of
SquareMaker()
- it is the single line between the curly brackets:
super(street, avenue, facing, beepers);
This is a really terrific feature of class inheritance - we simply take these
four values and pass them to the constructor method in the superclass using the
super()
command. We don't need to reinvent anything here - the
Robot
class already has the code for preparing a robot, so we might as well reuse it.
Sounds lazy doesn't it? Well, it is - sometimes it's clever to be lazy.
Putting SquareMaker To Use
OK, now that we have a new type of robot, we need to adjust our original program
to use it. Download the examples for this article (karel-example02.zip) from
http://member.melbpc.org.au/~tgosbell/ and unzip them into your Working
folder. Now start up BlueJ and open project example02a. Double-click on the
Example02 box to check that this is the same code as shown in Listing 1.
Now we need to add our new
SquareMaker
class, so click the New Class... button on the left menu and in the dialog box
enter "SquareMaker"
as the new class name (see Figure 3) and click OK. We now have a new
SquareMaker
box on the BlueJ project window - note that it has a striped pattern on it to
indicate that it is not yet compiled (see figure 4). Double-click on the
SquareMaker box and you will find that BlueJ has automatically filled-in some
code for us. Unfortunately it's heaps more than we need right now, so delete it
all and type in the code from Listing 2. Compile the code and close the text
editor to return to the project window.
|

Figure 3. Making a new class. |

Figure 4. SquareMaker before compiling. |
We also need to adjust the code in the Example02 box, so open it up and alter
the code so that it looks like Listing 3.
Listing 3.
import kareltherobot.*;
public class Example02 implements RobotTask
{
public void task()
{
World.reset();
World.setTrace(false);
World.setVisible(true);
SquareMaker karel = new SquareMaker(1, 2, East, 4);
karel.move();
karel.makeSide();
karel.turnLeft();
karel.makeSide();
karel.turnLeft();
karel.makeSide();
karel.putBeeper();
karel.move();
karel.move();
}
} |
Notice that
karel
is no longer declared to be an object of
Robot
type - it is
now a
SquareMaker. But the procedure for making a new
SquareMaker
is otherwise
the same as making a new
Robot. Also the set of instructions to
karel
is now
half as long (as predicted above). Compile Example02 and close down the text
editor.
|
Back in the project window things have changed a little. You should see a dotted
arrow going from
Example03
to
SquareMaker
as in Figure 5 (you might need to move
the
SquareMaker
box a bit). This indicates that
Example03
uses
SquareMaker. |

Figure 5. SquareMaker is used in Example02. |
Test your work in the same way as in the previous article: right-click on the
Example02
box and select new
Example02(), give the new instance a name such as
"test". When the "test" box appears at the bottom, right-click it and select
void task(). All being well, the robot should lay out a square of beepers as
shown in Figure 1.
Getting Fussy Now
Now I don't want to sound too fussy, but the code in Listing 3 still looks a bit
repetitive to me. How about if we edit
SquareMaker
to look like Listing 4?
Listing 4.
import kareltherobot.*;
public class SquareMaker extends Robot
{
public SquareMaker(int street, int avenue, Direction
facing, int
beepers)
{
super(street, avenue, facing, beepers);
}
public void makeSide()
{
putBeeper();
move();
move();
move();
}
public void makeSquare()
{
makeSide();
turnLeft();
makeSide();
turnLeft();
makeSide();
putBeeper();
}
} |
The new method
makeSquare()
takes everything in
Example02
that deals with making
the square and puts it where it belongs - in
SquareMaker. (Figure 6 shows the
list of methods now available in this class.) This means that we can adjust
Example02
to look like Listing 5.
|

Figure 6. An extra method for SquareMaker. |
Use BlueJ to make these changes, compile and see how it runs. Does it still
produce the same square? (I hope so!)
Listing 5.
import kareltherobot.*;
public class Example02 implements RobotTask
{
public void task()
{
World.reset();
World.setTrace(false);
World.setVisible(true);
SquareMaker karel = new SquareMaker(1, 2, East, 4);
karel.move();
karel.makeSquare();
karel.move();
karel.move();
}
} |
Why All This Bother?
All being well every revision of the program in this article, from the first
version in Listing 1 to the last in Listing 4 and Listing 5, produces the same
result. They are functionally equivalent, so why have we bothered to make these
changes?
If we can be certain that the
makeSquare()
method makes a 3x3 square every time
we ask it to, then this is the last time we need to worry about coding a 3x3
square. Whenever we want a square all we need to do is move our robot to where
we want the square and tell it to
makeSquare().
Look at it this way, suppose we wanted the robot to make two squares, one on top
of each other. Now if all we had to work on was Listing 1, then we would need to
rewrite (or copy) all of the square-making code - the result would be very long
and confusing. But using the revisions in Listings 4 and 5 all that we would
need to do is make a few moves and turns then call
makeSquare()
again.
Now suppose we wanted our two squares to be 4x4 instead of 3x3. If we had
altered Listing 1, we would have to make at least six changes but in Listing 4
we would only need to make one change. So although our final modification looks
more complicated, it actually makes things simpler for us. Having solved the
square-making problem once, we never need to solve it again. And that makes a
lot of sense.
Some Tasks For You
Double-decker squares: Adjust listing 5 to make the double-decker square shown
in figure 7. Then adjust listing 1 to produce the same result.
Variations on the theme: Take your two solutions from the previous exercise and
change them so that the squares have 4x4 dimensions. Now add a third square on
top.
A harder task: Extend Robot to make a new class that has the following methods:
turns the robot to face the right
putBeeperDiagonal()
puts down a beeper then moves one space "diagonally" to the north and east.
makeDiagonal() makes the diagonal line of four beepers shown in figure 8.
Test your new robot using an example program that only calls the
makeDiagonal()
method.
|

Figure 7. Double-decker squares. |

Figure 8. makeDiagonal()
produces this pattern. |
Wrap Up
This article introduced the following ideas:
- that classes are "blueprints" of objects
- how to extend the capabilities of a class with a subclass
- that different programs can produce the same result
In the next episode we will start controlling multiple robots.
Reprinted from the November 2003 issue of PC Update, the magazine of Melbourne PC User Group, Australia
|