Logo Game Design Introduction: Downhill Skiing

Downhill Skiing exercises Logo’s list handling capabilities and leverages turtleSpaces 3D functionality to create an engaging yet simple game.

Once loaded, click the flag icon to start.

Click here to open this project in the web IDE.

Herein lies a wonderful introduction to game development in turtleSpaces. The game mechanic behind downhill is simple: the player travels down the ski slope, avoiding randomly-positioned trees while shifting left and right to end in the target zone.

The core game mechanic is similar to simple early text-based ‘action’ games written in languages such as BASIC, where the player needs to avoid asteroids in an asteroid field, for example. It is straightforward and easy to understand.

As such, this program would make an excellent project for students moving from Scratch to text-based coding. It provides an introduction to the turtle, programmatically-generated lists, 3D model creation, user input and various sound primitives, without using any sophisticated math or complex language primitives.

The game logic is as follows:

  • Create initial graphic elements (stars, ground, slope)
  • Create the ‘landing zone’ and store its location in a container (variable)
  • Move the turtle across the ‘slope’ to each spot on a 39 x 40 grid
  • Decide randomly to create a tree or not, create that tree and log that decision in a list
  • Each row has a separate list (trees0 trees1 etc), made up of 39 values (one for each column)
  • Once finished, place the turtle randomly at the top, in a location free of trees
  • Loop through each row of trees (and their respective list containers) in descending order
  • Check the relevant list to see if the player is occupying the same location as a tree, if so crash
  • Check to see if the player has pressed a key, and act accordingly, moving the player
  • Pause between rows but for less time each iteration, so that the player speeds up as they descend
  • When we get to the ‘bottom’, check to see if we’re in the target zone
  • If we’re not in the target zone, retry with the same level
  • If we are in the target zone, create a new level increasing the odds of trees

As you can see, developing a lesson plan is easily achievable. Let’s explore each area of the code in depth. This tutorial assumes some familiarity with text-based coding on the part of the reader, and sometimes speaks in general terms. The fully-commented source code for this game is available at the end of this article, and inside the turtleSpaces environment itself as a published project.

In the beginning…

When the procedure starts, the first thing we need to do is reset the workspace, to ensure we’re working with a clean slate. Next, we need to provide the user with some instructions!

For this, we’ll use the simple text command print. We can change the color of the text using settextforeground. Note that for every set command there is a corresponding function to return the parameter’s status, such as textforeground. settextforeground takes a number as a parameter, which represents a color (type showcolors in the console to see a list of colors and their numeric values). However, turtleSpaces has a number of color primitives (red, orange, yellow etc.) that return their respective numeric values, so that you can say, for example, setttextforeground yellow (which works because yellow is a function that returns the number 13.)

The print command itself takes either a word (a single word preceded with a quote “) a list (a series of words wrapped in square brackets []) or a ‘long word’ (a series of words wrapped in pipes ||). You can use either a list or a long word to display a sentence, eg print [Welcome to turtleSpaces!] or print |This also works.| The advantage to using a list is that you can cherry-pick one word from it easily, eg print first [Welcome to turtleSpaces!], however it can be more complicated when pairing text with numeric values, for example, when using a long word would be preferable.

Note that print “Welcome to turtleSpaces” will throw an error, because the parser will attempt to execute to and turtleSpaces” as commands.

We can use cursordown to put a blank line between sections of text. We can also say text as well, to make things more interesting!

Next, we need to create some default containers (variables). We can create these using the make primitive, which is the traditional method in Logo (eg make “container 5), although turtleSpaces also supports put x in “container and “container := x (note the colon before the equals). A colon before a word in Logo returns the value stored in the container of that name, eg :container. But make needs the container name specified with a quote ” — this is because if you used a colon, make would create a container with the name of the value stored inside that container!

We need to set the initial :level (0). We’re also going to create a container containing the number of available ‘retries’ (we will give the player limited opportunities to generate a new level if the level provided to them is impossible.)

make “level 0 make “retry 3

Notice that we can ‘stack’ multiple commands on the same line! This can sometimes be a blessing, sometimes a curse, so use your discretion when stacking commands.

Finally, we’re going to declare a label, which can be returned to using the go primitive:

label “start

so that we can return here at the start of a new level.

My God, It’s Full of Stars!

It’s time to start building the graphical environment in which the game will take place. First, let’s create a starfield. This is a fairly simple algorithm:

  • set a ‘static’ z (depth) position
  • set a random x and y position
  • set a random ‘fill’ shade and color
  • set a random fill opacity
  • create a spot of varying size
  • do this all 100 times

In the code we use the setxy and setz primitives to accomplish the first two items, and the random function to generate the random values we need to give to each. There’s some turtleSpaces-specific primitives such as randomfillshade and randomfillcolor (the color used to create shapes is the ‘fill’ color, as opposed to the color used to create lines which is the ‘pen’ color.)

We wrap all of this in a repeat loop, which repeats the contents of the list provided to it the given number of times, for example repeat 20 [print repcount] which counts from 1 to 20.

