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:

  • 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 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

[ About Melbourne PC User Group ]