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

[ About Melbourne PC User Group ]