We actually need to create two starfields, one behind the hill, and one in front of it (so that we see stars regardless of which direction we’re facing, either looking at the hill, or from it while we’re playing. We can make the single loop do double-duty by using the oddp primitive which returns true if the value passed to it is odd. As you saw with the above example, repcount returns the current repeat loop iteration, and so if we pass repcount to oddp, then oddp will be true on odd iterations of the repeat loop.

We can then pass oddp to if, which will then execute the first list of instruction if oddp is true, and the second list of instructions if it is false. In this way, we set the z position of the turtle in an alternating fashion.

Note that the back side of a spot is not ‘lit’ and so we need to enable twosided mode before we create the stars to ensure both sides of the spot are lit, since we’re going to be looking at them from both directions.

Making Ground

Next, we’re going to create the ground. This is another spot, like the stars, just a very big one! We use setxy and setz to position the turtle in the lower-centre of the screen, and then tilt it up 90 degrees (so that we see the ‘dark’ back side of the spot), setting random fill and shade colors before making the spot. The value passed to spot signifies its radius, and is given as ‘turtle units’, the same unit of measure used by all shapes and lines.

To choose the fill color we use the pick primitive, which randomly selects an item from a list, eg print pick [1 3 5 7 9]

The landing area is where our skiball will end up assuming the player avoids all of the trees. This is created using the quad primitive, which creates a filled rectangle. A container is created containing a random value signifying the left (from this perspective) side of the target area. It’s width is dependent on the level, but to begin with, it could just be arbitrary (eg 100 ‘turtle units’ wide). We will use the value in this container later, to determine if the player is inside the target zone.

There are, then, three quads, the left side before the target zone, the target zone itself, and the right side after the target zone. We color the target zone dark blue to distinguish it from the rest of the landing area.

Finally, we create the ski hill itself. Its angle does not matter that much, as from now forward the turtle will operate on the plane of the hill. In this example we’ve used the gradient mode to create a gradient fill on the quad that makes up the ski hill. In gradient mode, the pen and fill colors are both used to create the gradient, the pen color being the ‘near’ color and the fill color being the ‘far’ color.

It’s time to populate the hill with trees.

Seeing the forest for the trees

When this game was first developed, the ‘trees’ were simple icospheres. In creating a lesson plan, you can take two approaches here:

  • Create and validate the core game logic first, using a placeholder such as a sphere
  • Create the trees first, and then develop the game logic later

This is really a choice between whether we have vegetables or dessert first, but here we’ll start with dessert.

The trees are made up of firstly a cylinder (for the trunk), then a series of cutcones and then finally an icosphere on the top. We select the color of each trunk and tree using the pick primitive, and select a random fillshade for each layer of the tree.

We need to do a bit of turtle acrobatics to ensure the trees are upright relative to the ground, and not upright relative to the slope! Shapes are created beneath the turtle, and so as a result the turtle needs to be ‘flipped over’ and the trees trees created successively from the trunk ‘downward’ to the icosphere on the top of the tree.

From a default startup state, type cylinder 20 50 20 into the console, then click and drag on the view area to rotate the camera:

As you can see, the cylinder is created beneath the turtle. To create additional shapes at the opposite end of the cylinder, we will need to lower the turtle the length of the cylinder.

cutcones are similarly created under the turtle. However, icospheres are created around the turtle. See the shape guides available on this website for more information about shape orientations relative to the turtle.

And so we sweep left to right, working up the slope, deciding at each ‘stop’ whether or not to build a tree, and then noting in the list for the current row if we have created a tree at the current column.

Here comes the vegetables. At the start of each row we create a list:

make word “trees repcount []

word combines two inputs into a single output. We’re using it here to create a new list container [] based on the current repcount, prefixing it with the word “trees, creating for example :trees11

As we sweep across each row, we use the queue primitive to add either a 0 (no tree) or a 1 (tree) to the end of the list:

queue 1 word “trees repabove 1

repabove returns the iteration of the parent repeat loop, in this case the row repeat loop. Using repcount here would return the current column, as we are iterating through the row.

We will use these lists later to cross-reference with the turtle’s position and reveal if a tree is present at that location.

Introducing the Player

Once we’ve created all the trees, we could spend a bunch of time creating an actual skier model, but I like just using the amigaball, which creates a checkered ball based on the current fill and pen colors. We use setmodel to make it the turtle model. Later on we can use setpremodel to make it ‘roll’ down the hill, which looks pretty cool! Note that we will need to elevate the turtle the radius of the amigaball to ensure it’s ‘sitting’ on the slope.

Skiing the Slopes

So basically now we’re ready to position the turtle at the top of the slope. We pick a random location and then check :trees40 and :trees39 to ensure there isn’t a tree at the random location, or below it, respectively. We do this by using a container called :pos which contains the column number occupied by the player.

Alright, so now we enter the main game loop, which is a repeat loop based on the number of rows (40):

  • Check for keypresses and action them as appropriate
  • Move the ball down one row
  • Check for collisions and act accordingly

We use if to check keyp which returns true if there are any keypresses waiting in the keyboard buffer. If there are, we use peekchar to ‘peek’ at what the first keypress in the buffer is. If the key is j or k we check our :pos and if valid we then move the turtle player and increment or decrement the :pos container as appropriate.

We can also check for a press of the r key and then initiate a ‘retry’ but that is something that we can add in later. We don’t need to deal with it now.

Once we’ve dealt with the keypress, we can remove it and any superfluous keypresses from the key buffer using clearchar

The second thing we do in the loop is move the turtle forward 10 turtle units (one row). This is pretty straightforward.

Third, we check for a collision:

if 1 = item :pos thing word “trees 40 – repcount [do this stuff…]

Whew! That’s a mouthful, isn’t it? The first thing you’ll notice is the meat of our sandwich is on the right — other programming languages would tend to have the sides of the equals reversed, with the complicated bit first followed by the = 1. But Logo ‘parses’ right to left, which means that if we did that, the = comparator would only check 1 against repcount, ignoring all the rest of it.

We can solve that problem by putting round brackets () around the entire expression (the meat) but that adds extra clutter we don’t need if we just put the value first.

  • item returns the nth list item. So print item 3 [1 2 3] will return 3, for example. item :pos returns the list item based on the player’s current position.
  • thing returns the value stored in the container of the given name. It’s the longhand of :container. But it’s useful here because unlike using the colon method, we can construct the name of the thing to be retrieved, as we did with make earlier.
  • word acts similarly here to when we created the lists, with the exception of subtracting repcount from 40 in order to get the current row list in descending order.
  • And so, if the value returned from all of that is 1, we’ve hit a tree! Do the stuff in the list.

Ouch! Well, when all else fails, try, try again… we can use a label go pairing to send the player back up to the top, logically speaking. And we can wrap a tag around the player’s progress down the hill so we can erase it, sending the player turtle back to where it started, turtle-y speaking.

If we make it to the bottom of the hill we need to check to see if we’re in the target zone:

if and xpos > -200 + :zone xpos < -100 + :zone – 10 * :level [do this stuff…]

xpos holds the turtle’s current x position. We can use it along with the value stored in the :zone container to decide if we’re in the zone. This statement assumes we’ve factored the :level into the width of the :zone.

If we’re in the zone, congratulations! Then (if we’re not on the last level) we increase the level and send the player off to try again. If we’re not in the zone, too bad, and we send the player off to try the level again.

Of course, the version available to play has a few more bells and whistles, you can read all about them in the documented source code below.

Note: This was created as a monolith procedure, but you could break bits of it out into separate procedures in order to demonstrate Logo’s abilities in that area. For example, the tree model creation could be its own procedure, as could the contents of the main game loop, the crash logic, the level completion logic and so forth.

TO downhill
  ;myrtle builds a ski slope and then rolls down
  ;it in the guise of a rolling amiga ball.
  
  ;this is a wonderful introduction to game
  ;development in turtlespaces. The game mechanic
  ;is quite simple:
  
  ;the player needs to travel down the ski slope
  ;while avoiding the randomly-placed trees
  ;and finish in the highlighted zone.
  
  ;most of this procedure is window-dressing.
  ;at its core it simply fills a number of lists
  ;with 0s and 1s, and then checks these against
  ;indices inferred by the player's position, eg
  ;the 'row' being the current iteration of the
  ;player's progress down the ski hill, and the
  ;column being the player's horizontal location
  ;on that hill. The row matches against the list
  ;generated with the row number, eg :trees 10
  ;while the column (:pos) matches with the item
  ;number in that list, eg item :pos :trees10
  
  ;this game would be rather pedestrian with a
  ;simple text representation, but in turtlespaces
  ;we can 'jazz it up' and make it more engaging.
  
  ;Let's go!
  
  reset
  cam:pullout 10
  ;the slope doesn't quite all fit in the screen!
  ;so we'll pull out the camera turtle a little bit.
  ;'cam' is shorthand for whichever turtle is the current
  ;view turtle
  
  cleartext settextforeground red
  print |Welcome to Amigaball Backcountry Downhill Skiing!|
  ;bars indicate a 'long word' or a word that contains spaces
  ;while quotes indicate a single word and square brackets
  ;indicate a list. Curly braces indicate a list 'resolved'
  ;when the list is processed during execution. (Don't worry
  ;if you don't know what that means just yet!)
  
  cursordown
  settextforeground yellow
  print |Press J and K to steer your skier (Amigaball)|
  print |Land in the Blue Zone. Don't hit the trees!|
  print |More trees each level. Try to make it to level 10.|
  print |Press R to get a different map (3 retries) Q to quit|
  say |Welcome. Don't hit the trees!|
  make "level 0
  ;'make' creates a container with the given name containing the given value
  
  make "retry 3
  ;although the odds of trees on lower levels is less,
  ;it is still possible to get a very dense level if you are unlucky!
  ;and so we'll give the player three opportunities to regenerate
  ;the level
  
  ;hint: if you open this in the web IDE you can hover
  ;over the primitive names with the mouse and a popup will
  ;appear with their syntaxes
  
  label "start
  ;we will return here at the start of each 'level'
  ;the program gets redirected to labels using 'go'
  
  ;create the 'mountain':
  clearscreen hideturtle penup
  ;you can 'stack' multiple directives on the same line
  
  twosided ; by default the back side of faces is not 'lit'
  
  ;make the background stars:
  repeat 200 [
    if oddp repcount [setz -600] [setz 600]
    setxy -800 + random 1600 -100 + random 600
    randomfillshade ; aka randfs
    randomfillcolor ; aka randfc
    setfillopacity 50 + random 50 ; aka setfo
    ;opacity is the transparency of the shape
    ;it takes a value from 0 to 100 (full)
    spot 2 + random 7
  ]
  
  setfo 100 ; aka setfillopacity
  
  setz 0
  setxy 0 -115
  
  ;create the 'ground spot':
  notwosided
  up 90 raise 1
  setfs -5 + random 15 ; aka setfillshade
  setfc pick [4 5 8 9] ; aka setfillcolor
  spot 1000
  slideleft 200 lower 1
  
  ;pick colors:
  setbg pick [2 6 7] ; aka setbackgroundcolor
  setbs -5 + random 15 ; aka setbackgroundshade
  setpc pick [5 6] ; aka setpencolor
  setps random 10 ; aka setpenshade
  setfc pick [10 15 7 14] ;aka setfillcolor
  setfs -10 + random 10
  gradient
  ;this causes many shapes to transition from the
  ;pen color to the fill color
  
  ;create the landing area:
  make "zone random 300
  ;pick the 'target zone' area
  
  ;containers that hold values are created using 'make'
  ;with the given name and value
  
  twosided
  quad :zone 400 ; creates a quad stretching to the start of the zone
  ;values in containers are retrieved by specifying the container name
  ;prefaced with a colon. You can also use the 'thing' function to retrieve
  ;the value of containers, eg 'quad thing "zone 400'
  slideright :zone ; aka sr
  make "pencol pencolor
  ;we need to store the pencolor and restore it later
  setpc blue ; several color names translate to their color number
  quad 100 - 10 * :level 400 ; create the zone based on the level
  sr 100 - 10 * :level
  setpc :pencol
  ;this is where we restore the pencolor
  quad 300 - :zone + 10 * :level 400 ; create the remainder of the landing area
  sl 100 + :zone - 10 * :level ; return to the start
  
  
  ;create the ski slope:
  notwosided
  down 120
  quad 400 400
  nogradient
  
  
  ;position the turtle for creating trees:
  fd 5 ; aka forward
  sr 5 ; aka slideright
  
  ;place the trees:
  repeat 40 [
    ;we'll have 40 rows of trees
    
    make word "trees repcount []
    ;our strategy here is to make a series of lists, each of which
    ;indicate the presence or absence of a tree in a particular
    ;location on a particular 'line' of the ski slope
    
    repeat 39 [
      ;there are 39 columns on each row of the ski slope
      sr 10
      
      if 1 = random 10 - :level [
        ;the higher the level the more likely a tree will be
        ;placed in a given spot
        
        noise (10 * repcount) + (200 * repabove 1) 1
        ;let's make a little noise based on position
        
        up 150
        ;most shapes are created under the turtle, so we need
        ;to flip it over
        
        make "size 0.5 + 0.1 * random 10
        ;pick a random size
        
        setfc pick [5 8 9]
        cylinder (1 + 0.1 * random 10) * :size 10 * :size 4 + random 10
        ;create the tree trunk. cylinder takes radius, depth and sides
        
        lo 10 * :size
        ;remember, these shapes are created under the turtle
        ;so we have to 'lower' to continue to build 'beneath' them
        
        setfc pick [4 5 6 7 12 14]
        
        ;create the body of the tree:
        repeat 5 [
          randfs
          cutcone (7 - repcount) * :size (5 - repcount) * :size 2 * :size (5 + random 10) * :size
          ;cutcone takes 4 parameters: the starting radius, ending radius, depth and sides
          lo 2 * :size
        ]
        
        ;put a little 'cherry' on the top of each tree:
        randfc ico 1 * :size
        
        ra 20 * :size
        dn 150
        ;return to the base of the tree and reorient back
        
        queue 1 word "trees repabove 1
        ;a 1 in the list indicates a tree
        ;queue adds it to the end of the list
      ]
      [
        ;this second list executes if the if condition is false:
        queue 0 word "trees repabove 1
        ;a 0 indicates no tree
      ]
    ]
    sl 390
    fd 10
    ;move ahead a row and return back to the starting column
    
  ]
  ;return to the start of the repeat loop and loop until finished
  
  ;we're done making trees! Let's place the player:
  
  bk 10
  ;pick a random placement:
  make "pos 5 + random 30
  sr 10 * :pos
  ;and check to make sure there's no tree there or directly in front:
  until (
    and 0 = item :pos :trees39
    0 = item :pos :trees40) [
    sr 10
    inc "pos
    if :pos > 39 [sl 400 make "pos 1
      ;if we hit the end of the row, circle back to the start
    ]
  ]
  rt 180
  ;turn to face down the hill
  ra 5
  ;elevate a little bit off the hill
  
  make "startpos :pos
  ;store the starting position to return back to it later
  
  label "attempt
  ;we will return here if the player's attempt fails
  
  setpremodel []
  ;setpremodel allows for the manipulation of the orientation
  ;and position of the turtle model.
  ;we use setpremodel later and need to clear it if
  ;the player needs another attempt
  
  begintag "route
  ;we're going to place the players 'route' into a 'tag'
  ;so that we can erase it if the player's attempt fails.
  ;this way we can keep the level we've generated and
  ;they can try again
  
  setview "myrtle
  ;view the scene from myrtle's perspective
  myrtle:setlight 1
  snappy:setlight 0
  ;switch the light to myrtle's position
  setviewpoint [0 10 -30]
  ;set the viewpoint offset from myrtle's position
  
  randpc ; aka randompencolor
  setpenwidth 4 ; make the turtle draw fat lines
  
  pendown ; let's draw the route the player takes!
  ;because why not? It'll look interesting later
  
  clearchar
  ;clears the keyboard buffer so the player doesn't
  ;end up with a bunch of buffered keypresses
  ;influencing movement
  
  setmodel [
    setfc red
    ico 5 ; aka icosphere
  ]
  ;sets the turtle 'model' to the given contents
  ;setmodel can also use tags
  
  ;Ready set go:
  showturtle
  type "Ready...
  toot 1000 30
  ;frequency and duration in 60ths of a second
  
  setmodel [
    setfc white
    ico 5
  ]
  type "Set...
  toot 1000 30
  print "Go!
  setmodel [
    setfc red
    setpc white
    amigaball 5 10 20
    ;creates a chequred ball
  ]
  toot 2000 60
  ;and we're off!
  
  repeat 39 [
    setpremodel {"dn repcount * 20}
    ;the premodel processor doesn't know what 'repcount' is, and so we need
    ;to use curly braces to make a 'soft list' that is evaluated when it is
    ;executed -- that is, repcount is resolved into its value, which is then
    ;passed to setpremodel
    
    fd 10
    ;move forward
    
    if keyp [
      ;has a key been pressed?
      
      if and :pos < 39 "j = lowercase peekchar [ ;if it's the j key and the position is less than 39: sl 10 ; aka slideleft inc "pos ; increment the 'pos' container value ] if and :pos > 0 "k = lowercase peekchar [
        ;similarly, if the k key and the position is greater than 0:
        sr 10 ; aka slideright
        dec "pos ; decrement the 'pos' container value
      ]
      
      if and :retry > 0 "r = lowercase peekchar [
        ;if the player presses the r key and has retries remaining:
        dec "retry
        (print :retry |retries remaining...|)
        setview "snappy
        snappy:setlight 1
        setlight 0
        ;switch the light and view back to snappy, the default view turtle
        endtag
        ;close the route tag
        go "start
        ;jump back to the start label
      ]
      
      if "q = lowercase peekchar [finish]
      ;I quit!
      
      clearchar
      ;clear the key buffer
    ]
    
    if 1 = item :pos thing word "trees 40 - repcount [
      ;are we where a tree is?
      
      ; - 'thing' returns the value of the given container.
      ;   we need to use it because we programatically
      ;   created a series of list containers earlier named
      ;   trees0 trees1 trees2 etc. and so we're using thing
      ;   and 'word' to create the appropriate container name
      ;   and then using 'item' to retrieve the value in the
      ;   relevant column (list index)
      
      ; - 'repcount' returns the current iteration of the
      ;   repeat loop. there is a similar function for other
      ;   loops called 'loopcount' and indeed loopcount will
      ;   also return the current repeat loop iteration. But
      ;   repcount is used by a number of different Logos and
      ;   it's a useful distinction
      
      ; so apparently we did:
      
      pr |Ouch! You Crashed! Try again.| ; aka print
      playsound "crash
      say pick [|too bad| |ouch| |that hurt| |dont hit the trees| |wipeout|]
      
      setview "snappy
      snappy:setlight 1
      setlight 0
      ;return the view and light back to 'snappy' the default view turtle
      
      repeat 2 [ht wait 30 st wait 30] ; aka hideturtle and showturtle
      ;wait causes the program to pause for the given number of 60ths of a second
      ;this is due to early computers operating at 60 frames per second
      ;you can also use 'sleep' which takes milliseconds
      
      make "pos :startpos
      ;return the value of the 'pos' container to its starting value
      endtag
      ;close the 'route' tag
      erasetag "route
      ;erase the 'route' tag. This returns the turtle to the position
      ;it was at at the start of the tag, as we have erased part of its
      ;'turtle track'
      go "attempt
      ;return to the 'attempt' label and continue execution from there
    ]
    ;indicates the end of instructions to execute if the player hits a tree
    
    noise repcount * 200 (40 - repcount) * 0.5
    wait (40 - repcount - :level) * 0.5
    ;make a little static noise based on progress down the hill
    ;and similarly wait a little bit based on the progress and the level.
    ;this makes us speed up as we descend the hill
    
  ]
  
  ;we've made it to the bottom!
  up 60
  ;reorient the turtle upward
  
  repeat 100 [
    setpremodel {"fd repcount "dn 10 * repcount}
    ;this causes the ball to move ahead of the camera
    setmodel [
      setfc 1 setpc 15
      setfillopacity 100 - repcount
      setpenopacity 100 - repcount
      amigaball 5 10 20
      ;this causes the ball to fade out
    ]
    setpenopacity 100 - repcount
    ;this causes the line to fade out
    forward 1 noise 8000 - 80 * repcount 1]
  ;do a little victory 'lap'
  
  endtag
  ;close the 'route' tag
  
  setfo 100 setpo 100 ; aka setfillopacity setpenopacity
  ;set the fill and pen opacity back to full
  
  setview "snappy
  snappy:setlight 1
  setlight 0
  ;set the view and light back to snappy, the default view turtle
  
  if and xpos > -200 + :zone xpos < -100 + :zone - 10 * :level [
    ;are we in the zone? if so:
    
    say pick [|great job| |good effort| |way to go|]
    playsound "cheers
    inc "level
    
    if :level < 10 [
      (print |Great Job! Try level| word 1 + :level "!)
      ;round brackets allow more than the default number of
      ;parameters to be 'passed' to a primitive, in this case
      ;print
      
      go "start
      ;return back to the 'start' label to create a new level
    ]
    
    print |You Win! Game Over.|
    say |you win|
    finish
    ;this finish isn't strictly necessary because we will fall out
    ;of execution at this point anyway...
  ] [
    ;we're not in the zone! Let's try this again:
    
    playsound "aw
    print |You missed the Blue Zone! Try Again.|
    say pick [|try again| |go again| |you can do it| |you'll get it this time|]
    wait 120
    make "pos :startpos
    erasetag "route
    go "attempt
  ]
  ;that's all folks!
END

 

Random Sine Wave Flowers Made With turtleSpaces Logo

This simple yet attention-grabbing Logo procedure is a real visual treat, creating random three-dimensional ‘flowers’ based on the sine function. This could spice up a math class or just provide a brief introduction to sine waves in general.

The procedure makes use of the move primitive, which allows the turtle to move based on an arbitrary number of degrees away from its current heading, on the X and Y planes. When combined with the sine function, it produces these interesting ‘natural’ 3D designs.

Live model view – click and drag to rotate
TO randomflower
  clearscreen randompencolor
  ;clear the screen and select a random
  ;pen color
  
  dountil and 200 < abs (:x - :y) 200 < abs (:y - :x) [
    make "x random 2000 make "y random 2000
  ]
  ;set :x and :y containers to a random value between 0 and 2000
  ;BUT make sure they aren't within 200 of each other.
  ;The results in that range aren't aesthetically pleasing.
  
  repeat 3600 [
    ;we move down a tenth of a degree each iteration, so to form
    ;a complete 'circle' we need 3600 repetitions.
    
    move 0.5 {:x * sin repcount :y * sin repcount}
    ;the move primitive takes a number of turtle steps as its
    ;first argument, and then either a single degree value (X)
    ;or a list of two values (X and Y), which turn the turtle
    ;the specified number of degrees on each plane.
    
    ;In the case of this example, we're passing a multiple of
    ;:x and :y based on the sine of the current interation
    ;(repcount). This produces the 'flower' effect.
    
    down 0.1
  ]
END

You could put a wait into the loop so that students can see the process a bit more clearly.

Don’t forget that you can click and drag the view to orbit around the model, click with both buttons and drag to ‘pan’ the orbit point, and use the mouse wheel (or right-click and drag up and down) to zoom in and out.

Children can pick their favourites and share them with the class!

This is the flower listing as seen in the turtleSpaces application editor

PS Adding the following two lines into the repeat 3600 loop can add a little ‘pizzaz’ in the form of frags, ‘fragments’ (think of a broken mirror) or triangles made of three arbitrary points. The frag primitive creates a triangle from the last three points the turtle has stopped at, while pin and pinfrag drop ‘pins’ at points you choose and generate triangles from the last three of those pin points respectively. When combined with the sine flower procedure they add a bit of ornamentation. Just remember to setfillcolor!

    if 0 = remainder repcount 10 [pin] 
    if 0 = remainder repcount 30 [pinfrag]

Thanks for reading! Please let us know how you go at integrating turtleSpaces into your classroom’s studies. E-mail us at help@turtlespaces.org 

Past and Future Turtles: The Evolution of the Logo Programming Language (Part 1)

When I was a young child, one of my best friends was a turtle.

Not a real turtle, although that would have been fun, but a virtual one. My turtle lived inside of an Apple II, an early 8-bit computer, at my elementary school.

When I first met her, my turtle, she didn’t do much. She just sat there. It didn’t take long for me to realize that in order to get her to do something, I would need to prompt her. To do this, there was a literal prompt on the screen, beckoning me to type something.

?

And so I typed FORWARD, thinking that this should, obviously, move the turtle forward.

NOT ENOUGH INPUTS TO FORWARD

Undeterred, I revised my input to FORWARD 10, just picking an arbitrary number. Although I was only six years old, I deduced that FORWARD needed a quantity, quantities were numbers and well, ten was a good start.

And hooray, the turtle moved! I was in love with my turtle from that moment on. I learned that she could also move BACK, and turn LEFT and RIGHT. I didn’t know what degrees were when I first met my turtle, but by the time I finished our first session together, I did!

Seymour Papert in 1971

That afternoon in 1981, I had learned some of a programming language called Logo. The father of Logo was a mathematician and philosopher named Seymour Papert. Interested in learning how to improve mathematics education in young children, in the early 1960s, he had studied under Jean Piaget, a Swiss psychologist who championed a philosophy of learning known as constructivism.

When children are given new information, for example, that a full turn is made up of 360 degrees, they are likely to only accept that information if they already have the underlying understanding required, such as that a turn can be divided in half, quarters and so on, down to 360ths. Or even the concept of a number as high as 360 can exist in the first place.

Otherwise, they are likely to either misinterpret the new information to fit their current understanding (“I only understand numbers as high as 100, and so they must mean 100”) or will simply ignore it, with a silent internal ‘syntax error’.

This can improve if a) the teacher explains numbers up to 360, b) the teacher explains division and then finally c) the teacher explains that when you spin around, you are making a circle (hold out your arm! The point of your finger draws a circle when you spin) and if you spin around halfway, you’re moving 180 degrees and so on.

