Turtle Plotter Case Study

Table of Contents


1. The Problem

Our task is to create a type of Turtle called a Plotter. A Plotter, when first created, draws a set of axes with tick marks from -8 to 8 in both directions. Then, it can be told to plot(x, y), and it will plot the point (x, y) in its current pen color.

As we think about this problem, some things we are fairly sure we can do. Drawing axes is no problem. If I know that I start at the origin facing up, it's no problem to move to any point on the canvas and draw. But some things do not have obvious solutions:We'll tackle the first two problems first. The last turns out to be fairly complicated.

2. Constructors

Very often, when you are creating a new class, there are some things that you want to be sure that objects of that class do before anything else. This is called "initializing" the object - setting up its initial state.

In order to initialize a class, you write a method called a "Constructor." Remember how you had to put parentheses after the class name when doing "new Turtle()"? Looked a bit like a method call, right? A constructor is, in fact, defined in your code just like any other method. To make it clear that it is a constuctor, you make a method whose name is the same as the object class.

We want our constructor to draw the axes. I've made a private method that does this; I won't bore you by printing it here. The constructor now looks like this:

public Plotter() {
   drawAxes();
}

Notice that there is no return type specified for this method.

3. "If" Statements

Our next job is to write the function plot(double x, double y). The basic code of the method is easy enough. We make the "points" we plot be little squares .05 units long (3 pixels on the "Normal" zoom). With some clever use of looping techniques from the last lesson, we get this:

public void plot(double x, double y) {
   penUp();
   rt();
   move(x - .05);
   lt();
   move(y - .05);
   penDown();

   int i = 1;
   while(i <= 4) {
      move(.1);
      rt();
      i = i + 1;
   }

   penUp();
   lt();
   move(x - .05);
   lt();
   move(y - .05);
   rt(180.);
}

However, we have a somewhat tricky requirement for this method that we haven't dealt with: it is supposed to reject any points that aren't within the -8 to 8 range in both coordinates. If asked to plot outside this range, the turtle should do nothing.

So, we want to check some sort of condition, and use that to decide whether we do something or not. For this, we need a command called "if":

if (condition) {
   Some statements;
}

When the computer encounters an "if" statement, it checks if condition is true. If so, it does whatever is in the block of code surrounded by curly braces; if not, it skips to the end of that block.

At this point, it would be helpful to you if I also intoduce some new things that you can include in a condition. Right now, I need to ask if x is greater than or equal to -8 and less than or equal to 8, and y is the same. This idea of "and" is expressed in a condition with the symbol "&&" So, I could write:

if(x ≥ -8 && x ≤ 8 && y ≥ -8 && y ≤ 8) {
all that code in the method above
}

This accomplishes what we wanted plot() to do. For more detail on what you can include in conditions, see Condition Syntax.

4. Overriding Methods

Our next task was to keep the "client" (the code that is using our class) from moving the Plotter away from the origin. Even turning the Plotter 90 degrees would completely mess up how it plots.

In order to keep anyone from accessing the move(), rt(), and lt() methods, we will overload them: write a new method to take the place of the old one. Our new methods will simply have the Plotter do nothing. We only need to overload the three moving methods that take parameters; the parameterless just call special cases of these.

public void move(double distance) {
}

public void rt(double angle) {
}

public void lt(double angle) {
}

If I overload a method, then any time that method is called, the overloaded method is called. This is true even if the method is called as part of code in the superclass.

OK, now we've successfully locked out the drawing methods. But how does Plotter access the drawing methods when it needs to draw?

We will often have to somehow access methods of the superclass from within a class. Java provides an easy way to do this. Just like the variable this represents the current object, there is a special variable super that represents the superclass of that object. This variable allows you to call a method like super.move(), if you need to distinguish between some move() method of your own and the super's version of it.

There are two possible ways to do what we need to. We could simply replace every call to move() inside Plotter with a call to super.move(). However, that seems inelegant to me. The other option is to, instead of entirely disabling move(), write a move() method that differentiates somehow between instructions generated by the plotter itself and those generated from outside.

5. Instance Variables

In order to have the Plotter remember something about itself, namely, whether it is currently engaged in drawing under its own employ rather than under the control of a stranger, we need to make some sort of variable that is accessible in every method of the class. We will use what is called an "instance variable": a variable that belongs to each copy ("instance") of an object class.

To create an instance variable, simply declare it as you would a method variable, but outside all methods, at the top of the class. Here we are going to create a variable called "itsMeDoingThat." This variable is a boolean: something that can only be either true or false. We make it "private" so that no one else can mess with it, or even see it.

public class Plotter extends Turtle {
   private boolean itsMeDoingThat;

We will set this variable to true whenever we start drawing, and set it back to false whenever we are done drawing. We can rewrite move(double) to look like this:

public void move(double distance) {
   if (itsMeDoingThat) {
      super.move(distance);
   }
}

Now we simply rewrite our draw methods to set itsMeDoingThat to true when they start and set it back to false when they're done.

Note that if I hadn't set itsMeDoingThat to be private, someone outside could access it (read or change it) as plotter.itsMeDoingThat (assuming "plotter" is a Plotter variable).

6. Some finishing touches

When I first ran the plotter, it was sort of dull to watch it go step by step to make the axes. If you remember from the extras on assignment 1, it's possible to make something happen instantaneously if you override the done() method of Turtle, which pauses the Controller after each step. We can do something like this for Plotter.

First, I will overload the done() method with a new copy that does nothing. Then, I will insert calls to super.done() at two places: the end of the constructor, and the end of plot(). Now, our turtle draws the axes in one step, and takes just one step to draw each point.

The plotter class is included with the rest of the Turtles files. You can try it out, entering something like this in Controller.run():

Plotter t = new Plotter();

t.penDown(Color.RED);
t.move();
t.rt();
t.lt();
t.plot(-3, 2);
t.plot(4, 5);
t.plot(-6, -1);
t.plot(7, -3);
t.plot(-1, 5);
t.plot(2, 3);
t.plot(-5, -3);
t.plot(8, -8);

t.die();

7. Summary

From this case study, you hopefully learned the following things:
back to Computer Programming Assignments.