Nuxtstop

For all things nuxt.js

100 Languages Speedrun: Episode 33: Logo

5 0

Logo is a programming language all the way from the 1960s aimed at teaching kids programming. The most notable feature of Logo are "turtle graphics" - simple commands that draw lines on screen by moving an imaginary "turtle".

One issue with covering Logo is that it's meant for interactive use in some Logo GUI environment, and these are platform specific and don't last very long, so every variant of Logo will be quite different. And it's not just their fancy features like 3D graphics, interactivity, and so on. Even very basic commands like like changing color are going to be different in each Logo.

I'll be using in-browser papert Logo, so all examples will work in papers. Different Logo implementations will need some adjustments. I'll try to mention when something is implementation-specific.

I'll post a few of the pictures generated by the programs - if you want to see some that I skipped, just try them out in papert.

Other Logo implementations I'll mention are SLogo (also in-browser), and ACSLogo for OSX.

Basic Drawing Commands

We're not printing anything, we're controlling a "turtle". A turtle has position on a screen as well as orientation.

To draw a square we can tell the turtle to move forward 50 steps, turn 90 degrees to the right, four times:

; Square
forward 50
right 90
forward 50
right 90
forward 50
right 90
forward 50
right 90
Enter fullscreen mode Exit fullscreen mode

Line comments use ;.

As these commands are a bit long, and we'll be using them all the time, there are shorter versions too. fd for forward, rt for right and lt for left:

; Triangle
fd 60
rt 120
fd 60
rt 120
fd 60
rt 120
Enter fullscreen mode Exit fullscreen mode

Logo implementation differences

We can also control color and thickness of lines. In papert we can use color [R G B] and penwidth WIDTH. For very simple loops we can do repeat N [COMMANDS]:

; Blue Hexagon - Papert
cs
color [200 200 255]
penwidth 4
repeat 6 [fd 60 rt 60]
ht
Enter fullscreen mode Exit fullscreen mode

Blue Hexagon

Logo programs tend to show the turtle on the screen. To show or hide the turtle we can use st and ht commands.

Logo doesn't clear the screen by default when you start the program, so if you want to do so, you should use cs command explicitly.

Anyway, here's the same program in JSLogo, which has RGB 0-100 instead of 0-255, and slightly different commands:

; Blue Hexagon - JSLogo
cs
setpencolor [80 80 100]
setpensize 4
repeat 6 [fd 60 rt 60]
ht
Enter fullscreen mode Exit fullscreen mode

And here's more traditional ACSLogo, which only has fixed colors 0-15, and doesn't have comments:

cs
setpencolor 15
setpenwidth 4
repeat 6 [fd 60 rt 60]
ht
Enter fullscreen mode Exit fullscreen mode

As you can see, there's zero hope of writing any kind of "portable" Logo programs.

Procedures

We can define procedures with to name ... end. Like this draws three letters I:

to draw_i
  ; draw line
  forward 10
  penup
  ; go to next character
  right 90
  forward 5
  right 90
  forward 10
  ; reset to facing up, pen down
  pendown
  right 180
end

cs
repeat 3 [draw_i]
Enter fullscreen mode Exit fullscreen mode

To move the turtle without touching the screen, we can use penup and pendown commands (or pu and pd).