This may still not be enough. If the child fails to understand a, b and / or c, they still won’t understand it.

However, if the child is able to actively explore these concepts in an environment that has some measure of feedback, the likelihood of their gaining understanding improves. For example, if the teacher suggests they turn randomly, and the teacher tells them how far they have turned each time they do it, they will soon learn that if they turn halfway, they have turned 180 degrees. They will learn that a full turn is 360 degrees. They will understand that the next number after 100 is 101, and hence the next number after 200 is 201. And so on.

This is what Papert learned under Piaget. But obviously, while desirable it’s not practical for each child to have an adult giving them feedback on everything they do until they understand it. The world just doesn’t (and probably will never have) a 1:1 student to teacher ratio. This was a problem Piaget and Papert simply couldn’t surmount.

But in the early 1960s, a new technology was emerging, one that Papert realized could remove his roadblock – the computer.

Computers did not tire, they did not lose patience. Although at that time large and small in number, it was not hard for Papert to see the future, a future where schools were full of terminals connected to computers, one for each child, with which the students could explore ‘mathland’, a simple environment where mathematical concepts could be demonstrated through trial and error. The more input students provided, and the more output they absorbed, the sooner they would grasp concepts. Computers seemed a perfect fit for the task of educating children using constructivist principles.

In 1961, Papert had met an American, Marvin Minsky, at a conference in England. They presented papers that were surprisingly similar, and connected as a result. So, powered by his idea of using computers to teach children, in 1964, Papert went to the US, and joined Minskey’s Artificial Intelligence Group, a team of researchers at the Massachusetts Institute of Technology (MIT).

