Controlling the Turtle

Let's look over the turtle graphics API, and use it to draw a few different images.

Instructions

We control the turtle by giving it instructions. These instructions are defined as methods on the object doodle.turtle.Instruction (similarly to the methods on doodle.core.Image that create images).

We can import the methods and then create instructions.

import doodle.turtle._
import doodle.turtle.Instruction._
forward(10)
// res0: Instruction = Forward(distance = 10.0)
turn(5.degrees)
// res1: Instruction = Turn(angle = Angle(0.08726646259971647))

This doesn't do anything useful unless we assemble these commands into an image. To do so, we create a list of instructions and then ask the turtle (doodle.turtle.Turtle to be exact) to draw them to an Image.

val instructions = 
  List(forward(10), turn(90.degrees), 
       forward(10), turn(90.degrees), 
       forward(10), turn(90.degrees), 
       forward(10))

val path = Turtle.draw(instructions)

This creates a path---an Image---which we can then draw in the usual way. This gives the output shown in Figure turtles:square. This is not a very exciting image, but we can change color, line width, and so on to create more interesting results.

A square created via the turtle graphics system.

The complete list of turtle instructions in given in Table turtles:instructions


Instruction Description Example -------------------------- ----------------------------------- -------------------------------- forward(distance) Move forward the given distance, forward(100.0) specified as a Double.

turn(angle) Turn the given angle (an Angle) turn(10.degrees) from the current heading.

branch(instruction, ...) Save the current position and branch(turn(10.degrees), forward(10)) heading, draw the given instructions , and then return to the saved position to draw the rest of the instructions.

noop Do nothing! noop

: The instructions understood by the turtle. Table turtles:instructions

Exercises {-}

Polygons {-}

In the previous chapter we wrote a method to create a polygon. Reimplement this method using turtle graphics instead. The method header should be something like

def polygon(sides: Int, sideLength: Double): Image =
 ???

You'll have to do a bit of geometry to work out the correct turn angle, but as that's half the fun we won't spoil it for you.

<div class="solution"> Here's our solution. It's a structural recursion over the natural numbers. The turn angle is exactly the same as the rotation angle used to create polygons in polar coordinates in the previous chapter, though the derivation is quite different.

def polygon(sides: Int, sideLength: Double): Image = {
  val rotation = Angle.one / sides
  def iter(n: Int): List[Instruction] =
    n match {
      case 0 => Nil
      case n => turn(rotation) :: forward(sideLength) :: iter(n-1)
    }

  Turtle.draw(iter(sides))
}

</div>

The Square Spiral

The square spiral is shown in Figure turtles:square-spiral. Write a method to create square spirals using turtle graphics.

This task requires a bit more design work than we usually ask of you. You'll have to work out how the square spiral is constructed (hint: it starts at the center) and then create a method to draw one.

The square spiral!

<div class="solution"> The key insights to draw the square spiral are realising:

Once we have this understood this, the structure is basically the same as drawing a polyon. Here's our solution.

def squareSpiral(steps: Int, distance: Double, angle: Angle, increment: Double): Image = {
  def iter(n: Int, distance: Double): List[Instruction] = {
    n match {
      case 0 => Nil
      case n => forward(distance) :: turn(angle) :: iter(steps-1, distance + increment)
    }
  }

  Turtle.draw(iter(steps, distance))
}

</div>

Turtles vs Polar Coordinates {-}

We can create polygons in polar coordinates using a Range and map as shown below.

import doodle.core.Point._

def polygon(sides: Int, size: Int): Image = {
  val rotation = Angle.one / sides
  val elts =
    (1 to sides).toList.map { i =>
      PathElement.lineTo(polar(size, rotation * i))
    }
  Image.path(ClosedPath(PathElement.moveTo(polar(size, Angle.zero)) :: elts))
}

We cannot so easily write the same method to generate turtle instructions using a Range and map. Why is this? What abstraction are we missing?

<div class="solution"> Each side of the polygon requires two turtle instructions: a forward and a turn. Thus drawing a pentagon requires ten instructions, and in general n sides requires 2n instructions. Using map we cannot change the number of elements in a list. Therefore mapping 1 to n, as we did int the code above, won't work. We could map over 1 to (n*2), and on, say, odd numbers move forward and on even numbers turn, but this is rather inelegant. It seems it would be simpler if we had an abstraction like map that allowed us to change the number of elements in the list as well as transform the individual elements. </div>