We can describe this process recursively as follows. Draw a framed rectangle at position (x,y) with side of length size. Then shift down and to the right by 2 pixels, decrease the size by 4 pixels, and draw the rest of the nested squares starting there.
Here is a Java class doing exactly that:
public class NestedSquares { private static final double SHIFT = 2; public NestedSquares(double x, double y, double size, DrawingCanvas canvas) { new FramedRect( x, y, size, size, canvas ); if ( size >= 4 ) { new NestedSquares(x+SHIFT, y+SHIFT, size-2*SHIFT, canvas); } }
The constructor will draw nested squares only if the size of the nested square would be non-negative.
Of course, you could probably figure out how to do the same thing with a while loop:
public class NestedSquares { private static final double SHIFT = 2; public NestedSquares(double x, double y, double size, DrawingCanvas canvas) { new FramedRect( x, y, size, size, canvas ); while (size >= 4 ) { size = size -2*SHIFT; x = x + SHIFT; y = y + SHIFT; new FramedRect( x, y, size, size, canvas ); } }
public class NestedSquares { private static final double SHIFT = 2; private FramedRect outerSquare; private NestedSquares rest; public NestedSquares(double x, double y, double size, DrawingCanvas canvas) { outerSquare = new FramedRect( x, y, size, size, canvas ); if ( size >= 4 ) { rest = new NestedSquares(x+SHIFT, y+SHIFT, size-2*SHIFT, canvas); } } public void moveTo(double x, double y) { outerSquare.moveTo(x,y); if (rest != null) { rest.moveTo(x+SHIFT, y+SHIFT); } } }
The only change we have made to the constructor is to give names to the square (now called outerSquare) and the rest of the nested squares, rest. The reason for this is so that we can refer to them in the moveTo method.
Notice that the type of rest is NestedSquares. Java has no problem with an instance variable whose type is the same as the entire class. When a class has an instance variable with the same type as the class, we say that it represents a recursive data structure.
The moveTo method is very straightforward. Simply move outerSquare to the desired position, and then move rest to a position two pixels to the right and down so that they will remain properly nested. Other methods like setColor, move, removeFromCanvas would be written similarly.
The basic idea is this: since we have a recursive data structure, methods manipulating it will also be recursive. In this case, we have taken care of the non-recursive part (outerSquare) and then send a recursive message to the rest.
How could you do this if you didn't want to do it recursively? If you were to use a while loop you would need to save the names of all of the squares! We'll see later in the semester that this can be a perfectly reasonable solution, but it's certainly more complex than our recursive solution.
Notice that broccoli has a central stem that splits into three pieces. Each of those three pieces looks like a smaller scale version of broccoli. This should give us a hint that broccoli might be created in a recursive fashion. However, before beginning we need to learn about a class called AngLine which will make life a bit easier for us. It will allow us to draw lines by giving the starting position, length, and angle to the horizontal. The constructor declaration for an AngLine is:
public AngLine(Location start, double length, double radianAngle, DrawingCanvas, canvas)
Now that we know about this class, we can turn to the problem of drawing broccoli. We start out by constructing an object of type BroccoliBranch.
public class BroccoliController extends WindowController{ private BroccoliBranch plant; // Broccoli object to be drawn and moved /** * Create the broccoli */ public void begin(){ plant = new BroccoliBranch(new Coords(200,350), 80.0, Math.PI/2.0, canvas); } ... }The first parameter is the location of the base of the broccoli, the second is the size of the stem of the broccoli (the part before the branching), the next is the angle at which the broccoli is growing, and finally we include the canvas.
Now, how do we create an item of type BroccoliBranch? Look at the constructor in the class below:
/** * Class to recursively draw broccoli */ import objectdraw.*; import java.awt.*; public class BroccoliBranch implements BroccoliPart { // dark green color for broccoli private static final Color BROCCOLI_COLOR = new Color(0,135,0); // Below this size draw flower private static final double MINSIZE = 25.0; // How much broccoli shrinks each call private final static double TOP_FRACT = 0.8; // stem of broccoli private AngLine stem; // branches of broccoli private BroccoliPart left, center, right; /** * Draw broccoli by recursively drawing branches (and flower) */ public BroccoliBranch(Location startCoords, double size, double direction, DrawingCanvas canvas) { // Draw stem and color green stem = new AngLine(startCoords, size, direction, canvas); stem.setColor(BROCCOLI_COLOR); Location destCoords = stem.getEnd(); // end of stem if ( size > MINSIZE ) // big enough to keep growing { left = new BroccoliBranch(destCoords, size * TOP_FRACT, direction + Math.PI/9.0, canvas); center = new BroccoliBranch(destCoords, size * TOP_FRACT, direction, canvas); right = new BroccoliBranch(destCoords, size * TOP_FRACT, direction - Math.PI/9.0, canvas); } else // draw flowers when small enough { left = new Flower(destCoords, size * TOP_FRACT, direction + Math.PI/9.0, canvas); center = new Flower(destCoords, size * TOP_FRACT, direction, canvas); right = new Flower(destCoords, size * TOP_FRACT, direction - Math.PI/9.0, canvas); } } /** * @param x,y amount to move broccoli */ public void move( double x, double y) { stem.move(x,y); // move stem left.move(x,y); // move other parts center.move(x,y); right.move(x,y); } }
We begin by drawing the stem as an AngLine starting at the base of the broccoli, with the length and angle passed as parameters. Now we have to figure out what to put on the end of the stem. If the stem size is greater than MINSIZE, we place three smaller BroccoliBranches. One will be growing at the same angle as the stem, but the other two will be offset by pi/9 radians or 20 degrees on either side of the stem. Recall that all recursive algorithms need a base case. For us the base case will be when the size of the stem is no greater than MINSIZE. In that case we add three flowers (see the Flower class below) drawn at the same angles as the broccoli branches would have been drawn.
Notice that the move method of BroccoliBranch is recursive. We first move the stem the appropriate distance. Then the three BroccoliParts - whether BroccoliBranches or Flowers are also moved the same amount. Since each of the parts is simpler (either an AngLine, Flower, or smaller BroccoliBranch), everything works out fine.
One of the versions of the Broccoli program found on applecore has the BroccoliBranch extend ActiveObject. The constructor just draws the stem. The run() method delays for about a second and then constructs the three BroccoliParts. Because each of those also draws its stem and then pauses before drawing its branches, we can see the consecutive levels of the broccoli being drawn.
public interface BroccoliPart { // amount to move the broccoli part public void move( double x, double y); }
The class Flower is straightforward:
/** * Class to draw a broccoli flower */ import objectdraw.*; import java.awt.*; public class Flower implements BroccoliPart { private static final Color BROCCOLI_COLOR = new Color(0,135,0); private static final int BUD_SIZE = 3; // diameter of bud private AngLine stem; // stem of broccoli private FilledOval bud; // flower of broccoli plant /** * Draw flower */ public Flower(Location startCoords, double size, double direction, DrawingCanvas canvas) { // Draw stem and color green stem = new AngLine(startCoords, size, direction, canvas); stem.setColor(BROCCOLI_COLOR); Location destCoords = stem.getEnd(); // end of stem bud = new FilledOval(destCoords, BUD_SIZE, BUD_SIZE,canvas); bud.setColor(Color.yellow); } /** * @param x,y amount to move flower */ public void move( double x, double y) { // move stem stem.move(x,y); // move bud bud.move(x,y); } }Because this is a base case of the drawing algorithm, there are no recursive calls to constructor.
When you run the program, you can see that you get a nice symmetric broccoli plant. The actual program also allows the user to move the broccoli plant. The Broccoli class (not shown here) extends WindowController and includes the usual code in methods onMousePress and onMouseDrag to allow the user to drag around the broccoli by sending move messages to plant. The code for moving BroccoliBranches and Flowers was included above. They simply involve moving the instance variables.