Papert was impressed with the technology-centric culture at MIT, and attitude that everyone had the potential to learn anything they wanted to, a position Papert shared. The students under Minsky and Papert had free rein to work on whatever they wanted, encouraging experimentation and the practical testing of even the most wild theories. Papert saw how his students, when encouraged, would learn great amounts of knowledge on their own, and collaborate with others to get past roadblocks they encountered in their pursuits. At MIT these students were known as ‘hackers’.

One of these hackers, Daniel Bobrow, graduated and got a job at a research and development (R&D) firm called Bolt, Beranek and Newman. In the mid-1960s they had started exploring computer technologies, and their potential. Bobrow became head of their new Artificial Intelligence group. There, he met Wally Feurzeig, who was leading BBN’s education group. The space race had encouraged the US government to find ways of improving math education in schools, and BBN was hoping to find a solution. They had already developed ‘time-sharing’ technology which allowed a mainframe computer to be used by many people at the same time, they just needed software to run on it that children could use to learn.

Bobrow brought in Papert and introduced him to Feurzeig, and the three discussed the idea of developing a computing language for children, Papert’s Mathland. Around that time, BBN had a visitor who demonstrated a new programming language called BASIC. Rather than using memory addresses, BASIC used line numbers to track execution, and named variables that transparently linked to memory addresses. These abstractions made it much easier for non-computer scientists to create computer programs.

BBN’s researchers created a version of BASIC they called Telcomp, which Feurzeig modified to include strings, and called Stringcomp. BBN trialed Stringcomp (using teletypes, typewriters connected to a remote computer) in eight elementary and junior high-school classrooms in 1966. That year, Minsky’s assistant at MIT, Cynthia Solomon, joined Feurzeig’s team at MIT.

Seymour Papert at Muzzey Junior High School in 1966

After Papert visited these classrooms and saw the interaction between children and Stringcomp, he became convinced that BASIC was not the answer, and that a new language would need to be written, one more suited to learning.

Importantly, the new language would need to more adequately demonstrate how the computer came to its conclusions. Papert felt that due to computers’ requirements for literal instruction, children would be able to see how the computer ‘thought’ and realize that was how they themselves also thought, helping them break down problems into smaller elements the way a computer would when it solved them. Thus, the new language would serve a dual purpose, both teaching mathematics, and how mathematics are solved.

BASIC was too monolithic in its command-based structure, and those ‘smaller parts’ could not be shown to its users, happening behind the scenes and out of sight.

And so, the new language would both need to be able to accomplish what one could accomplish in BASIC, while doing so using smaller steps that could be inspected by novice programmers. But to avoid needless and redundant repetition of code, these steps would need to be able to be reused. Because of the philosophy behind it, Wally Feurzeig christened the new language ‘Logo’, derived from Logos, the greek word for ‘thought’.

Minsky, Papert and Solomon had a number of discussions about how Logo would work. Solomon had learned some of the Lisp programming language (a language where all code and data are written in the form of expressions and lists, the name Lisp short for LISt Processing) while Minsky’s assistant, and she appreciated its power for manipulating English language strings, which she recognized could be used as a method by which students could observe the effects of computer code on recognizable data, or data that they created themselves.

It also probably didn’t hurt that Lisp had been invented by the same BBN employee (and MIT alumnus) that had developed its time-sharing system. And so, it was decided that the new language would be a variant of Lisp.

However, Lisp had some drawbacks in terms of its use with younger coders. It had an extreme reliance on structure, using an intricate array of parentheses which often led to errors that were hard to resolve (leading to nicknames Lost in Stupid Brackets and Lots of Irritating Superfluous Parentheses). Its interpreter was also bulky, and not interactive, like BASIC.

In designing Logo, Papert, Minsky and Solomon took the expression, list and string elements of Lisp, removing much of the need for brackets by making assumptions about syntax parsing and inputs, and later allowing for the ‘stacking’ of what they called ‘primitives’ (functions which may or may not resolve to a value) on a single line, such that you could type:

PRINT SUM 4 4

and the computer would output 8, or:

TYPE 1 TYPE 2 TYPE 3

and the computer would output 123.

Lists allowed for the string manipulation Solomon had been hoping for:

(PRINT PICK [RED GREEN BLUE] PICK [DOG CAT BOAT])

BLUE BOAT

RED DOG

GREEN CAT

etc.

Logo was a lot more BASIC-like in its syntax and usage, while allowing for many features BASIC did not, such as the ability to create ‘procedures’, lists of primitives that could be named, and executed just like any other primitive, such as:

TO BOX

   REPEAT 4 [
      FORWARD 10
      RIGHT 90
   ] 

END

which could be executed simply by typing:

BOX

but even better:

TO POLY :SIDES :SIZE

   REPEAT :SIDES [
      FORWARD :SIZE
      RIGHT 360 / :SIDES
   ]

END

the colon indicating a variable or container for storing a value.

POLY 4 10

would re-create our box, but the user could provide any desired values, like SUM. This satisfied Papert’s need to have both the ability to demonstrate computer problem-solving in smaller chunks, while also allowing for the reuse of code. Students could create the BOX or POLY procedures themselves, then use them in other procedures. A picture would form in their minds of the hierarchy of execution, and they would understand better how the computer, and they themselves, thought.

Except that we are getting ahead of ourselves with BOX and POLY, because in the beginning Logo did not have a turtle! When Logo was initially trialled in a seventh-grade classroom (in 1968 at Muzzey Junior High School in Lexington, Massachusetts), students were using the same teletypes the Stringcomp trial had used, and they used Logo to manipulate lists and strings rather than a turtle. Which worked quite well to engage students who were strong in English, but failed to engage those who were not.

Cynthia Solomon (left) and Seymour Papert (right) at Muzzey Junior High in 1966

Also, on the teletype math was still just numbers, regardless of the more elegant way Logo could break down mathematical equations. Kids who didn’t ‘get’ numbers still didn’t get numbers! Papert craved some sort of graphical representation, but the technology at the time strongly precluded that. And so, towards the end of the trial, he had the idea of creating a physical robot, one that could move about on the floor, and draw out graphical representations of mathematic output.

Papert and Solomon left BBN and headed back to MIT, where they formed the Logo Group. In England, two robots had been developed that could wander about a space, their inventor dubbing them ‘tortoises’. The Logo Group developed their own robots, calling them turtles. Having access to expensive display hardware, they also developed a virtual turtle, one that lived on the computer screen.

They added primitives to Logo to control the turtle: FORWARD, BACK LEFT, RIGHT. And they discovered that Papert had been right – children took to the turtle, the turtle allowing Logo users to have a perspective inside the computer, that of the turtle, rather than depending on an understanding of abstract concepts of code paths or numbers. The procedural structure of Logo meant the turtle could be ‘taught’: taught how to draw a square, taught how to draw a polygon, creating them inside of its environment, interacting it and shaping it like a child scrawling on the wall of their bedroom.

The turtle allowed for an empathetic bond to form between it and the child, giving the child a dual sense of accomplishment when the turtle succeeded at what the child told it to do – both because the child correctly instructed the turtle, and the turtle successfully carried out those instructions, the result of the latter the evidence of the former. The child could also ‘think like the turtle’ when developing procedures and debugging, moving about the room as the turtle would, figuring out roughly what they needed the turtle to do, and then refining their program as needed.

An early Logo video display

While Logo’s string and list facilities are impressive, it is fair to say the turtle made Logo. Both the physical and virtual turtles were used by fifth graders at the Bridge School in Lexington in a trial during 1970 and 1971. But both robots and video terminals were expensive in the 1970s, and schools hadn’t widely adopted computer terminals; as such, there was no widespread use of Logo in the 1970s.

However, the emergence of the home computer market in the late 1970s provided an opportunity for Logo to reach a wider segment of the population. In 1980, Minskey, Papert, Solomon and two Canadians founded Logo Computer Systems Inc. (LCSI) and  in 1981 they published a version of Logo for the Apple II computer, which I booted up and met Seymour, Cynthia and Marvin’s turtle for the first time.

They would have been very happy I learned about degrees (and other things) from their turtle!

But where did Logo go from there? Carry on to Past and Future Turtles: Logo’s Adventures in Academia (Part 2)

And don’t forget to try our weblogo… or read the Hacker News discussion!

One-A-Day: Qtips

Today’s example is short but sweet. It creates a design made out of a bunch of qtip-like ‘sticks’ with balls on the ends.

It is a design made out of ‘almost squares’ (four sides each at an 85 degree angle to each other). The turtle then turns right 5 degrees, and slides left 20 before continuing to the next almost-square.

