Writing with the Turtle, part 2

In part 1 it became obvious that if we want our turtle to be able to trace out every capital letter and every digit (36 characters in total) then our code could become very long, with a lot of repetition of long lines of code that make the turtle move or turn. Even if we only look at the code for drawing the letter 'E' from part 1 we can see some repetition (I've used colored highlighting to make the repetition more obvious):

  Turtle.Turn(-90)

  Turtle.Move(200) 

  Turtle.Turn(90)

  Turtle.PenDown()

  Turtle.Move(100)

  Turtle.PenUp()

  Turtle.Turn(-63.434)

  Turtle.Move(223.6)

  Turtle.Turn(-26.565)

  Turtle.Turn(-90)

  Turtle.PenDown()

  Turtle.Move(200)

  Turtle.Turn(-90)

  Turtle.Move(400)

  Turtle.Turn(-90)

  Turtle.Move(200)

  Turtle.PenUp()

  Turtle.Move(100)

If we could substitute a single character for each of these lines then all we would have is a few characters that we could then arrange in a neat horizontal string. Let's use the following characters to represent each command: 

  Turtle.Turn(-90)        A

  Turtle.Move(200)        2

  Turtle.Turn(90)         a

  Turtle.PenDown()        P

  Turtle.Move(100)        1

  Turtle.PenUp()          p

  Turtle.Turn(-63.434)    F

  Turtle.Move(223.6)      e

  Turtle.Turn(-26.565)    E

  Turtle.Turn(-90)        A

  Turtle.PenDown()        P

  Turtle.Move(200)        2

  Turtle.Turn(-90)        A

  Turtle.Move(400)        4

  Turtle.Turn(-90)        A

  Turtle.Move(200)        2

  Turtle.PenUp()          p

  Turtle.Move(100)        1

So now we can list the instructions for drawing the letter 'E' as simply as this: A2aP1pFeEAP2A4A2p1 ... as long as we know what each of those characters means and yes, that will need a fair amount of code too, but ultimately we will have shorter, better code.

Look carefully, and you will notice that I have used lower case letters to represent a forward motion or a turn to the right while the same letter in capitals means move or turn the same amount but in the opposite direction. An example pair is highlighted.

But even if we use upper and lower case letters as well as digits we may not have enough characters to do our neat substitution trick for all the different moves and turns we will need when we try to work with the whole alphabet. Unless... Unless we notice that most of the commands are in pairs, with each Move command usually (but not always) followed by a Turn command. If we break the sequence into pairs then that actually can double the number of commands that we can work with. Here's how it works:

0A 2a 0P 1p 0F eE 0A 0P 2A 4A 2p 10

Every Move command is now the first element of a pair and every Turn command is now the second element (I had to put in some empty characters to make that true - the character '0' means 'do nothing'). The neat thing about this pairing is that we can now use the same character to represent two different commands. For example, the character 'e' could mean Turtle.Move(223.6) if it is the first character in the pair, and Turtle.Turn(26.565) if it is the second character.

OK, I sense you're itching to see the finished code, so here it is. It draws the string "EV3" so it includes the sequences for the characters 'V' and '3' as well as the sequence for 'E' that you saw already. The sequences include some characters we didn't use before, so their meaning is given later in the code. The first character in each pair within a sequence corresponds to the Move command, so I called it movechar. The second character in each pair corresponds to the Turn command, so I called it turnchar. The first part of the code is rather trivial, so skip that and concentrate on the code beginning with the line in bold:

GraphicsWindow.Width=1000

GraphicsWindow.Height=500

GraphicsWindow.PenWidth=3

Turtle.Speed=8

Turtle.PenUp()

GraphicsWindow.penColor="yellow"

For y=50 To 450 Step 200  'draw horizontal grid lines

  GraphicsWindow.DrawLine(50,y,950,y)

EndFor

For x=50 To 950 Step 100  'draw vertical grid lines

  GraphicsWindow.DrawLine(x,50,x,450)

EndFor

GraphicsWindow.PenColor="black"

Turtle.x=50

Turtle.y=450

Turtle.Angle=90

mystring="EV3"

sequence["E"]= "0A 2a 0P 1p 0F eE 0A 0P 2A 4A 2p 10"

sequence["V"]= "0A 4J 0P Jj 0j jp 0J 8a 10"

sequence["3"]= "0P 10 Qr QR 5p 0f fF 10"

For j=1 To Text.GetLength(mystring)

  char=Text.GetSubText(mystring,j,1)

  For n=1 to (1+Text.GetLength(sequence[char]))/3

    movechar=text.GetSubText(sequence[char],n*3-2,1)

    unicode=Text.GetCharacterCode(movechar)

    If unicode>64 And unicode<91 Then  'capital letter

      movesign=-1

      movechar=Text.ConvertToLowerCase(movechar)

    Else

      movesign=1

    EndIf

    

    turnchar=Text.GetSubText(sequence[char],n*3-1,1)

    turn_unicode=Text.GetCharacterCode(turnchar)

    If turn_unicode>64 And turn_unicode<91 Then  'capital letter

      turnsign=-1

      turnchar=Text.ConvertToLowerCase(turnchar)

    Else

      turnsign=1

    EndIf

    

    If movechar="0" Then

      ' do nothing

    ElseIf movechar="1" Then

      Turtle.Move(100)

    ElseIf movechar="2" Then

      Turtle.Move(200)

    ElseIf movechar="4" Then

      Turtle.Move(400)

    ElseIf movechar="5" Then

      Turtle.Move(-100)

    ElseIf movechar="8" Then

      Turtle.Move(-400)

      

    ElseIf movechar="e" Then

      Turtle.Move(movesign*223.6)

    ElseIf movechar="f" Then

      Turtle.Move(movesign*447.2)

    ElseIf movechar="j" Then

      Turtle.Move(movesign*412.3)

      

    ElseIf movechar="q" Then  'draw arc

      If turnchar="q" then

        imax=45  '90° in 45 steps of 2° each

      ElseIf turnchar="r" then

        imax=90  '180°

      ElseIf turnchar="s" then

        imax=135   '270°

      ElseIf turnchar="t" then

        imax=180   '360°

      EndIf

      

      For i=1 to imax

        Turtle.Move(turnsign*3.49)  ' 2*pi*100/(4*45)  =3.49

        Turtle.Turn(movesign*2)

      EndFor

    EndIf

    

    If turnchar="0" Then

      ' do nothing

    ElseIf turnchar="a" Then

      Turtle.Turn(turnsign*90)

    ElseIf turnchar="e" Then

      Turtle.Turn(turnsign*26.565)

    ElseIf turnchar="f" Then

      Turtle.Turn(turnsign*63.434)

    ElseIf turnchar="j" Then

      Turtle.Turn(turnsign*14.036)

      

    ElseIf turnchar="p" Then

      If turnsign=1 then

        Turtle.PenUp()

      Else

        Turtle.PenDown()

      EndIf

    EndIf

  EndFor

EndFor

Yes, there's quite a lot in that program that I haven't explained, so you'll have to do some real work (for once) to figure out what's going on.

One thing you will notice is that I have code to detect whether movechar and turnchar are capital letters or not (capital letters have corresponding 'Unicode' values between 65 and 90, inclusive). If the character is a capital letter then I set a variable movesign or turnsign to be negative and later in the code the move distance or turn angle is multiplied by movesign or turnsign to get the correct direction. That means I don't need to write separate code for the lower and upper case versions of each letter, only for the lower case.

Another point is that the character pairs for drawing arcs have to be processed as a pair, not individually. For example, the pair 'qr' means draw a clockwise arc with the pen moving forwards until a semicircle has been drawn. The letter 'q' by itself indicates that the motion will be clockwise, but that's not enough information to move or turn the turtle.

When you run the program you'll probably be disappointed to find that it does no more than the program in part 1, but don't forget what we have achieved in part 2: we have developed a way of condensing a long list of instructions for drawing each character into a single string on just one line, so adding all the other characters will be straightforward now (time-consuming, but straightforward). See the code for drawing any string you want HERE.