Step by step:

  • turtle faces up, at some point, let's say (100, 100), Logo normally doesn't use coordinates at all, but let's say these are normal computer graphics coordinates (X points right, Y points down). Pen is down.
  • forward 10 makes turtle draw line up to (100, 90)
  • penup ends drawing, but we still need to position turtle at the next letter
  • right 90 and forward 5 makes turtle turn clockwise by 90 degrees (so it's pointing right for us) and advance to (105, 90) without drawing.
  • right 90 and forward 10 makes turtle turn clockwise by 90 degrees (so it's pointing down for us) and advance to (105, 100) without drawing.
  • pendown and right 180 makes turtle press the pen down turn clockwise by 180 degrees (so it's pointing up for us), so we end up 5 pixels to the right from where we started, in same orientation and pen state

Fizz

You can probably see where this is going, here's a program that says FIZZ three times:

to draw_i
  fd 10 ; main stroke
  ; go to next character
  pu
  rt 90 fd 5
  rt 90 fd 10
  ; reset pen state
  pd
  rt 180
end

to draw_f
  fd 10 ; main line
  rt 90 fd 5 ; top stroke
  pu rt 180 fd 5 lt 90 fd 5; move to next stroke
  pd lt 90 fd 5; middle stroke
  ; go to next character
  pu fd 5 rt 90 fd 5
  ; reset pen state
  pd
  rt 180
end

to draw_z
  rt 90 fd 5; bottom line
  pu rt 180 fd 5 ; return
  pd rt 120 fd 11 ; diagonal stroke
  lt 120 fd 5 ; top line
  ; advance to next character
  pu rt 180 fd 5 rt 120 fd 11 lt 120 fd 10
  ; reset pen state
  pd lt 90
end

to draw_fizz
  draw_f draw_i draw_z draw_z
end

cs
repeat 3 [draw_fizz]
Enter fullscreen mode Exit fullscreen mode

Fizz

I could explain it step by step, but it's probably easier if you try to run it in Papert using "run slowly" button to see how turtle moves step by step.

As we didn't use any special commands, this program runs in JSLogo as well. It doesn't work with ACSLogo as it doesn't support comments, and it needs its GUI to define procedures.

Buzz

Drawing BUZZ is basically saem as drawing FIZZ, except loops work weird way - instead of drawing starting where the turtle is, the arc degrees radius command draws an arc around the turtle, starting where the turtle is facing and going up.

to draw_b
  fd 2.5 ; main stroke a bit
  arc 2.5 180 ; bottom loop
  fd 5 ; more main stroke
  arc 2.5 180 ; top loop
  fd 2.5 ; finish main stroke
  pu rt 180 fd 10 ; go back
  ; go to next character
  lt 90 fd 7
  ; reset pen state
  pd lt 90
end

to draw_u
  pu fw 3 pd fd 7 ; left stroke
  pu rt 90 fd 6 ; move to right stroke
  pd rt 90 fd 7 ; right stroke
  pu rt 90 fd 3 ; move to center of arc
  pd rt 180 arc 3 180 ; arc
  ; go to next character
  pu fd 8 rt 90 fd 3
  ; reset pen state
  pd rt 180
end

to draw_z
  rt 90 fd 5; bottom line
  pu rt 180 fd 5 ; return
  pd rt 120 fd 11 ; diagonal stroke
  lt 120 fd 5 ; top line
  ; advance to next character
  pu rt 180 fd 5 rt 120 fd 11 lt 120 fd 10
  ; reset pen state
  pd lt 90
end

to draw_buzz
  draw_b draw_u draw_z draw_z
end

cs
repeat 3 [draw_buzz]
Enter fullscreen mode Exit fullscreen mode

Digits

Doing this 10 more times with proper loops for each digit would be a bit much, so let's do them in style of 7-segment display.

For 1 I'll use the I code instead to avoid awkward spacing.

Here's the code:

;  C
; B D
;  G
; A E
;  F
to seven_seg :a :b :c :d :e :f :g
  ifelse :a [pd] [pu]
  fd 5
  ifelse :b [pd] [pu]
  fd 5
  rt 90
  ifelse :c [pd] [pu]
  fd 5
  rt 90
  ifelse :d [pd] [pu]
  fd 5
  ifelse :e [pd] [pu]
  fd 5
  rt 90
  ifelse :f [pd] [pu]
  fd 5
  pu rt 90 fd 5 rt 90
  ifelse :g [pd] [pu]
  fd 5
  pu fd 5 rt 90 fd 5 rt 180 pd
end

to draw_0
  seven_seg true true true true true true false
end

to draw_1
  fd 10 pu
  rt 90 fd 5
  rt 90 fd 10
  pd rt 180
end

to draw_2
  seven_seg true false true true false true true
end

to draw_3
  seven_seg false false true true true true true
end

to draw_4
  seven_seg false true false true true false true
end

to draw_5
  seven_seg false true true false true true true
end

to draw_6
  seven_seg true true true false true true true
end

to draw_7
  seven_seg false false true true true false false
end

to draw_8
  seven_seg true true true true true true true
end

to draw_9
  seven_seg false true true true true true true
end

to draw_digits
  draw_0 draw_1 draw_2 draw_3 draw_4
  draw_5 draw_6 draw_7 draw_8 draw_9
end

reset
cs
draw_digits
ht
Enter fullscreen mode Exit fullscreen mode

And here are the digits:

Digits

As you can see procedures can take parameters, and ifelse condition [then] [else] can do some simple logic.

Numbers

To draw numbers we just need to add a bit of code and some recursion:

...

to draw_digit :digit
  if (:digit = 0) [draw_0]
  if (:digit = 1) [draw_1]
  if (:digit = 2) [draw_2]
  if (:digit = 3) [draw_3]
  if (:digit = 4) [draw_4]
  if (:digit = 5) [draw_5]
  if (:digit = 6) [draw_6]
  if (:digit = 7) [draw_7]
  if (:digit = 8) [draw_8]
  if (:digit = 9) [draw_9]
end

to draw_number :number
  make "a (:number % 10)
  make "b (:number - :a)
  make "c (:b / 10)
  if (:c > 0) [draw_number :c]
  draw_digit :a
end

reset
cs
draw_number 42069
ht
Enter fullscreen mode Exit fullscreen mode

Numbers

make "var (...) is how you can assign variables. We need to use a bunch of extra variables, as Logo lacks integer division.

FizzBuzz

And here's the moment we've all been waiting for, the FizzBuzz in Logo!

Here's the complete program, mostly the code we wrote before:

;  C
; B D
;  G
; A E
;  F
to seven_seg :a :b :c :d :e :f :g
  ifelse :a [pd] [pu]
  fd 5
  ifelse :b [pd] [pu]
  fd 5
  rt 90
  ifelse :c [pd] [pu]
  fd 5
  rt 90
  ifelse :d [pd] [pu]
  fd 5
  ifelse :e [pd] [pu]
  fd 5
  rt 90
  ifelse :f [pd] [pu]
  fd 5
  pu rt 90 fd 5 rt 90
  ifelse :g [pd] [pu]
  fd 5
  pu fd 5 rt 90 fd 5 rt 180 pd
end

to draw_0
  seven_seg true true true true true true false
end

to draw_1
  fd 10 pu
  rt 90 fd 5
  rt 90 fd 10
  pd rt 180
end

to draw_2
  seven_seg true false true true false true true
end

to draw_3
  seven_seg false false true true true true true
end

to draw_4
  seven_seg false true false true true false true
end

to draw_5
  seven_seg false true true false true true true
end

to draw_6
  seven_seg true true true false true true true
end

to draw_7
  seven_seg false false true true true false false
end

to draw_8
  seven_seg true true true true true true true
end

to draw_9
  seven_seg false true true true true true true
end

to draw_digit :digit
  if (:digit = 0) [draw_0]
  if (:digit = 1) [draw_1]
  if (:digit = 2) [draw_2]
  if (:digit = 3) [draw_3]
  if (:digit = 4) [draw_4]
  if (:digit = 5) [draw_5]
  if (:digit = 6) [draw_6]
  if (:digit = 7) [draw_7]
  if (:digit = 8) [draw_8]
  if (:digit = 9) [draw_9]
end

to draw_number :number
  make "a (:number % 10)
  make "b (:number - :a)
  make "c (:b / 10)
  if (:c > 0) [draw_number :c]
  draw_digit :a
end

to draw_i
  fd 10 ; main stroke
  ; go to next character
  pu
  rt 90 fd 5
  rt 90 fd 10
  ; reset pen state
  pd
  rt 180
end

to draw_f
  fd 10 ; main line
  rt 90 fd 5 ; top stroke
  pu rt 180 fd 5 lt 90 fd 5; move to next stroke
  pd lt 90 fd 5; middle stroke
  ; go to next character
  pu fd 5 rt 90 fd 5
  ; reset pen state
  pd
  rt 180
end

to draw_b
  fd 2.5 ; main stroke a bit
  arc 2.5 180 ; bottom loop
  fd 5 ; more main stroke
  arc 2.5 180 ; top loop
  fd 2.5 ; finish main stroke
  pu rt 180 fd 10 ; go back
  ; go to next character
  lt 90 fd 7
  ; reset pen state
  pd lt 90
end

to draw_u
  pu fw 3 pd fd 7 ; left stroke
  pu rt 90 fd 6 ; move to right stroke
  pd rt 90 fd 7 ; right stroke
  pu rt 90 fd 3 ; move to center of arc
  pd rt 180 arc 3 180 ; arc
  ; go to next character
  pu fd 8 rt 90 fd 3
  ; reset pen state
  pd rt 180
end

to draw_z
  rt 90 fd 5; bottom line
  pu rt 180 fd 5 ; return
  pd rt 120 fd 11 ; diagonal stroke
  lt 120 fd 5 ; top line
  ; advance to next character
  pu rt 180 fd 5 rt 120 fd 11 lt 120 fd 10
  ; reset pen state
  pd lt 90
end

to draw_fizz
  draw_f draw_i draw_z draw_z
end

to draw_buzz
  draw_b draw_u draw_z draw_z
end

to draw_fizzbuzz
  draw_fizz draw_buzz
end

to draw_line :i
  setxy 15 (:i * 15)
  ifelse (:i % 15 = 0) [draw_fizzbuzz] [
    ifelse (:i % 5 = 0) [draw_buzz] [
      ifelse (:i % 3 = 0) [draw_fizz] [draw_number :i]
    ]
  ]
end

reset
cs
make "i 1
repeat 30 [
  draw_line :i
  make "i (1 + :i)
]
ht
Enter fullscreen mode Exit fullscreen mode

Which generates the FizzBuzz we want:

FizzBuzz

We had to do a few more things:

  • setxy x y moves the turtle to specific point on the screen - we use turtle position to go to the next letter, so we don't really know how far it went - it's a lot easier this way
  • make "i 1 - there's no for loops in Logo, so we make a weird while/for hybrid by defining i and increasing it 30 times

That's enough Logo for now.

Should you use Logo?

No.

Turtle graphics is "easier" for children only in some evil mirror universe where children's favorite subject is trigonometry. In this universe coordinate graphics with damn graph paper is drastically more approachable. And if you absolutely need to do turtle graphics, there's packages for that in every regular language anyway. Even disregarding turtle vs coordinate graphics issue, Logo is absolutely dreadful as a programming language, and learning Logo teaches no useful skill.

As for teaching programming to total beginners, the easiest ways are either HTML+CSS then Javascript path (the junior web dev path), or spreadsheets then SQL path (the business analyst path). Or do what ambitious bootcamps do and start with Ruby or Python, with proper TDD from right away and so on. These approaches are all proven to work. Nobody teaches programming with Logo, as it would be horribly ineffective and completely ridiculous.

Code

All code examples for the series will be in this repository.

Code for the Logo episode is available here.