Each side of the almost-square is coloured based on its iteration in the almost-square loop, and the fill colour of the balls is set to match. Also, each iteration is raised an amount relative to its iteration, creating a pleasing 3D effect.

Click here to open the project in the viewer or open it in the webLogo IDE

TO qtips
  hideturtle
  clearscreen
  repeat 24 [
    repeat 4 [
      penup 
      setz repcount * 3 
      ;raise up the turtle based on the loop count
      pendown 
      setpencolor 10 + repcount
      ;set the pen color based on the loop count
      setfillcolor pencolor 
      ;for the spheres
      icosphere 2 
      rope 100 
      ;a rope is a cylinder-based line
      icosphere 2 
      right 85
    ] 
    right 5 
    penup
    slideleft 20 
    pendown
  ]
END

One-a-Day: FRAG

The FRAG primitive creates a filled shape out of the current turtle position and her last two positions. For example:

FD 100 RT 90 FD 100 FRAG

will create a triangle.

While turtleSpaces has a variety of shape primitives, sometimes you need to create an arbitrary shape, and FRAG aids you in this.

Take this example, which draws a star:

TO star
  repeat 4 [
    forward 100
    right 170
    forward 86
    frag
    left 70
    forward 86
    right 170
    forward 100
    frag
    right 180
  ]

We can change the number of sides the star has by changing the number of repeats and fiddling with the values a bit:

TO star2
  repeat 5 [
    fd 100 rt 170
    fd 86 frag
    lt 88 fd 86
    rt 170 fd 100
    frag rt 180
  ]
END

First, let’s change our star procedures so they can take a :size parameter, like so:

TO star1 :size
  repeat 4 [
    fd 100 * :size
    rt 170
    fd 86 * :size
    frag
    lt 70
    fd 86 * :size
    rt 170
    fd 100 * :size
    frag
    rt 180
  ]
END

In this case, :size is a ratio, that is, 0.5 will make a star half the size, and 2 will make a star twice the size of the default.

You can change the color of the star using the SETFILLCOLOR primitive, or set a random fill color with RANDFC.

The following procedures create a sky full of stars:

TO star1 :size
  repeat 4 [
    fd 100 * :size
    rt 170
    fd 86 * :size
    frag
    lt 70
    fd 86 * :size
    rt 170
    fd 100 * :size
    frag
    rt 180
  ]
END

TO star2 :size
  repeat 5 [fd 100 * :size rt 170 fd 86 * :size frag lt 88 fd 86 * :size rt 170 fd 100 * :size frag rt 180]
END

TO star3 :size
  repeat 6 [fd 100 * :size rt 170 fd 86 * :size frag lt 100 fd 86 * :size rt 170 fd 100 * :size frag rt 180]
END

TO star4 :size
  repeat 7 [fd 100 * :size rt 170 fd 86 * :size frag lt 108.625 fd 86 * :size rt 170 fd 100 * :size frag rt 180]
END

TO star5 :size
  repeat 8 [fd 100 * :size rt 170 fd 86 * :size frag lt 115 fd 86 * :size rt 170 fd 100 * :size frag rt 180]
END

TO stars
  reset cam:setposition [0 0 0]
  cam:fixate [0 0 0]
  cam:setviewpoint [0 0 0]
  cam:newworker [forever [up 0.1 lt 0.1 rr 0.1 wait 1]]
  repeat 200 [
    pu home randori fd 400 + random 1000 up 90
    lt random 60 pd randpc randfc randfs randps
    make "size (10 + random 90) / 100
    run {pick [star1 star2 star3 star4 star5] :size}
  ]
  
END

Type STARS and press Enter to see the stars!

FRAG’s sister, SHARD creates a three-dimensional FRAG with depth beneath it. This depth is supplied as a parameter, in turtle-units, eg. SHARD 5. Try replacing FRAG with SHARD 5 in one of your star procedures and see what happens! (You’ll need to drag the camera around to see the sides of the star)

Myrtlebot: A turtleSpaces Twitter Bot

Don’t want to download the turtleSpaces client just yet? Well, you can take turtleSpaces for a bit of a spin using Myrtlebot, our turtleSpaces Twitter Bot! Myrtlebot executes whatever Logo code (not procedures yet) you tweet at her, and then returns an image of the results. In the future, we plan to allow for the recording of movies, but for now it’s just a still image. If your code runs for more than 60 seconds, it will be cut off at the 60 second mark.

Head on over to Twitter and check it out!

We’ve created a number of ‘short codes’ for various graphics primitives so that you can put more into a tweet. Here are the new ‘short codes’ (along with the typical logo rt, lt, fd, bk, up, dn etc.) See the documentation for a fuller description of each primitive:

AB = AMIGABALL
ABO = AMIGABALLOID
CAM = CURRENT CAMERA TURTLE
CB = CUBE
CBO = CUBOID
CC = CUTCONE
CCO = CUTCONOID
CCOS = CUTCONOIDSLICE
CCS = CUTCONESLICE
CF = CUTFUNNEL
CFO = CUTFUNNELOID
CFOS = CUTFUNNELOIDSLICE
CFS = CUTFUNNELSLICE
CIR = CIRCLE
CN = CONE
CNO = CONOID
COSL = CONOIDSLICE
CSL = CONESLICE
CSO = CUTSPHEROID
CSOS = CUTSPHEROIDSLICE
CSP = CUTSPHERE
CSS = CUTSPHERESLICE
CY = CYLINDER
CYA = CYLINDERARC
CYAS = CYLINDERARCSLICE
CYO = CYLINDROID
CYOA = CYLINDROIDARC
CYOAS = CYLINDROIDARCSLICE
CYOS = CYLINDROIDSLICE
CYS = CYLINDERSLICE
DCC = DUOCUTCONOID
DCFO = DUOCUTFUNNELOID
DCO = DUOCYLINDROID
DIVP = DIVISORP
DM = DOME
DMO = DOMOID
DOD = DODECAHEDRON
DODO = DODECAHEDROID
DTO = DUOTUBOID
ELL = ELLIPSE
FIX = FIXATE
FU = FUNNEL
FUO = FUNNELOID
FUOS = FUNNELOIDSLICE
FUS = FUNNELSLICE
HD = HEADING
ICD = ICOSPHEROID
IT = ITEM
NTWS = NOTWOSIDED
OCT = OCTAHEDRON
OCTO = OCTAHEDROID
PK = PICK
POPT = POPTURTLE
PRI = PRISM
PS = POLYSPOT
PT = PITCH
PUSHT = PUSHTURTLE
PY = PYRAMID
PYO = PYRAMOID
QD = QUAD
QUO = QUOTIENT
RBG = RANDBG
RBS = RANDBS
RCT = REPCOUNT
RD = RANDOM
RE = REMAINDER
RFC = RANDFC
RFS = RANDFS
RO = ROLL
RP = ROPE
RPA = REPABOVE
RPC = RANDPC
RPS = RANDPS
RPT = REPEAT
SBDS = SETBOUNDS
SBG = SETBG
SBS = SETBS
SFC = SETFC
SFS = SETFS
SHD = SETHEADING
SISI = SETICOSPHEREITERATIONS
SKF = SKEWFISO
SKI = SKEWISO
SKP = SKEWPYRAMID
SKPO = SKEWPYRAMOID
SKQ = SKEWQUAD
SKR = SKEWRECT
SKT = SKEWTRAPEZOID
SKTR = SKEWTRAPERECT
SKV = SKEWVOXELOID
SKVO = SKEWTRAPEVOXELOID
SMW = SETMARKERWIDTH
SP = SPOT
SPC = SETPC
SPD = SPHEROID
SPDS = SPHEROIDSLICE
SPH = SPHERE
SPHS = SPHERESLICE
SPI = SETPITCH
SPN = SETPOSITION
SPO = SETPOS
SPS = SETPS
SPW = SETPENWIDTH
SSA = SETSPHEROIDAXIS
SQ = SQUARE
SRO = SETROLL
SSPD = SETSPEED
STD = SETTYPEDEPTH
STDE = SETTYPEDELAY
STF = SETTYPEFONT
STFI = SETTYPEFILLED
STR = SETTYPESTRETCH
STS = SETTYPESIZE
TB = TUBE
TBA = TUBEARC
TBAS = TUBEARCLICE
TBO = TUBOID
TBOA = TUBOIDARC
TBOAS = TUBOIDARCSLICE
TBOS = TUBOIDSLICE
TBS = TUBESLICE
TH = TETRAHEDRON
THO = TETRAHEDROID
TR = TORUS
TRE = TRAPERECT
TRI = TRIANGLE
TRO = TOROID
TROS = TOROIDSLICE
TRS = TORUSSLICE
TVO = TRAPEVOXELOID
TWS = TWOSIDED
TZ = TRAPEZOID
VX = VOXEL
VXO = VOXELOID

Examples: Turtle Art

Logo is great for creating art! And 3D Logo makes it even better.

Many of these Logo artworks were inspired by examples created by Seymour Papert’s daughter Artemis Papert using the two-dimensional block-based TurtleArt application developed by LCSI Logo developer Brian Silverman. We’re grateful for both of their efforts in advancing Logo over the past several decades!

TO adobo
  reset setpenwidth 5 definecolor 16 [0 0 0]
  setpc 16 setbg 1 setbs 12 pu
  repeat 9 [
    repeat 10 [
      setpos {-120 + 20 * repcount -120 + 20 * repabove 1}
      setz -2 + 0.01 * random 400
      setfc pick [8 9 13]
      setfs -13 + random 10
      setheading -5 + random 11
      square 40 box 40
    ]
  ]
END

TO brush
  reset setbg 8
  setpenwidth 5 setbs 11
  
  repeat 300 [
    setpc pick [1 8 9 11]
    setps -10 + random 20
    pu
    setpos {-200 + random 400 -100 + random 200}
    rt random 360 pd
    repeat 20 + random 50 [
      make "stroke 30 + random 150
      fd :stroke rt -0.5 + 0.1 * random 10
      bk :stroke + random 2 lt -0.5 + 0.1 * random 10
    ]
    ra 0.01
  ]
END




TO bundles
  reset randbg
  repeat 40 [
    pu setpos {-200 + random 400 -100 + random 200}
    setpenwidth 4  pd
    lt random 360
    make "head heading
    repeat 2 [
      randpc
      repeat 90 [
        make "stroke 50 + random 5
        setps -10 + random 20
        fd :stroke
        bk 2 * :stroke
        fd :stroke
        rt 0.1 * random 20
        up -2 + (0.1 * random 40)]
      setheading :head
    ]
  ]
