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
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
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
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
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
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]
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
andforward 5
makes turtle turn clockwise by 90 degrees (so it's pointing right for us) and advance to(105, 90)
without drawing. -
right 90
andforward 10
makes turtle turn clockwise by 90 degrees (so it's pointing down for us) and advance to(105, 100)
without drawing. -
pendown
andright 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]
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]
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
And here are the 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
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
Which generates the FizzBuzz we want:
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 definingi
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.
Top comments (0)