Karel heads for the stars

Programming with Karel J Robot - Part 4 of 4

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 × and one that makes a +. Put the two patterns on top of each other and you get a criss-cross 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).

Figure 1

Figure 1

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 for the ShapeMaker class.

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();
}

There’s not too much new here so hopefully you get the general idea from the inline comments. 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

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();
    }
}

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.

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.

Figure 2.

Figure 2

Have another look at listing 3.

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();
    }
}

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.

Figure 3

Figure 3

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. But here’s the rub - because asterisk is a ShapeMaker type name, it only knows 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 × 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.

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();
    }
}

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

Figure 4

Figure 4

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. 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 5

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!

BlueJ and Karel J Robot updates

Since this series of articles started, there have been new releases of BlueJ and Karel J Robot. The new version of Karel adds a few features including the ability to change colours - which means we can make this program look more star-like (figure 6).

Figure 6

A task for you

Read the comments below by Tracy Kouns under the heading “Content” and try out the recommendations.

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.

First published: PC Update Mar 2004 (online version updated)

2 Responses to “Karel heads for the stars”

  1. Tracy Kouns Says:

    I really like this approach of introducing abstract classes at a very early stage in the students’ programming education. I do, however, have a some recommendations.

    Style

    As of 9/16/05 this page needs some attention to the html and css. See Listing 2, Listing 4, Listing 5, and the end of the first paragraph in Super Methods. Also, all of the listings in all 4 parts appear really small in Firefox.

    Content

    In Listing 2, XMaker, I would recommend adding turnAround(); as the last statement in makeDiagonal(). This would cause the Robot to not only wind up in its original starting location but again face its original direction at the time of the method call.

    In Listing 4, LineMaker, I would recommend adding the following code to the end of the makeShape() method.

    turnAround();
    move();
    move();
    move();
    move();
    turnAround();

    This again puts the Robot in its original location and direction at the time of the method call.

    With the above changes, the makeShape() method in Listing 5, CrossMaker, could then be written as follows.

    public void makeShape()
    {
      super.makeShape();
      turnLeft();
      super.makeShape();
      turnLeft();
      super.makeShape();
      turnLeft();
      super.makeShape();
      turnLeft();
    }

    I would like this approach better because the CrossMaker “is a” LineMaker. It therefore seems NOT to be the best approach that it then creates more Robots when called to makeShape(). This currently tends to leave side effects (unintended visible evidence from the method). The writer of Example04 would probably be expecting to see one or possibly two Robots in the output. As it is currently, it displays five Robots.
    This new approach would still lend itself to a good example of using inheritance with calls to a super method.

    Thanks for making these great lessons available.

  2. tech.thingoid Says:

    Hello Tracy and thanks for your comments. It’s great to get feedback as detailed and thoughtful as yours - I’m only sorry that I haven’t been able to respond earlier.

    Anyway, to take your comments in turn:

    Style

    Yep, even now there are still some lingering styling problems on this site - the side-effect of an automated conversion between authoring systems. I’m exceptionally grateful for your pointing out some particularly egregious faults on this page, which I hope I’ve addressed (except for the readability issue, which I probably won’t be in a position to test and fix for a month or two).

    Content

    In listing 2, you are right that it would be neater if XMaker had a turnAround() statement at the end. However, for my purposes this adds little except two extra turns on every diagonal. But for purity I agree that the robot should really end in it’s beginning state.

    On the other hand, I do not agree with your recommendation to return the four robots to the centre. The final position of all robots is specified in fig 1, which shows that four robots stop at the ends of the vertical and horizontal arms, not back in the middle - so that is what the writer of Example04 should expect to see. Programming problems for Karel are often effectively instructions to “make this shape” where the final positions of the robots may or may not be relevant, but in this case I argue that they are.

    Regarding your recommendation for listing 5, isn’t this simply reproducing the same approach as XMaker (listing 2) just in a more complex way? That is, your suggested call to super.makeShape(); is functionally equivalent to a call to a local makeLine(); function (similar to makeDiagonal() in XMaker). That said, I agree that it’s a neat way of illustrating inheritance and calls to superclass methods - and probably more straightforward.

    I don’t want to sound defensive (because I’m not) or like I’m rejecting your comments out of hand (because I don’t). In fact I’ve added to the article a suggestion for readers to try out and experiment with your recommendations as a means to further learning.

    Thanks

    Thanks again for the comments and feedback. I’m glad that you’re finding a use for the Karel series.