END



TO burst
  reset  pu
  make "colors {yellow orange red magenta blue}
  repeat 5 [
    setpc item repcount :colors
    setmarkerwidth repcount
    make "count repcount
    repeat 50 [
      rt random 360 mark :count * (20 + random 10)
      setpos [0 0]] lo repcount
  ]
END

TO caduceus
  reset  setpenwidth 5 pu
  repeat 4 [
    setpc item repcount [13 9 1 8]
    ra repcount * 2
    bk 120 pd
    repeat 100 [
      fd 1.4 + 0.1 * repabove 1
      rt 7.5 * sin 270 + 10 * repcount
    ]
    repeat 75 [
      fd 2.5 + 0.5 * repabove 1
      lt 0.5 * repcount
    ]
    pu home rr 180 * repcount
  ]
END

TO cards
  reset  pu
  repeat 1000 [
    setpos {-200 + random 400 -100 + random 200}
    rt random 360
    make "sizex 10 + random 20
    make "sizey 10 + random 20
    setfc 2 setfs 13
    quad :sizex + 2 :sizey + 2
    sr 1 fd 1 ra 0.01
    setfc pick [2 3 7 14]
    setfs -5 + random 10
    quad :sizex :sizey
    ra 0.01
  ]
END


TO citylights
  reset pu  setbg 2 setbs 10
  repeat 200 [
    setpos {-200 + random 400 -100 + random 200}
    rt random 360 make "size 20 + random 50
    setfc pick [1 9 13 7] lo 0.01
    until :size = 0 [
      randfs spot 0.05 * :size
      fd 5 lo 0.01 dec "size
    ]
  ]
END


TO dotcube
  cs seticosphereiterations 1 pu
  repeat 9 [
    repeat 9 [
      repeat 9 [
        setposition {-100 + 20 * repabove 2 -100 + 20 * repabove 1 -100 + 20 * repcount}
        setfc repabove 1 setfs repcount ico 2
      ]
    ]
  ]
END

TO dotspiral
  Reset setbg 13 setfc 9 cs
  repeat 1400 [
    pu fd repcount / 10
    spot repcount / 250
    rt 35
  ]
END


TO fireworks
  cs setpenwidth 5
  repeat 60 [
    pu home
    rt random 360
    randpc pd
    fd 30 + random 50
    repeat 50 [
      run pick [[pu][pd]]
      fd 2]
  ]
END

TO fish
  reset wrap
  setbounds [-180 -120 180 120]
  setbg 1 setbs 12
  pu setpos [-170 75]
  repeat 12 [
    setpenwidth 4 rt 44.5
    repeat 10 [
      sl 1
      repeat 3 [
        setpc item repcount [13 1 13]
        if repcount = 2 [ra 0.2]
        if repcount = 3 [lo 0.2]
        pd fd 50 pu bk 50 pu sr 1
      ]
      sl 2 rt 10
    ]
    setfc 1 ico 1 st
    lt 54.5 sr 13 fd 60
    lt 90
  ]
END

TO gaia
  reset setbg 2 setpenwidth 5  setpc 1
  repeat 5 [
    setpc item repcount [8 1 9 13 15]
    setorigin {0 0 2 * repcount}
    repeat 20 [
      pu home pd rt repcount * 18
      repeat 80 [
        fd 0.9 + ((repabove 2) * 0.1)
        rt 8 * sin (repcount * 8)
      ]
      lt 7.5
      repeat 5 [
        fd 20 pu bk 20 rt 15 pd
      ]
    ]
  ]
END


TO gradient
  cs pu
  repeat 30 [
    setfs -15 + repcount quad repcount 200 lo 0.01 sl 0.5
  ]
END

TO gridart
  reset
  randbg
  randfc
  pu
  
  setpos [-210 113]
  repeat 31 [
    for [i 1 57] [
      spot 3.5 * sin ((:i + repcount) * 2)
      sr 7.5
    ]
    sl 57 * 7.5
    bk 7.5
  ]
END

TO gridartico
  cs
  seticosphereiterations 1
  pu
  
  setpos [-210 113]
  repeat 31 [
    for [i 1 57] [
      ico 3.5 * sin ((:i + repcount) * 2)
      sr 7.5
    ]
    sl 57 * 7.5
    bk 7.5
  ]
END

TO gridartshade
  reset randbg randfc pu
  setpos [-210 113]
  repeat 31 [
    for [i 1 57] [
      setfs int (10 * (sin ((:i + repcount) * 2)))
      spot 3.5
      sr 7.5
    ]
    sl 57 * 7.5 bk 7.5
  ]
END

TO gridartwave :size
  cs
  seticosphereiterations 1
  pu
  
  setpos [-210 113]
  repeat 31 [
    for [i 1 57] [
      make "sin 3.5 * sin ((:i + repcount) * 2)
      ra :size * :sin
      ico :sin
      lo :size * :sin
      sr 7.5]
    sl 57 * 7.5
    bk 7.5
  ]
END

TO guessing
  Reset setspeed 5 setbg 9 setbs 3 setpenwidth 5
  repeat 8 [
    pu home ra repcount rt repcount * 45 pd
    repeat 22 [
      if repcount < 4 [
        setpc 8 setps 9 - (3 * repcount)] [
        setpc 13 setps (penshade - 1) + random 3
      ]
      repeat 360 [fd 1 - (0.015 * repabove 1) rt 1]
      pu sr 0.02 pd
    ]
  ]
END



TO heat
  cs pu
  repeat 10 [
    setfc pick [1 9 13]
    setpos {-170 + (repcount * 30) -120}
    up 90
    repeat 80 [
      cylinder 0.1 * repcount 1.6 10
      sr sin (repcount * 10)
      lo 1.5
    ]
    repeat 80 [
      cylinder 0.1 * (80 - repcount) 1.6 10
      sr sin ((80 + repcount) * 10)
      lo 1.5
    ]
    dn 90
  ]
END

TO heat2
  cs pu
  repeat 10 [
    setfc pick [1 9 13]
    setpos {-170 + (repcount * 30) -120}
    up 90
    repeat 80 [
      cylinder 0.1 * repcount 1.6 10
      sr sin (repcount * 10)
      lo 1.5
    ]
    repeat 80 [
      cylinder 0.1 * (80 - repcount) 1.6 10
      sr sin ((80 + repcount) * 10)
      lo 1.5
    ]
    dn 90 rr 180
  ]
END

TO hedgehog
  reset  setfc 1 pu ra 1 spot 20 setpc 13
  repeat 36 [
    home dropanchor tether pullout 18
    orbitleft repcount * 10 rt 225
    setmarkerwidth 0.1 * random 20
    mark 30 + random 80
  ]
END

TO funnybird :col1 :col2
  rl 90 setfc :col1 icospheroid 20 0.5
  pu rr 90 fd 5 sr 12 lt 30
  cylindroidarc 10 15 20 30 10 0.3
  fd 10 sr 15 ra 2
  setfc :col2 ico 3 lo 3.5 ico 3 ra 1.75
  bk 10 dn 90 lt 90
  domoid 10 8 20 0.3 fd 30 sl 7 rt 90
  icospheroid 8 2 fd 14
  icospheroid 8 2 bk 7 rr 30 rr 10 up 10
  cylinder 2 35 20 lo 40 rr 180 lt 90 dn 10
  domoid 7 10 10 0.5 oval 3.5 7 up 10 rt 90
  rl 180 ra 35 dn 20 rl 20
  cylinder 2 35 20 lo 40 rr 180 lt 90 up 10
  domoid 7 10 10 0.5 oval 3.5 7
END

TO hiding
  reset setpenwidth 5 pu
  sl 30 funnybird 1 9 home
  ra 20 rr 180 sl 30 up 10
  funnybird 13 8 rl 180 fd 20
  sr 20 spot 100 sl 40 up 90
  rl 90 sl 60 pd setpc 13
  pushturtle
  repeat 50 [
    popturtle
    pushturtle
    pu sr repcount * 2
    pd lt -10 + random 30
    setpc pick [8 9 13]
    setfs -10 + random 20
    raise -2 + random 6
    repeat 70 + random 30 [fd 1 rt 0.7]
  ]
  ht
END



TO icicles
  reset pu up 90
  repeat 20 [
    setpos {-210 + (20 * repcount) 115}
    make "size 20 + random 15 randfc
    repeat :size [
      cylinder 0.2 * (:size - repcount) 0.6 + (:size / 5) 20
      ra 0.5 + (:size / 5)
    ]
  ]
END

TO moon
  reset lt 120
  repeat 180 [fd 1 rt 1]
  rt 150
  repeat 180 [fd 0.774 lt 0.676]
  pu fd 30 pd
  repeat 3 [
    repeat 5 [
      fd 20 bk 40 fd 20 rt 36
    ]
    pu rt 180 sl 40 fd 40 pd
  ]
  ht
END

TO needles
  reset
  repeat 5 [
    repeat 10 [
      pu setpos {-240 + 80 * repabove 1 126 - repcount * 23}
      pushturtle rr 90 up -15 + random 30 randfc
      cylinder 1 30 10 rr 180 cylinder 1 30 10 popturtle
    ]
  ]
END


TO nest
  reset setspeed 1
  repeat 3 [
    setpc item repcount [9 8 4]
    repeat 18 [
      pu home raise 2 * repabove 1
      rt 7 * repabove 1 rt repcount * 20
      setps -5 up 10 pd
      mark 50 + 2 * repabove 1
      repeat 4 [
        setps penshade + 2
        repeat 90 [
          pu bk 0.1 pd mark 0.2 + 0.1 * repabove 1
          rt 2 up 1
        ]
        setpenshade penshade + 1
        repeat 90 [
          pu bk 0.1 pd
          mark 0.2 + 0.1 * repabove 1
          lt 2 up 1
        ]
      ]
    ]
  ]
  setfc 7 pu home ra 17 icospheroid 10 2 ht
END



TO notnot
  reset pu sl 10 lt 40 setpc 15 setbg 1
  setmarkerwidth 10 repeat 360 [
    mark 1.1 bk 0.1 lt 1
  ]
  lt 90 mark 115 home sr 90 fd 25 rt 40
  repeat 360 [
    mark 0.8 bk 0.1 lt 1
  ]
  lt 90 mark 80
