Teaching a young robot new tricks
Teaching a young robot new tricks
Programming with Karel J Robot - Part 2 of 4
In the previous article, I left readers with the task of modifying the example program to make the robot close the L-shape into a square, as shown here:
Listing 1 shows a solution to the problem.
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();
}
}
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 that computers were made for and humans weren’t.
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.
Consider the following line from Listing 1
Robot karel = new Robot(1, 2, East, 4);
We discussed this in the previous article - 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 it’s 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 our alterations to that.
Let’s say we want a robot with an extra method called makeSide() that will replace the repetative 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 these 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.
Listing 2.
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();
}
}
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
Jargon
There are a few jargon words used to describe the kind 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 the previous article 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 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.
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.
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.
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.
Gettin’ fussy now
Now I don’t want to sound too fussy, but the code in Listing 3 still looks a bit repetative 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 is belongs - in SquareMaker.
This means that we can adjust Example02 to look like Listing 5.
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();
}
}
Use BlueJ to make these changes, compile and see how it runs. Does it still produce the same square? (I hope so!)
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 sure that the makeSquare() method makes a 3â—Š3 square every time we ask it to, then this is the last time we need to worry about coding a 3â—Š3 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 4â—Š4 instead of 3â—Š3. 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 4â—Š4 dimensions. Now add a third square on top.
A harder task
Extend Robot to make a new class that has the following methods:
- turnRight() - 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 here:
Test your new robot using an example program that only calls the makeDiagonal() method.
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.
First published: PC Update Nov 2003 (online version updated)