END


TO orangepeel
  reset setpenwidth 8 setbs 3
  setbg 13 setpc 9 setps 0
  repeat 5 [
    pu home
    setpos {-240 + repcount * 50 113}
    rt 100
    repeat 10 [
      sr 1.5 fd 1 pd setps -5 + repcount
      repeat 100 [
        if divisorp 10 repcount [
          setps penshade + 1
        ]
        fd 2.75 rt repcount
      ] lt 10
    ]
  ]
END

TO patio
  cs pu setpos [-87.5 85] setfc 13
  repeat 5 [
    repeat 6 [
      setfillshade -10 + random 21
      make "offset random 10
      lt 50 - :offset
      polyspot 20 4
      rt 50 - :offset
      sr 35
    ]
    sl 210 bk 35
  ]
END

TO peaks
  reset  ht pu bk 125 setbg 2
  repeat 3 [
    setfs 0 setfc 13
    fiso 100 250
    setfc 7 setfs repcount * 3
    ra 0.1
    fiso 75 200
    sl 120 lo 50 bk 30
  ]
  home bk 125 sr 120 lo 50 bk 30
  repeat 2 [
    setfc 13 setfs 0 fiso 100 250
    setfs 3 + repcount * 3
    setfc 7 ra 0.1 fiso 75 200
    sr 120 lo 50 bk 30
  ]
  setpenwidth 5 home setpos [140 80]
  setpc 13
  repeat 36 [
    pd fd 35 pu bk 35 rt 10
  ]
END


TO pinwheel
  reset twosided
  pu setbg 2
  setfc 14 setpc 7
  repeat 2 [
    setpc item repcount [7 11]
    setfc item repcount [14 13]
    pu home rt repcount * 45
    bk 10
    repeat 100 [
      fd 40 + repcount * 1.2
      lt 90 fd 10 lt 92 fd 10
      frag lt 90
    ]
  ]
END

TO postits
  reset pu
  repeat 10 [
    repeat 10 [
      setpos {-120 + 20 * repcount -120 + 20 * repabove 1}
      randfc
      setheading -5 + random 11
      square 20 ra 0.1
    ]
  ]
END


TO rainbow
  reset setpenwidth 10  pu sl 225 bk 125 lt 10 pd
  repeat 500 [
    foreach "col {red orange yellow green blue lightblue magenta} [
      setpc :col fd 0.5 * (0.1 * (10 + repcount))
    ]
    pu bk (0.5 * (7 * (0.1 * (10 + repcount))))
    sr 1 fd 0.2 * (sin (0.5 * (repcount + 100)))
    pd
  ]
END

TO redonpink
  reset setpc 1 setbg 11 setbs -5
  pu setfc 1
  repeat 1000 [
    setpos {-220 + random 440 -120 + random 240}
    ra 0.001 setfo 50 setfs -15 + random 10
    spot 5 + random 5
  ]
  setorigin [-50 -30 2]
  repeat 16 [
    setmarkerwidth 3 home dropanchor
    pullout 10 orbitleft repcount * 22.5
    rt 180
    repeat 70 [
      setmarkerwidth markerwidth - 0.02
      bk 0.1 mark 1 rt 2
    ]
    repeat 70 [
      setmarkerwidth markerwidth - 0.02
      bk 0.1 mark 2 lt 2
    ]
  ]
END


TO roses
  reset setpenwidth 5
  repeat 3 [
    pu setpos {-190 + (repcount * repcount) * 30 -90 + repcount * 50}
    pd make "size 0.05 * (repcount / 2)
    setpc item repcount [1 9 13]
    repeat 9 [
      repeat 100 [
        fd repcount *  :size rt 10
      ]
    ]
  ]
END


TO slats
  reset
  setorigin [-260 -120]
  repeat 23 [
    pu setps -7 + repcount
    home sr 20 * repcount
    setmarkerwidth 18 rt 10
    mark 250
  ]
END


TO slats2
  reset
  setorigin [-260 -120]
  repeat 23 [
    pu setps -7 + repcount
    home setpc pick [8 9 13]
    sr 20 * repcount
    setmarkerwidth 18 rt 10
    mark 250
  ]
END

TO snake
  CS PU RANDFC
  BK 110 UP 90
  repeat 80 [
    cylinder 0.1 * repcount 1.6 10
    sr sin (repcount * 10)
    lo 1.5
  ]
  repeat 60 [
    cylinder 0.1 * (80 - repcount) 1.6 10
    sr sin ((80 + repcount) * 10)
    lo 1.5
  ]
  DN 90 RT 60 ICOSPHEROID 5 2 SL 7 FD 3
  RANDFC ICO 1 BK 6 ICO 1 HT
END

TO snakes
  CS PU RANDFC
  repeat 10 [
    home setpos {-170 + (repcount * 30) -110}
    up 90 repeat 80 [
      cylinder 0.1 * repcount 1.6 10 sr sin (repcount * 10) lo 1.5
    ]
    repeat 60 [
      cylinder 0.1 * (80 - repcount) 1.6 10
      sr sin ((80 + repcount) * 10) lo 1.5
    ]
    DN 90 RT 60 ICOSPHEROID 5 2 SL 7 FD 3
    RANDFC ICO 1 BK 6 ICO 1
  ]
  HT
END

TO solar
  reset  repeat 5 [
    setpc item repcount [1 8 9 13 15]
    repeat 18 [
      pu home raise 2 * repabove 1
      rt repabove 1 rt repcount * 20
      setps 0 pd mark 50
      repeat 5 [
        setps penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1 rt 2
        ]
        setpenshade penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1 lt 2
        ]
      ]
    ]
  ]
END

TO solar2
  reset setspeed 5
  repeat 5 [
    setpc item repcount [1 8 9 13 15]
    repeat 18 [
      pu home raise 2 * repabove 1
      rt 3 * repabove 1
      rt repcount * 20
      setps 0 pd
      mark 50
      repeat 5 [
        setps penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1 rt 2
        ]
        setpenshade penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1 lt 2
        ]
      ]
    ]
  ]
END


TO solar3
  reset           setspeed 5
  repeat 5 [
    setpc item repcount [1 8 9 13 15]
    repeat 18 [
      pu home raise 2 * repabove 1
      rt 4 * repabove 1
      rt repcount * 20 setps 0
      pd mark 50
      repeat 5 [
        setps penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1
          rt 2
        ]
        setpenshade penshade + 1
        repeat 90 [
          mark 0.2 + 0.1 * repabove 1 lt 2
        ]
      ]
    ]
  ]
END

TO sparks
  reset setpenwidth random 10
  randbs randbg
  repeat 5 [
    pu setposition {-200 + random 400 -100 + random 200 -100 + random 100}
    setorigin position randpc randps
    repeat 30 [
      rt random 360 pd
      while (and xpos < 300 xpos > -300 ypos < 180 ypos > -180) [fd 1 lt 0.2]
      pu home
    ]
  ]
END

TO spiralart
  reset
  snappy:pullin 100
  pu
  randbg
  sl 40
  rr 90
  bk 10
  repeat 2 [
    randfc
    repeat 2800 [
      tube 2 repcount / 400 6
      lo repcount / 500
      up 2
    ]
    pu home
    fd 10 sr 40 rl 90
  ]
  ht
END


TO spirallights
  reset  setpenwidth 5
  repeat 5 [
    make "ypos 130 - (40 * repcount)
    randpc
    repeat 10 [
      pu setpos {-220 + (40 * repcount) :ypos}
      pd
      repeat 100 [
        setps -10 + int (repcount / 5)
        fd repcount / 10 rt 30
      ]
    ]
  ]
END

TO spirallightsorange
  reset  setbg 8
  setbs 13 setpenwidth 5
  repeat 5 [
    make "ypos 130 - (40 * repcount)
    repeat 10 [
      pu setpc pick [1 8 9 13]
      setpos {-220 + (40 * repcount) :ypos}
      pd
      repeat 100 [
        setps -10 + int (repcount / 5)
        fd repcount / 10
        rt 30
      ]
    ]
  ]
END


TO spirallightsorange3d
  reset  setbg 8 setbs 13
  setpenwidth 5
  repeat 5 [
    make "ypos 130 - (40 * repcount)
    repeat 10 [
      pu setpc pick [1 8 9 13]
      setposition {-220 + (40 * repcount) :ypos 0}
      pd
      repeat 100 [
        setps -10 + int (repcount / 5)
        fd repcount / 10 rt 30 dn 2
      ]
    ]
  ]
END

TO splash
  reset setspeed 5 setpenwidth 5
  repeat 144 [
    pu home rt repcount * 2.5 dn 75 pd
    repeat 400 [
      up 5 * (sin (repcount * 4))
      setps -15 + (repcount / 12)
      fd .4
    ]
  ]
  pu home
END

TO splash2
  reset setspeed 5 setpenwidth 5
  repeat 144 [
    pu home rt repcount * 2.5 dn 75 pd
    repeat 400 [
      up 5 * (sin (repcount * 4))
      rt sin repcount
      setps -15 + (repcount / 12)
      fd .4
    ]
  ]
  pu home
END

TO splash3
  reset setspeed 5 setpenwidth 5
  repeat 144 [
    pu home rt repcount * 2.5 dn 75 pd
    repeat 400 [
      up 5 * (sin (repcount * 4))
      rt sin repcount
      rr sin repcount
      setps -15 + (repcount / 12)
      fd .4]
  ]
  pu home
END

TO splash4
  reset setspeed 5 setpenwidth 5 setpc 1
  repeat 144 [
    pu home rt repcount * 2.5 dn 75 pd
    repeat 400 [
      up 5 * (sin (repcount * 4))
      rt sin repcount
      rr sin repcount * 2
      setps -15 + (repcount / 12)
      fd .4]
  ]
  pu home
END

TO spotwave
  cs  pu
  repeat 32 [
    setpos {-220 205 - (repcount * 10)}
    repeat 45 [
      setfc pick [2 3 5 6 7 10 14 15]
      spot 5 sr 10
      fd -2 + sin (repcount * 10)
    ]
  ]
END


TO stickman :size
  lt 45 setmarkerwidth :size / 3
  repeat 2 [
    lt 90 mark :size * 5 spot :size
    bk :size * 5] lt 135 mark :size * 2
  lt 45 repeat 2 [
    mark :size * 5 spot :size bk :size * 5 rt 90
  ]
  lt 135 mark :size * 5
  spot :size * 2 pu bk :size * 10
END

TO stickmen
  reset pu sl 80 fd 20
  repeat 3 [
    randfc setpc fillcolor
    stickman repcount * 3
    pu sr 80 ra 20
  ]
END

TO stormy
  reset  pu setpos [-230 -120]
  repeat 8 [
    setpc item repcount [2 6 7 14 1 8 9 13]
    repeat 160 [
      pushturtle rt 45 bk random 20
      randps mark 50 + random 20
      popturtle pu
      sr 2.5 + random 5 ra 0.01
    ]
    ra 0.1 pu
    setpos {-230 -120 + repcount * 30}
  ]
END

TO swirl
  reset pu bk 20 sr 40 setpc 1
  setbg 13 setbs -10 setorigin position pd
  repeat 12 [
    repeat 30 [
      setropewidth 6.1 - (repcount / 5)
      rope 4 bk 0.2 rt 6 bk 0.2
    ]
    pu home rt repcount * 30 pd
  ]
  ht
END


TO thicket3
  reset  setbg 2 setpenwidth 5  setpc 1
  repeat 8 [
    setpc item repcount [8 1 9 4 12 13 14 15]
    repeat 20 [
      pu setposition {-200 + (repcount * 20) 0 5 + repabove 1}
      up 25 pd
      repeat 80 [
        fd 0.9 + ((repabove 2) * 0.1)
        rt 8 * sin (repcount * 8)
      ]
      lt 7.5
      repeat 5 [
        fd 20 pu bk 20 rt 15 pd
      ]
    ]
  ]
END

TO twomoons
  reset
  setorigin [-23 -23 0]
  repeat 2 [
    setpc item repcount [7 2]
    repeat 36 [
      pu home dropanchor
      pullout 10 * repabove 1
      orbitleft repcount * 10
      rt 180 pd
      if oddp repcount [fd 40 * repabove 1] [fd 20 * repabove 1]
    ]
    setorigin [30 30 -20]
  ]
END


TO vase
  reset setpenwidth 5  pu fd 50 sl 50 pd rt 135
  repeat 40 [fd 1 rt 2]
  repeat 125 [fd 1 lt 1]
  lt 5 quad 80 220 rt 180 rr 180
  quad 80 220 rr 180 rt 185
  repeat 125 [fd 1 lt 1]
  repeat 40 [fd 1 rt 2]
  pu home bk 70 lt 15 setpc 15
  repeat 6 [
    pushturtle pd fd 150 + random 20
    setfc pick [1 8 9 11] ra 0.1
    spot 5 popturtle rt 6
  ]
END


TO weave
  reset  wrap
  repeat 30000 [
    fd 1 rt
    sin repcount
    if divisorp 1000 repcount [randpc]
  ]
END

TO zig
  reset  setbg pick [1 8 9 13]
  setbs 10 setpenwidth 10
  repeat 200 [
    ra 0.1 pu
    setpos {-220 + random 440 -120 + random 240}
    rt random 360 setpc pick [1 8 9 13]
    setps -15 + random 10
    repeat 50 [
      pd fd 50 + random 3 lt random 3 bk 50 + random 3
      rt random 3 setps penshade + (-1 + random 3)
    ]
  ]
  pu setpos [-100 -50]
  setheading 300 pd setpc 1
  setps 5 ra 1
  repeat 15 [
    bk 0.2 fd 20 rt 140
    bk 0.2 fd 20 lt 132.5
  ]
END


TO zigzag
  reset setbg 8 setbs 0 setpenwidth 5
  
  repeat 360 [
    pu home pd rt repcount * 1 lt 45
    setpc pick [1 5 7 9]
    repeat 20 [
      fd repcount rt 90
      fd repcount lt 90
    ]
  ]
END

TO zigzag3d
  reset setbg 8 setbs 0 setpenwidth 5
  repeat 360 [
    pu home pd rt repcount * 1
    lt 45 up 45 setpc pick [1 5 7 9]
    repeat 20 [
      fd repcount rt 90 fd repcount lt 90
    ]
  ]
END

Introducing turtleSpaces, a Logo environment for everyone

After a year of work, we are pleased to make available to the public our new Logo interpreter, turtleSpaces.

turtleSpaces implements the Logo programming language inside of the OpenGL 3D graphics environment, allowing you to use turtles to construct 3D models and create animations, simulations and games.

We initially implemented a version of Apple Logo II in our microM8 Apple II emulator to make it easier to use for our younger users, and because microM8 itself uses OpenGL to render the Apple II’s graphics (also in 3D) we added some rudimentary 3D commands to it (up, down, rollleft, rollright, etc.)

We received positive feedback from our users and decided to seperate the Logo interpreter from microM8 and work to grow its potential more broadly.

Why Logo?

Logo’s benefits for learning how to code are many and varied, but before we explore them, it might be better to address the concerns many have with it first, and get them out of the way.

Why not Logo?

Logo’s problems are mainly historical. In the late 1970s there was great excitement among some quarters regarding the potential benefits of teaching children computer programming. Logo was designed around this time to be an as easily-accessible programming language as could be achieved given the technology of the era, which was not very advanced and led to extreme tradeoffs between usability and practicality. In short, Logo was still difficult to use, while not being useful for very much.

Despite this, evangelists (including and mainly Logo’s co-architect Seymour Papert) still made a big deal of Logo, and attempted (and for a time largely succeeded) to gain its widespread use in education, engaging in a great deal of publicity and the making of overstated promises about its potential to turn every child who used it into a little genius. To make matters worse, Papert’s constructivist philosophy dictated that children be allowed to ‘explore’ Logo at their own whim, which while valid on its face (and is something we encourage with turtleSpaces!) was impractical in the context of the crude interpreters that were made available at the time, which were slow, unforgiving, low-resolution and had limited built-in tools, resources and examples.

To make any use of it, you really had to read the manual, but since reading the manual ran counter to Papert’s philosophy, many kids just figured out how to move the turtle forward and turn, and that was about it. Studies were done that showed some initial tutorial-based hand-holding was required, and improvements needed to be made in the user interface, and while later versions of Logo did attempt to correct these shortcomings, by that time the initial enthusiasm for Logo amongst educators had died out, and they were somewhat jaded to the concept in general.

Logo appeared too early, and pushed too hard.

In response, ‘blocks’-based versions of Logo appeared such as Scratch, which attempted to make a subset of commands and their parameter requirements obvious to young coders, who could ‘snap’ them together in the correct order and change default parameters and witness their effects. While useful perhaps in the near-term (and to be clear, we are in the process of implementing a blocks-style mode in turtleSpaces, so we agree they are not without value as an early introduction to Logo), these reduced the very valuable concept of the Turtle to little more than a gimmick, and they nobbled the potential of the Logo language they were based on so much as to make greater exploration of the language largely impossible in the context of their environments.

They were, and are, ‘dead ends‘, and this causes some concern to parents and educators, many of whom are reluctant to use them.

That isn’t to say there weren’t and aren’t other full-Logo interpreters also out there. There were and are, but they have also faced difficulty. First, they’ve tended to rely heavily on the host operating system’s user interface, which was a popular direction to go in the late 1990s and early 2000s, but not so much recently. MicroWorlds Logo, for example, allowed the user to create buttons and load images, and so forth. But the programs users created were very much restricted to their environments, and those environments were still very much oriented towards children. And this trend continued, making for very limited experiences.

As a result, given today’s cross-platform, limited UI landscape these have not aged well. Other developers implemented variants of Logo (such as NetLogo) designed for simulations, such as ant colonies or viruses. But these are largely targeted toward college students, and many of their mechanisms are in our opinion convoluted and counter-intuitive and not at all suitable for children.

Logo for children was simply a victim of being in the wrong place at the wrong time, and its stunted evolution reflected that. But there’s nothing wrong and a lot very right with Logo, and studies of the language itself have shown that. Its functional nature is fairly easy to grasp, the immediate feedback you get from the turtle promotes exploration and learning, and it is powerful enough to become a go-to language for someone even as an adult, if there was a Logo environment that was flexible enough to be useful for a broader audience, and could occupy that middle ground.

So we thought, “Why couldn’t we find that middle ground? We have the technology! Why don’t we go back to where it all started and work forward from there?”

I’d like to definitively state that we have succeeded in our task, but I’ll more tentatively say that we’re well into working on it. Here’s what we have accomplished so far:

  • A full re-implementation of the LCSI Apple Logo II primitive (command) set
  • The addition of a few hundred useful primitives from other Logos and programming languages (including some of our own invention), such as multiple turtles and threading, loops and math functions, retro-styled music and sound, turtle models, gamepad and image placement primitives
  • Several hundred primitives related to the 3D environment, including a wide variety of shapes, positional and vector based functions and tools, and Minecraft-inspired pixelated color schemes
  • An improved while retro-inspired interface that is able to easily get out of the way – no tiny window into the Logo world, no large obtrusive text areas that you can’t get rid of. You can make a program and execute it full screen just like any other game you might buy or download.
  • Written in Go so it’s cross-platform (Windows, macOS and Linux) with the potential for mobile platforms
  • Lots and lots of other smaller things that make the whole experience fun, engaging, productive and straightforward that I can’t even begin to mention.

Here’s a list of some big-ticket items that still need to happen (in no particular order):

  • Raspberry Pi, iOS and Android support, in particular for running turtleSpace apps
  • Stand-alone, distributable app generation for mobile and desktop
  • Private “Classes” that groups of students can join, whose progress can be viewed and monitored by their facilitators / teachers, who can share content between their students
  • Multi-user, multi-player capabilities so that users can design, code and play together
  • API to support control by other applications such as IDEs and tutorials
  • The ability to connect and interact with the outside world
  • 3D model import / export so that users can 3D print their creations, or use them in other 3D environments or vice-versa
  • Terrain and more complex shape design tools
  • In-environment tutorials, badges, achievements and so forth to encourage exploration
  • ‘Blocks’-based entry mode to support younger learners
  • Command-line version without the OpenGL baggage, but with internal FauxGL rendering support to enable image output
  • Refactoring and optimization!
  • A whole bunch more primitives!
  • Finally, and most importantly, to make Logo the relevant all-purpose language it was always meant to be

So, a long way to go. But a good start. Hope to see you around turtleSpaces soon!