Category Logo Programming Examples

Activity Idea: Gone Fishin’

Build this cool island scene using turtleSpaces Logo and a few basic shapes!

First we begin with a tree. We can build a trunk using a repeat loop of cylinders that get narrower in diameter and longer as we go. We can introduce a slight curve as well by tilting the turtle up a bit each cylinder we create:

Next we need to create the leaves, which we can do using the fiso (filled triangle) primitive. We can use a repeat loop to create a series of triangles growing in size, and then a second repeat loop to create a further series of triangles shrinking in size. In both cases, we tilt the turtle down a bit each triangle we create:

Then we can use a further wrapping repeat loop to create 8 of them around the trunk:

Let’s add six coconuts to the top of the tree using another repeat loop and icospheroids:

But one tree is kind of lonely, so let’s create a ring of eight around the edge of the island using the orbitleft primitive. We can also make the island more mound-like using a domoid:

Let’s add a dock Myrtle can fish off of made out of cylinders and voxeloids:

And a hut to shelter in made out of made out of a cutsphereslice and a sphereslice:

Myrtle’s all set, let’s get her fishing! The fishing rod is created using a thick line and a thin line:

The sunset effect is created using an inverted gradient tube:

Good job, Myrtle! You can check out the island yourself at https://turtlespaces.org/weblogo/?pub=64

Just click the flag to create the island. Don’t forget that you can click and drag on the viewport to move the camera, use the scroll wheel to zoom in and out, rotate using the right button and drag, and click both buttons and drag to pan.

Check out the code to see how it’s done! Then try something similar yourself. What will your island look like? Share it so we can see for ourselves!

turtleSpaces Halloween!

Witches, pumpkins, devils, ghosts and black cats are all haunting turtleSpaces this Hallowe’en! turtleSpaces is great for creating 3D models using code.

You can also find these models in the Published projects section of the webLogo interpreter.

The Pumpkin:

The pumpkin is created using the spheroidslice primitive, which creates an elongated slice of a sphere. By creating ten of these slices (every 36 degrees) we can easily create the pumpkin’s body:

TO pumpkin
  pu
  ;penup
  
  rr 180 up 90 lt 18 ra 20
  ;these were added after in order to position
  ;the pumpkin to face the camera
  
  pushturtle
  ;stores the turtle's state on to a stack,
  ;to be restored later using popturtle
  
  setspheroidaxis false
  setfillcolor orange
  setpencolor brown
  
  gradient
  repeat 10 [
    setfillshade 0
    spheroidslice 50 10 40 18 23 1.5
    rt 36
  ]

Next, we’ll position the turtle and use the domoid primitive to create the eyes, nose and mouth:

  left 2
  raise 37
  down 42
  right 30
  lo 85
  setfillcolor 13
  setpencolor 8
  setspheroidaxis true domoid 15 10 3 0.5
  ;eye
  
  ra 85
  lt 30
  UP 42
  RT 40
  DN 42
  RT 30
  lo 85
  domoid 15 10 3 0.5
  ;other eye
  
  ra 85
  LT 30 UP 42 LT 20 DN 50 RT 30 lo 85
  domoid 10 10 3 0.5
  ;nose
  
  ra 85
  LT 30 lo 3 DN 48 FD 40 lo 60 
  domoid 17 10 10 0.5
  ;mouth

Finally, we’ll create the pumpkins stem:

  popturtle
  ;return the turtle to its position, orientation
  ;and so forth when we did pushturtle
  nogradient
  setfc 8
  ;setfillcolor
  cylinder 8 60 10 lo 60
  up 90 rt 180 sr 20 lt 72.5
  ;sr = slideright
  cylinderarc 8 20 10 10 3
  home ht
END

The Witch:

The witch procedure first stores the pumpkin as a turtle model, then begins with her signature feature: her hat!

First we position the camera and store the pumpkin as a turtle model:

TO witch
  reset cam:pullout 150 cam:orbitleft 20 cam:orbitup 10
  begintag "pumpkin
  pumpkin
  endtag
  newmodel "pumpkin "pumpkin
  cs

Then we create her hat:

  pu setfs 0
  dn 90 ra 80 
  setoriginvectors vectors
  pushturtle
  setfc 5
  rr 180
  cone 40 100 20
  cylinder 80 5 20

Next is her head:

  rr 180
  setfc 1
  up 5
  cylinderslice 40 50 20 10
  lo 50
  cutsphereslice 40 20 20 10 20 10
  up 90
  spot 40
  dn 90
  ra 50
  dn 5
  lo 30
  setfc 11
  rl 90
  icospheroid 35 1.5
  rr 90
  ra 20
  lt 30
  bk 30
  setfc 2
  ico 7
  fd 30
  rt 60
  bk 30
  ico 7
  fd 30
  lt 30
  lo 30
  up 90
  rr 180
  up 10
  setfc 11
  cutcone 15 7 60 20
  lo 60
  setfc 1
  dome 7 20 20
  popturtle

Then her body:

  lo 100
  setfc 2
  rr 180
  cappeddomoid 30 20 20 2
  rr 180
  setfc 9
  cylindroid 30 50 20 1.2
  pushturtle
  lo 50
  domoid 30 50 20 1.2
  repeat 2 [
    pushturtle
    sr 25
    lo 10
    dn 30
    rr 180 dome 20 20 20
    rr 180
    cylinder 20 50 20
    lo 50
    ico 20
    up 90
    cutcone 20 15 50 20
    lo 50
    setfc 2
    cylinder 20 20 20
    lo 20
    dn 90
    cylindroid 10 20 20 2
    lo 20 domoid 10 20 20 2
    ra 20 rr 180
    cylindroid 10 10 20 2
    lo 10
    domoid 10 20 20 2
    popturtle sl 50]
  popturtle
  pushturtle
  sr 45
  dn 20
  ra 4
  setfc 11
  cylinder 13 50 20
  lo 50
  ico 13
  dn 90
  cutcone 13 8 50 20
  lo 50
  ico 12
  setmodelscale 0.5
  dn 90 rt 160 lo 25 stamp "pumpkin
  setmodelscale 1
  popturtle
  pushturtle
  sl 45
  dn 20
  ra 4
  setfc 11
  rl 9
  cylinder 13 50 20
  lo 50
  ico 13
  dn 20
  rl 40
  cutcone 13 8 50 20
  lo 50
  ico 12

And finally her broom!

setvectors originvectors 
  setx 0 
  pushturtle 
  up 80 
  setfc 8 
  cylinder 5 100 20 
  lo 100 
  lo 5 
  rr 180 
  cappeddomoid 13 20 20 3.5 
  rr 180 
  cylindroid 13 15 20 3.5 
  lo 15 
  sl 35 
  setfc 13 
  repeat 15 [
    rl -8 + repcount 
    cylinder 0.1 * (10 + random 20) 70 20 
    rr -8 + repcount 
    sr 5 
    randfs 
  ]
  sl 72.5 
  fd 5 
  repeat 14 [
    randfs 
    rl -7 + repcount 
    cylinder 0.1 * (10 + random 20) 70 20 
    rr -7 + repcount 
    sr 5 
  ]
  bk 10 
  sl 5 
  repeat 14 [
    randfs 
    rr -7 + repcount 
    cylinder 0.1 * (10 + random 20) 70 20 
    rl -7 + repcount 
    sl 5 
  ]
  popturtle 
  up 90 
  rr 180 
  up 10 
  setfc 8 
  cylinder 5 100 20 
  lo 100 
  dome 5 20 20 
  dn 90 
  ra 5 
  bk 10 
  st 
END

The Devil:

This unicycling devil robot is a scary creature!

His head is made out of a padded voxel:

TO devil
  cs
  pu
  setfc 11
  cylinder 10 50 20
  bk 10
  voxeloid 50 20 50
  fd 10
  sr 50
  cylinder 10 50 20
  sl 60
  voxeloid 70 50 50
  fd 50
  sr 10
  cylinder 10 50 20
  bk 10
  voxeloid 50 20 50
  fd 10
  sr 50
  cylinder 10 50 20
  rr 180
  repeat 2 [
    repeat 4 [
      dome 10 5 20
      rl 90
      cylinder 10 50 20
      rr 90
      sr 50
      rt 90
    ]
    up 180
    ra 10
    voxeloid 50 50 10
    lo 60
    lt 90
  ]
  lo 10
  bk 15
  sr 15
  lo 0.1
  setfc 1
  polyoval 10 5 100
  bk 20
  polyoval 10 5 100
  sl 10
  rr 180
  fd 5
  rt 60
  quad 15 3
  rt 30
  sl 10
  rt 30
  quad -15 3
  lt 30
  sr 5
  bk 40
  pushturtle
  repeat 8 [
    quad 4 3
    sr 4
    lt 10
  ]
  popturtle
  pushturtle
  repeat 8 [
    quad -4 3
    sl 4
    rt 10
  ]
  popturtle
  fd 50
  lo 20
  sl 20
  up 90
  pushturtle
  repeat 2 [
    repeat 15 [
      cylinder 16 - repcount 10 10
      lo 8
      up 10
      rr 30 - 20 * repabove 1
    ]
    popturtle
    sr 40
    pushturtle
  ]

And his body:

  sl 60
  ra 60
  bk 15
  rr 180
  rt 20
  setfc 13
  cylinder 30 10 20
  setfc 1
  lo 10
  cylindroid 30 20 20 2
  sl 40
  setfc 1
  cylindroid 20 100 20 1
  pusht
  lo 100
  ico 20
  up 120
  rr 30
  cutcone 20 10 100 20
  lo 100
  up 90
  rl 15
  ra 20
  setfc 10
  cylinder 20 40 20
  popt
  pusht
  sr 80
  cylinder 20 100 20
  lo 100
  ico 20
  up 60
  rl 20
  cutcone 20 10 100 20
  lo 100
  setfc 5
  up 90
  rl 10
  ra 20
  cylinder 20 40 20
  popt
  sr 40
  cylinder 40 100 20
  lo 160
  rr 90
  ra 20
  setfc 11
  cylinder 60 40 20
  setfc 13
  ra 5
  cylinder 10 50 20
  lt 90
  lo 2.5
  up 90
  cylinder 2.5 100 10
  dn 90
  lo 45
  up 90
  cylinder 2.5 100 10
END

The Ghosts:

These bug-eyed PacMan-style ghosts are a real scream!

The Ghost:

TO ghost
  up 90 
  make "gc 1 + random 14 
  make "ec pick [0 1 2 3 4 8 9] 
  setfc :gc 
  dome 50 20 18 
  rr 180 
  cylinder 50 50 18 
  lo 50 
  dn 90 
  repeat 18 [
    tent 9 30 49.2 
    rl 20 
  ]
  setfc 15 
  up 90 
  ra 50 
  fd 50 
  rr 90 
  icospheroid 15 1.5 
  fd 15 
  setfc :ec 
  icospheroid 7.5 1.5 
  rl 90 
  bk 65 
  rt 40 
  fd 50 
  setfc 15 
  rr 90 
  icospheroid 15 1.5 
  fd 15 
  setfc :ec 
  icospheroid 7.5 1.5 
  rl 90 
END

The Ghost Circle (this code assumes the above code is in a procedure called ghost:)

TO ghostcircle
  reset 
  snappy:newworker [
    setposition [0 0 0] 
    dropanchor 
    forever [
      orbitleft 0.5 
      wait 1 
    ]
  ]
  randbg 
  setbs 13 
  setfs 0 
  dropanchor 
  up 90 
  pu 
  ra 80 
  gradient 
  randpc 
  setfc bg 
  spot 400 
  lo 80 
  nogradient 
  pullout 300 
  repeat 18 [
    pushturtle 
    dn 90 
    rr 20 
    ghost 
    popturtle 
    orbitright 20 
  ]
END

 

The Cats:

Don’t get in the way of these cats, or it’s bad luck!

The Cat’s Eye:

TO cateye
  setfc 13 
  pu 
  rt 90 
  spot 10.1 
  fd 4 
  fiso 9.3 20 
  bk 8 
  rt 180 
  fiso 9.3 20 
  bk 4 
  ra 0.1 
  setfc 0 
  polyspot 6 40 
  lt 90 
  fd 3 
  fiso 5.2 7 
  bk 6 
  rt 180 
  fiso 5.2 7 
  bk 3 
END

The Cat:

TO cat
  sl 30 
  setfs 0 
  cateye 
  sr 60 
  cateye 
  sl 30 
  lo 0.3 
  bk 10 
  setfc 5 
  setfs 12 
  cylindroid 40 10 50 2 
  sl 20 
  lt 30 
  fd 25 
  tent 30 70 10 
  bk 25 
  rt 30 
  sr 40 
  rt 30 
  fd 25 
  tent 30 70 10 
  bk 25 
  lt 30 
  sl 20 
  bk 5 
  up 180 
  setfs 0 
  setfc 11 
  dome 5 20 20 
  fd 15 
  setfc 5 
  setfs 5 
  dome 8 20 20 
  lo 3 
  dropanchor 
  tether 
  pullout 7 
  rt 180 
  orbitright 70 
  setpw 2 
  repeat 2 [
    repeat 5 [
      line 80 
      orbitright 10 
    ]
    orbitright 130 
  ]
  pullin 7 
  rr 180 
  lo 12 
  rt 20 
  setfs 12 
  sr 20 
  setfs 13 
  cylindroid 40 100 50 2 
  lo 90 
  rr 90 
  setfs 12 
  cylinder 10 200 20 
  lo 200 
  dome 10 20 20 
  ra 200 
  rr 180 
  up 20 
  cylinder 10 130 20 
  lo 130 
  dome 10 20 20 
  ra 130 
  dn 40 
  cylinder 10 130 20 
  lo 130 
  dome 10 20 20 
  ra 130 
  sr 80 
  cylinder 10 130 20 
  lo 130 
  dome 10 20 20 
  ra 130 
  up 40 
  cylinder 10 130 20 
  lo 130 
  dome 10 20 20 
  ra 130 
END

The Cat Ring:

TO catring
  cs 
  pu 
  dn 90 
  dropanchor 
  pullout 250 
  pu 
  repeat 8 [
    pushturtle 
    up 90 
    cat 
    popturtle 
    orbitright 45 
  ]
END

Happy Hallowe’en!

Creating Animated Videos Using turtleSpaces

The above animation was created inside turtleSpaces, and then exported as a webm video file using the savewebm primitive. I then used Final Cut Pro to add the audio and the titles.

The Logo code used to create the animation is fairly straightforward. The turtle moves in an arc outwards from the center, creating arcs around it and generating the pattern. The location and sizes of the arcs depends on the current loop iterations. Graphics rendering is suspended while each ‘frame’ is created, after which the code renders the frame and pauses to ensure the user sees it, then continues on.

It runs much faster via the desktop application than in the web version, so you may need to adjust it to slow it down. Tinker with it! Play around. It’s the best way to learn.

TO arcflower3d
  cs
  ;clearscreen

  ;maximize
  ;the above line forces full-screen display. This is needed
  ;with savewebm, because the video capture routine uses the
  ;webgl canvas, and so whatever resolution it displays at
  ;is the resolution the video file will be. Uncomment it if
  ;saving video

  ht
  ;hideturtle

  setpw 2
  ;setpenwidth

  ;savewebm 260
  ;uncomment the above line to record video

  repeat 3600 [
    norender
    ;suspend rendering graphical output

    cs
    
    repeat 128 [
      setpc 1 + remainder repcount 15
      ;set the pencolor to the remainder of a modulus
      ;of the current loop count (repcount)

      pu
      ;penup

      rarc 0.1 * repabove 1 repcount
      ;move in an arc trajectory to the right
      ;based on the current repcount and the 
      ;repcount of the loop above (repabove)

      rr 0.1
      ;rollright

      dn 0.1
      ;down

      arc 90 repcount
      ;create a 90 degree arc based on the current
      ;repcount
    ]

    render
    ;resume rendering

    nextframe
    ;wait until one frame is rendered
  ]

END

 

Example: Towers of Hanoi

This rendition of Towers of Hanoi is a simple game to code and makes introductory use of 3D shapes. Recreating it could serve as a great introduction to turtleSpaces and Logo coding in general.

In the game, you attempt to transfer the disks on post 1 to post 3 ending in the same order the disks started in (smallest to largest, starting from the top). To do this, you can transfer disks between posts, but disks can only be placed on top of disks smaller than they are.

This example uses lists to store the disks on each post, and the queue and dequeue primitives to ‘transfer’ the disks from one ‘post’ (list) to another.

The graphical representation of the game is optional and can be added after first creating the game logic, using text output. A potential project would be to first create the game logic, using the game rules (and for the instructor, this example) as a guide. In its most basic form, it can be created using conditionals (if), lists (make, queue, dequeue). loops (label, go), and input/output (print, question) primitives.

Then, add the graphical representation, once the game is working correctly. The example here uses voxels, cylinders and typeset text. Finally, add some camera movements.

The example follows, and is commented:

SETABOUT |Towers of Hanoi (run myrtle:hanoi)|

CREATORS [melody]

NEWTURTLE "myrtle

TO hanoi
  ;This is a simple recreation of the classic
  ;Towers of Hanoi game. It has fairly basic logic
  ;and would nake a great introduction to game creation
  ;using turtleSpaces

  ;as a project, we would create the text-game
  ;first and then add the graphical representation
  ;later (as a 'stretch goal')

  reset
  hideturtle
  penup
  ;reset and set up workspace

  make "moves 0
  ;initialize the moves counter

  ;*** OPTIONAL ***
  make "camerastep 1
  ;define camera 'step'
  cam:orbitdown 90
  ;position camera
  ;*** END OPTIONAL ***

  print |*** TOWERS OF HANOI ***|
  print |Move the disks from rod 1 to rod 3, keeping the same order|
  print |(largest at the bottom to smallest at the top)|
  cursordown
  playsound "doodoo
  ;instructions

  label "start
  question |Number of Disks (3-9)?|
  make "disks answer
  if not numberp :disks [go "start]
  if or :disks < 3 :disks > 9 [go "start]
  ;select number of disks to play with
  ;and validate choice

  if :disks > 6 [print |Wow! You're brave! Good luck!| cursordown]
  ;message of encouragement for hard levels

  ;*** OPTIONAL ***
  control "snappy {"raise 2 * :disks}
  control "snappy {"pullin 140 - (8 * :disks)}
  make "snappypos snappy:position
  make "snappyvec snappy:vectors
  ;position camera based on number of disks
  ;and store the camera position.
  ;this can be omitted if text-only game
  ;*** END OPTIONAL ***

  make "rod1 reverse range {1 :disks}
  make "rod2 []
  make "rod3 []
  ;define the rod lists, creating the disks
  ;on the first rod

  label "select
  ;labels can be returned to using 'go'

  (show :rod1 :rod2 :rod3)
  ;show the rod lists
  (show |Moves elapsed:| :moves)
  ;and the move count

  ;*** OPTIONAL ***
  drawdisks
  ;draw the graphical representation
  ;(via the optional drawdisks user procedure)
  ;*** END OPTIONAL ***

  question |Rod?|
  make "selection word "rod answer
  if not namep :selection [print |Invalid selection!| go "select]
  if (count thing :selection) = 0 [print |Invalid rod!| go "select]
  ;select the source rod and check for validity

  label "dest
  question |Destination?|
  make "destination word "rod answer
  if not namep :destination [print |Invalid selection!| go "dest]
  if (last thing :destination) < (last thing :selection) [
    print |Invalid move!|
    go "select
  ]
  ;select the destination rod and check for validity

  make "disk dequeue :selection
  queue :disk :destination
  ;move the disk, by 'dequeuing' or removing the last
  ;item from the selected rod into a variable,
  ;and then queuing the item (adding it as the last
  ;item) to the destination rod

  inc "moves
  ;increment the moves counter

  ;*** OPTIONAL ***
  ;the following orbits the camera around the game
  ;to spice it up a little:
  inc "camerastep
  ;increment the camerastep container
  cam:repeat 45 [orbitright 1]
  ;orbit the camera 45 degrees right
  if divp 3 :camerastep [cam:repeat 45 [orbitright 1]]
  ;if camerastep divisible by 3 then orbit another 45
  ;so we're not looking at the side of the game
  ;*** END OPTIONAL ***

  if :rod3 = reverse range {1 :disks} [
    drawdisks
    ;update the graphics (OPTIONAL)

    control "snappy {
    "setposition :snappypos
    "setvectors :snappyvec
    }
    ;restore the camera position (OPTIONAL)

    print |You win! Great job. Try more disks!|
    (print |Moves taken:| :moves)
    playsound "applause
    finish
  ]
  ;check the third rod for a completed tower
  ;and if complete, displays 'win' message and quits

  go "select
  ;return (loop back) to select label

END

TO drawdisks
  ;the graphical representation of the towers is
  ;optional, and makes a good 'stretch goal'

  ;you can start with just rendering the disks,
  ;then add a base and rods, and finally
  ;rod numbers (to make it easier to keep track
  ;of the rods when playing with camera rotation
  ;enabled)

  norender
  ;don't update graphics until render is called

  clean
  ;erase graphics

  setposition {-50 - 4 * :disks 0 - 4 * :disks 0}
  setfillcolor brown
  voxeloid 2 * (50 + 4 * :disks) 2 * (4 * :disks) 5
  ;create base of appropriate size

  repeat 3 [
    setposition {-100 + (50 * repcount) 0 0}
    rollright 180
    setfillcolor brown
    cylinder 2 5 + :disks * 5 10
    rollright 180
    raise 5
    ;create rod of appropriate height

    if 0 < count thing word "rod repcount [ ;if there are disks on the rod: foreach "i thing word "rod repcount [ ;for each disk on the rod: setfillcolor :i ;set the color based on the disk size cylinder 2 + (2 * :i) 5 10 * :i ;create the disk raise 5 ] ] ] ;create disks on each rod up 90 settypesize 5 + (:disks / 4) repeat 3 [ setfc item repcount [13 11 14] setposition { (-100 - :disks / 2) + 50 * repcount 0 - 4 * :disks (-14 - (:disks / 3)) - (:disks / 4) } typeset repcount ] ;print numbers under towers rollright 180 repeat 3 [ setfc item repcount [14 11 13] setposition { (100 + :disks / 2) - 50 * repcount 4 * :disks (-14 - (:disks / 3)) - (:disks / 4) } typeset 4 - repcount ] ;print numbers under towers opposite side rollright 180 down 90 ;return the turtle to the proper orientation render ;resume rendering if :moves > 0 [playsound "knock]
  ;if not the start of the game, make a sound

END

NEWTURTLE "snappy


NEWTURTLE "libby


 

Example: Plane Trapped in a Torus

This example demonstrates the use of various camera-related functions, shape inversion, the premodel primitive and others to create this cool animation of a plane trapped in a torus!

The procedure first sets the turtle model to the built-in plane model, before creating a tag that sets the color of the torus. Then it creates an inverted torus (one whose inside is rendered instead of its outslde) because we’re going to fly inside it!

We then position the turtle to prepare it for its orbital flight path, position the camera behind it and set up its light. Then we begin orbiting, and while we do so we use setpremodel to move the turtle relative to the camera, creating the drifting motions of the plane as it flies.

Every so often, we change the color of the torus by replacing the contents of the color tag. Cool stuff!

Read through the source code below and take note of the comments, which explain what various parts of the trapped procedure do.

TO trapped
  reset
  setmodel "plane
  ;sets Myrtle's model to the built-in plane model
  
  begintag "col
  setfillcolor 1 + random 15
  endtag
  ;the contents of tags can be used to create models, or
  ;they can be disabled or replaced. We're going to replace
  ;the contents of this tag later in our procedure, to change
  ;the color of the torus 'on the fly' (ba-dum)
  
  torus 30 -50 20 20
  ;creates an inverted torus by inverting the radius parameter.
  ;Closed shapes such as the torus don't ordinarily have an 'inside'
  ;unless we create them inverted.
  
  ;Why do we want to invert it?
  
  ;We're going to switch the camera turtle to Myrtle, and so
  ;we want to see inside the torus, not the outside. To do this,
  ;we invert it, as demonstrated above.
  
  penup
  ;don't forget, the turtle draws a line by default
  
  dropanchor
  tether
  pullout 50
  left 90
  ;We're going to use the orbit primitive to move Myrtle
  ;inside of the torus. So we 'dropanchor' at Myrtle's position,
  ;to set the 'anchorpoint' that the orbit primitives rotate around,
  ;'tether' to keep the anchor point static (because otherwise
  ;when Myrtle turns the anchor point moves to stay in front of
  ;her) 'pullout' 50 turtle units from the anchor point, and then
  ;turn left 90 degrees (which we can do because we called tether)
  
  ;To demonstrate the need for tether, try:
  ;reset repeat 36 [repeat 90 [orbitleft 4] right 10]
  ;As you can see, the point Myrtle orbit arounds moves when she
  ;turns right. Put a 'tether' primitive before the first repeat
  ;and notice the difference!
  
  setpremodel [rt 10]
  ;'setpremodel' allows us to put commands between the 'turtle track'
  ;that contains everything the turtle draws and the turtle model
  ;itself.
  
  ;When we attach the camera to a turtle, its position is
  ;set before premodel (and the turtle model) and so we can use
  ;setpremodel to change the position and orientation of the turtle
  ;as seen from the camera. And so we will see the model pointing
  ;slightly to the right
  
  setview "myrtle
  ;set the camera to show Myrtle's point of view
  
  snappy:setlight 0
  ;turn Snappy (the normal camera turtle)'s light off.
  ;Snappy has a light on by defalt.
  
  setlight 2
  ;turn Myrtle's light on. The value 2 is a point light,
  ;which casts light around Myrtle
  
  setdiffuse [40 40 40 100]
  setambient [0 0 0 0]
  ;these set paramets related to the light.
  ;see their help entries for more information
  
  setviewpoint [5 10 -50]
  ;sets the position of the camera relative to the turtle
  ;as a list of [x y z]. So in this case, to the right, above
  ;and behind the turtle
  
  forever [
    
    setpremodel {
    "right 20 - 30 * (sin 0.5 * loopcount)
    "slideright -5 + 10 * (sin 0.5 * loopcount)
    "up 10 * (sin loopcount)
    "raise 2.5 - 10 * (0.5 * sin loopcount)
    }
    
    ;curly braces indicate a 'softlist', a list that is
    ;evaluated at the time of execution (the point at
    ;which the interpreter interprets the list).
    ;They allow us to use functions and containers to
    ;assemble a list dynamically.
    
    ;In this case, we're using the sin function and loopcount
    ;to move the turtle model relative to its position and
    ;simulate the motion of an aircraft in flight.
    
    ;loopcount is similar to repcount, but is used in non-repeat
    ;loops such as forever, while, until, dowhile, dountil and
    ;foreach.
    
    ;it counts the number of times the loop has been executed
    
    orbitleft 0.5
    ;orbit to the left half a degree. In this case, 'to the left'
    ;causes the turtle to appear to move forward. But remember,
    ;we turned the turtle to the left earlier in the procedure!
    
    if divp 300 loopcount [
      replacetag "col {
      "setfillcolor 1 + random 15
      "setfs -15 + random 30
      }
    ]
    ;divp (or divisorp) returns true if the numerator (the first
    ;number) divides equally into the denominator (the second
    ;number, in this case the loopcount.)
    
    ;if divp returns true, we replace the "col tag we created
    ;earlier in the procedure with new contents setting a random
    ;color and shade. We need to use curly braces so that the
    ;random numbers are generated at the time we call replacetag.
    
    ;note that the primitives need to have a " in front of them
    ;so they are not themselves executed when the softlist is
    ;evaluated! By putting a quotation mark in front of them,
    ;they are evaluated as words and passed verbatim to
    ;the replacetag primitive.
    
    ;there are other ways to do this that you can do in other
    ;Logo environments but they are more cumbersome.
    
    sleep 5
    ;sleep for 5 milliseconds
    
  ]
  ;do this forever, or until we press escape or the stop button
  
END

 

 

 

Rocket Orbit

This simple commented project allows for the introduction of 3D movements and shapes (the rocket procedure) including a basic introduction to repeat, the creation of turtle models and the use of premodel, an introduction to the orbit primitives and the use of a new worker (thread) for orbiting the camera!

TO rocket
  forward 70
  down 90
  ;these two commands orient the rocket better for use as
  ;a turtle model
  
  setfillcolor yellow
  ;yellow is a function that returns the value 13
  ;it is a shortcut used for convenience in learning
  ;as is red, green, blue etc.
  
  cylinder 20 120 10
  ;cylinder takes   
  
  rollright 180
  ;flip the turtle over to create the nose cone
  
  cone 20 50 10
  
  lower 50
  ;lower the turtle to create the 'nose'
  
  setfillcolor red
  icosphere 3
  
  setfillcolor orange
  ra 50 + 120
  ;ra is short for raise
  
  rr 180
  ;rr is shorthand for rollright
  
  cutcone 20 30 30 10
  ;move the turtle to the bottom of the rocket and
  ;create the tail
  
  up 90 bk 30.1
  ;bk is short for back
  
  setfillcolor white
  repeat 2 [cylinderslice 40 5 20 10 rr 90]
  ;create the fins
  
  setfillcolor red
  dn 90 sl 10 bk 10 cylinder 10 10 10
  ;sl is short for slideleft
  
  fd 20 cylinder 10 10 10 sr 20
  ;fd is short for forward
  ;sr is short for slideright
  
  cylinder 10 10 10 bk 20 cylinder 10 10 10
  ;create the jets
  
END

TO createmodel
  clearscreen
  ;cs is short for clearscreen
  
  begintag "rocketmodel
  ;tags are used to create turtle models
  ;among other uses. You can also use them to
  ;show or hide parts of the 'turtle track'
  
  rocket
  ;run the rocket procedure
  
  endtag
  ;close the tag
  
  newmodel "rocket "rocketmodel
  ;create a new model called 'rocket' based on the
  ;rocketmodel tag
  
  setmodel "rocket
  ;set the turtle's model to the rocket model
  
  clearscreen
  ;clear away the tagged model, leaving only the
  ;turtle
END

TO starfield :number
  hideturtle
  norender
  ;suspend rendering of the scene until we're done drawing the stars
  penup
  repeat :number [
    home
    randomvectors randomfillcolor
    ;randomvectors orients the turtle randomly
    ;while randomfillcolor sets a random fill color
    
    forward 1000 + random 1000
    ;move forward a random distance
    
    up 90
    ;orient the turtle up in preparation of creating a spot
    ;because the spot is created around and below the turtle
    
    spot 10 + random 10
    ;create a randomly-sized spot
    
  ]
  
  home
  showturtle
  render
  ;resume rendering
  
END

TO main
  reset
  ;resets turtleSpaces to a default state
  
  createmodel
  ;execute the createmodel procedure
  
  starfield 200
  ;execute the starfield procedure, passing the parameter value 200
  
  setmodelscale 0.5
  ;decrease the size of the turtle model to half normal
  
  setpremodel [lt 90]
  ;inserts 'left 90' into the turtle track between the scene
  ;and the turtle model ('pre' the model)
  
  setfillcolor 14
  ;sets the fillcolor to 14. Type 'showcolors' or 'sc' in the console
  ;to see a list of default colors. You can also definecolor your own!
  
  ico 50
  ;create an icosphere (the planet)
  
  penup
  dropanchor
  ;set the 'anchor point', or the point the turtle 'orbits' around
  ;using the orbit commands, to the current turtle position
  
  pullout 80
  ;pull away 80 turtle units from the anchor point
  
  snappy:newworker [forever [orbitleft 0.1]]
  ;tell snappy the camera turtle to create a new routine or thread
  ;that forever orbits around the scene to its left one tenth of
  ;a degree at a time
  
  make "rotation 0
  ;define a container (variable) called 'rotation'
  ;and set it to 0. We're going to use it to keep track of the
  ;rotation of the rocket
  
  forever [
    inc "rotation
    ;same as make "rotation :rotation + 1
    ;there is also dec (decrement)
    
    setpremodel {"lt 90 "rollright :rotation % 360}
    ;curly braces indicate a 'softlist', a list that is evaluated
    ;at runtime. In this case, so that we can have a dynamic
    ;rotation value we pass to setpremodel
    
    ;% is shorthand for modulus
    
    orbitleft 1
    ;orbit the rocket one degree to the left
    
    wait 1
    ;wait one sixtieth of a second
    
  ]
END

 

 

Example: Tetris written in turtleSpaces Logo

What do Steve Wozniak and George H.W. Bush have in common? They’ve both been seriously into Tetris! But who can blame them? The object of the game (as if you didn’t know) is to complete horizontal lines using falling shapes of various configurations. When you finish a line, it disappears, causing the rest of the blocks to fall down a line. However, if you stack up shapes to the point they overflow the top of the playfield: Game Over. When you finish a certain number of lines, the level ends… and in the next, the shapes fall faster, and you need to complete more lines! The insanity never ends.

In an era where games were becoming increasingly more complex, the simplicity of Tetris was seen as a breath of fresh air. Tetris would inspire a number of other “falling block puzzle games” such as Sega’s colour-matching Columns, and the three-dimensional Welltris. But Tetris would always remain king (tsar?) of the arcade puzzle game world, with sequelsclones and variations being released for virtually every console, computer, and operating system worldwide.

A little history

Alexey Pajitnov’s Tetris – early unauthorised versions of which, such as Spectrum Holobyte’s rendition described above, began appearing on home computers in 1987 – spawned a whole new generation of shape-based puzzle games.

Holobyte’s version took next year’s CES by a storm, and garnered the attention of the Soviet government, who held the rights to the game. They sold the arcade rights to Atari and the console rights to Nintendo.

Nintendo released Tetris on both its Nintendo Entertainment System and on the Game Boy, the latter as a pack-in with the portable console. The inclusion of Tetris arguably made the Game Boy a success –the game was perfect for smaller screen sizes, and was very addictive, spawning a whole generation of Tetris ‘junkies’. 

Nintendo’s version of Tetris for the NES was criticised for not having a two-player mode; however, Atari Games, perhaps mistakenly believing their arcade licensing gave them the right to release a console version, came out with their own Nintendo Tetris game through their Tengen subsidiary (created after the consumer rights to the Atari name were sold to Jack Tramiel, see Point & Click) which featured head-to-head play.

Nintendo sued, and Tengen was eventually forced to withdraw its cartridge from sale, after selling around 100,000 copies. They are collectibles.

All right, so let’s take a look at the listing. The first thing you’ll notice is that it’s very monolithic, there’s only one procedure! That’s okay, though, in this context. While Logo is very versatile, sometimes aspects of that versatility such as the use of multiple procedures are unneeded.

You can also view the example inside of turtleSpaces webLogo by clicking File -> Browse Published… after it finishes loading and selecting the Tetris project.

In this example, we handle most of the structure using dountil and switch / case — dountil loops its contents until a condition is true, while switch takes a value from a container (variable) and then case compares the value it is given with the switch, and executes its list if that condition is true.

The game procedure begins with some basic initialization, such as creating the table we will use to note the finishing positions of the pieces, draws the border around the playfield and then proceeds into the main ‘run loop’, the dountil :gamover which loops until the gameover container is set to true.

Inside the dountil loop we choose the next Tetris piece to play, set a random fill color, set the starting position for the piece based on its characteristics (we don’t want it overlapping the edges or starting ‘over the top’) and ensure that the new piece doesn’t overlap an existing piece (if so, game over!)

If it doesn’t, we continue into the dountil :dropped loop, which runs until the piece has fully ‘dropped’ into its final position. We use a switch statement and case statements to run the logic relevant to the current piece. For readability I decided to make each orientation of a piece its own piece — while this means there are a lot more ‘pieces’ than if I had handled variants (rotations) using the same chunks of code, these are simpler to understand than they would have been had they had all of these conditionals in them dependent on the orientation of the piece.

Also, you can hand each orientation of a new piece off to a student to complete, which could make for an interesting group project. A number of pieces have not been implemented in this example, which could be implemented by your students, based off of the pieces that have been implemented.

Inside each piece subroutine we check for a keypress, then act if there was a keypress, checking to see if the action (moving left, right, or ‘rotating’ (switching to another orientation)) would collide with an existing piece or the edges before performing the action.

Then we draw the piece, and delay. There is a ‘drop key’ that sets the delay to zero for the remainder of the piece’s time in play. Then we see if we’ve ‘landed’ either on another piece or on the bottom, by checking the appropriate cells in the table. If so, we set the :dropped container to true, so that we will ‘drop out’ of the dountil loop, and we set our current piece’s occupied cells in the table. We only bother to set these when the piece has finished moving because there is no point in setting them while it’s dropping.

If we haven’t landed, we turn off rendering (so things don’t flicker) and ‘backtrack’ the necessary number of steps to ‘undo’ the drawing of the piece. Since turtleSpaces graphics are made out of 3D shapes, we can’t just erase a particular area, we have to erase or backtrack part of the turtle track — the list of objects the turtle has created.

You could also use tags to do the same thing, wrapping the piece in a tag, and then erasing the tag. turtleSpaces is versatile!

If the current piece has ‘dropped’ we then check to see if we’ve filled in any lines. To do this we sequentially run horizontally through each row in the table, checking each column, and adding to a counter each time a column has a value in it. If we count up to 10, we have a full row, and we erase it by copying the contents of all of the above rows down one row, clearing the top row when we are done. We add one to a score counter, and inform the user of their success.

The game then continues until the :gameover dountil condition is met, at which point it terminates.

TO game
  reset
  penup hideturtle
  
  ;create table to hold tetris piece placement information:
  newtable "board [10 21]
  
  ;draw playfield border:
  setpos [-60 -110]
  voxeloid 10 210 10
  voxeloid 120 10 10
  setpos [50 -110]
  voxeloid 10 210 10
  
  make "gameover false
  make "score 0
  
  dountil :gameover [
    
    ;pick random piece:
    make "piece pick [square hline vline hzed vzed hess vess]
    
    ;the logic is easier to implement if each orientation of a shape
    ;is its own piece. While this means implementing a lot of pieces
    ;to make a full Tetris game, it avoids cumbersome if statements
    
    ;TODO: tee0 tee90 tee180 tee270 -- T shapes
    ;TODO: jay0 jay90 jay180 jay270 -- J shapes
    ;TODO: elle0 elle90 elle180 elle270 -- L shapes
    ;MAYBE: crowbar corkscrew rightangle cshape?
    
    show :piece
    
    randomfillcolor
    
    ;choose random starting placement based on the piece.
    ;We need to make sure the piece is fully in the playfield
    ;and doesn't overlap any edges:
    
    if :piece = "square [make "y 1 make "x 1 + random 9]
    if :piece = "hline [make "y 0 make "x 1 + random 7]
    if :piece = "vline [make "y 3 make "x 1 + random 10]
    if :piece = "hzed [make "y 0 make "x 1 + random 8]
    if :piece = "vzed [make "y 2 make "x 1 + random 8]
    if :piece = "hess [make "y 1 make "x 1 + random 8]
    if :piece = "vess [make "y 2 make "x 2 + random 9]
    
    ;check if the area selected for the new piece is already
    ;occupied by another piece, if so then game over:
    
    if :piece = "square [
      if (or not emptyp cell "board {:x :y}
      not emptyp cell "board {:x + 1 :y}
      not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x + 1 :y + 1}) [make "gameover true]]
    if :piece = "hline [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x + 1 :y + 1}
      not emptyp cell "board {:x + 2 :y + 1}
      not emptyp cell "board {:x + 3 :y + 1}) [make "gameover true]]
    if :piece = "vline [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x :y}
      not emptyp cell "board {:x :y - 1}
      not emptyp cell "board {:x :y - 2}) [make "gameover true]]
    if :piece = "hzed [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x + 1 :y + 1}
      not emptyp cell "board {:x + 1 :y + 2}
      not emptyp cell "board {:x + 2 :y + 2}) [make "gameover true]]
    if :piece = "vzed [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x :y}
      not emptyp cell "board {:x + 1 :y}
      not emptyp cell "board {:x + 1 :y - 1}) [make "gameover true]]
    if :piece = "hess [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x + 1 :y + 1}
      not emptyp cell "board {:x + 1 :y}
      not emptyp cell "board {:x + 2 :y}) [make "gameover true]]
    if :piece = "vess [
      if (or not emptyp cell "board {:x :y + 1}
      not emptyp cell "board {:x :y}
      not emptyp cell "board {:x - 1 :y}
      not emptyp cell "board {:x - 1 :y - 1}) [make "gameover true]]
    
    make "dropped false
    ;boolean to indicate the piece has 'landed'
    make "speed 10
    ;the speed at which the pieces 'fall'
    
    dountil :dropped [
      ;repeat the following until :dropped is true
      
      switch "piece
      ;based on the value of the "piece container, execute one
      ;of the following case statements:
      
      case "square [
        
        make "y :y + 1
        ;increment "y
        
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x - 1 :y - 1}) [dec "x]]]
          if :key = "k [
            if :x < 9 [ if (and emptyp cell "board {:x + 2 :y} emptyp cell "board {:x + 2 :y - 1}) [inc "x]]] if :key = "m [make "speed 0] clearchar ] ;we check to see if a key has been pressed, and ;if it has, then we see if it's one of the keys ;we respond to, and then we respond to them IF ;moving the piece will not collide with already ;existing pieces. m sets the speed to 0 and causes ;it to drop as quickly as possible ;clearchar clears the keyboard buffer sety 100 - :y * 10 setx -60 + :x * 10 voxeloid 20 20 10 ;sets the turtle's position and draws the shape if :speed > 0 [trackrender]
        ;don't render if the shape is 'dropping'
        ;(eg we pressed the m key)
        
        wait :speed
        ;delay in 60ths of a second, so 10 is 1/6th of a second
        
        ;check to see if we've landed on another piece,
        ;or hit the bottom, and then enter the piece position
        ;into the table:
        if (or :y = 20
        not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x + 1 :y + 1}) [
          setcell "board {:x :y} fc
          setcell "board {:x + 1 :y} fc
          setcell "board {:x :y - 1} fc
          setcell "board {:x + 1 :y - 1} fc
          make "dropped true
          ;set dropped to true so we continue to the
          ;line check routine
        ]
        
        ;if we haven't 'landed' then turn off rendering,
        ;and 'undo' the drawing of the piece in preparation
        ;for the next move:
        else [notrackrender backtrack]
        
      ]
      ;that's it for this piece, on to the next!
      ;As the pieces get more complex, so does the logic
      ;needed to position and move them
      
      case "hline [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if emptyp cell "board {:x - 1 :y} [dec "x]]]
          if :key = "k [
            if :x < 7 [ if emptyp cell "board {:x + 4 :y} [inc "x]]] if :key = "i [ if :y > 3 [make "piece "vline]]
          if :key = "m [make "speed 0]
          clearchar
        ]
        
        sety 100 - :y * 10 setx -60 + :x * 10
        voxeloid 40 10 10
        if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 20
        not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x + 1 :y + 1}
        not emptyp cell "board {:x + 2 :y + 1}
        not emptyp cell "board {:x + 3 :y + 1}) [
          setcell "board {:x :y} fc
          setcell "board {:x + 1 :y} fc
          setcell "board {:x + 2 :y} fc
          setcell "board {:x + 3 :y} fc
          make "dropped true
        ]
        else [notrackrender bt]
      ]
      
      
      case "vline [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x - 1 :y - 1}
              emptyp cell "board {:x - 1 :y - 2}
              emptyp cell "board {:x - 1 :y - 3}) [dec "x]]]
          if :key = "k [
            if :x < 10 [
              if (and emptyp cell "board {:x + 1 :y}
              emptyp cell "board {:x + 1 :y - 1}
              emptyp cell "board {:x + 1 :y - 2}
              emptyp cell "board {:x + 1 :y - 3}) [inc "x]]]
          if :key = "i [
            if :x < 8 [ if (and emptyp cell "board {:x + 1 :y + 1} emptyp cell "board {:x + 2 :y + 1} emptyp cell "board {:x + 3 :y + 1}) [make "piece "hline]]] if :key = "m [make "speed 0] clearchar ] sety 100 - :y * 10 setx -60 + :x * 10 voxeloid 10 40 10 if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 20 not emptyp cell "board {:x :y + 1}) [
          setcell "board {:x :y} fc
          setcell "board {:x :y - 1} fc
          setcell "board {:x :y - 2} fc
          setcell "board {:x :y - 3} fc
          make "dropped true
        ]
        else [notrackrender bt]
      ]
      
      
      case "hzed [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x :y + 1}) [dec "x]]]
          if :key = "k [
            if :x < 8 [if (and emptyp cell "board {:x + 2 :y} emptyp cell "board {:x + 3 :y + 1}) [inc "x]]] if :key = "i [if :x > 1 [if (and
              emptyp cell "board {:x + 1 :y}
              emptyp cell "board {:x + 1 :y - 1})
              [make "piece "vzed]]]
          if :key = "m [make "speed 0]
          clearchar
        ]
        
        sety 100 - :y * 10 setx -60 + :x * 10
        voxeloid 20 10 10 bk 10 sr 10 voxeloid 20 10 10
        if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 19 not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x + 1 :y + 2}
        not emptyp cell "board {:x + 2 :y + 2}) [
          setcell "board {:x :y} fc
          setcell "board {:x + 1 :y} fc
          setcell "board {:x + 1 :y + 1} fc
          setcell "board {:x + 2 :y + 1} fc
          make "dropped true
        ]
        else [notrackrender repeat 4 [bt]]
      ]
      
      
      case "vzed [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x - 1 :y - 1}
              emptyp cell "board {:x :y - 2}) [dec "x]]]
          if :key = "k [
            if :x < 9 [ if (and emptyp cell "board {:x + 1 :y} emptyp cell "board {:x + 2 :y - 1} emptyp cell "board {:x + 2 :y - 2}) [inc "x]]] if :key = "i [ if (and :y > 2 :x < 9 :y < 19) [ if (and emptyp cell "board {:x + 1 :y + 1} emptyp cell "board {:x + 2 :y + 2} emptyp cell "board {:x + 3 :y + 2}) [ make "piece "hzed]]] if :key = "m [make "speed 0] clearchar ] sety 100 - :y * 10 setx -60 + :x * 10 voxeloid 10 20 10 fd 10 sr 10 voxeloid 10 20 10 if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 20
        not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x + 1 :y}) [
          setcell "board {:x :y} fc
          setcell "board {:x :y - 1} fc
          setcell "board {:x + 1 :y - 1} fc
          setcell "board {:x + 1 :y - 2} fc
          make "dropped true
        ]
        else [notrackrender repeat 4 [bt]]
      ]
      
      
      case "hess [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 1 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x :y - 1}) [dec "x]]]
          if :key = "k [
            if :x < 8 [ if (and emptyp cell "board {:x + 2 :y} emptyp cell "board {:x + 3 :y - 1}) [inc "x]]] if :key = "i [ if (and :y > 2 :x > 1) [
              if (and emptyp cell "board {:x :y}
              emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x - 1 :y - 1}) [
                make "piece "vess]]]
          if :key = "m [make "speed 0]
          clearchar
        ]
        
        sety 100 - :y * 10 setx -60 + :x * 10
        voxeloid 20 10 10 fd 10 sr 10 voxeloid 20 10 10
        if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 20
        not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x + 1 :y + 1}
        not emptyp cell "board {:x + 2 :y}) [
          setcell "board {:x :y} fc
          setcell "board {:x + 1 :y} fc
          setcell "board {:x + 1 :y - 1} fc
          setcell "board {:x + 2 :y - 1} fc
          make "dropped true
        ]
        else [notrackrender repeat 4 [bt]]
      ]
      
      
      case "vess [
        
        make "y :y + 1
        if keyp [
          make "key readchar
          if :key = "j [
            if :x > 2 [
              if (and emptyp cell "board {:x - 1 :y}
              emptyp cell "board {:x - 2 :y - 1}
              emptyp cell "board {:x - 2 :y - 2}) [dec "x]]]
          if :key = "k [
            if :x < 10 [ if (and emptyp cell "board {:x + 1 :y} emptyp cell "board {:x + 1 :y - 1} emptyp cell "board {:x :y - 2}) [inc "x]]] if :key = "i [ if (and :y > 2 :x < 9 not :y = 20) [ if (and emptyp cell "board {:x + 1 :y + 1} emptyp cell "board {:x + 2 :y - 1}) [ make "piece "hess]]] if :key = "m [make "speed 0] clearchar ] sety 100 - :y * 10 setx -60 + :x * 10 voxeloid 10 20 10 fd 10 sl 10 voxeloid 10 20 10 if :speed > 0 [trackrender]
        wait :speed
        
        if (or :y = 20
        not emptyp cell "board {:x :y + 1}
        not emptyp cell "board {:x - 1 :y}) [
          setcell "board {:x :y} fc
          setcell "board {:x :y - 1} fc
          setcell "board {:x - 1 :y - 1} fc
          setcell "board {:x - 1 :y - 2} fc
          make "dropped true
        ]
        else [notrackrender repeat 4 [bt]]
      ]
      
    ]
    
    ;if the game is over, don't bother checking for lines:
    if :gameover [go "gameover]
    
    ;the following code checks to see if we've made any lines:
    trackrender
    ;turns on rendering the turtle track
    
    ;we're going to check every column of every row. Every time
    ;we get a 'hit' checking a column, we add up a counter, called
    ;"count. If :count hits 10, then we've made a line:
    
    repeat 20 [
      make "count 0
      repeat 10 [
        if not emptyp cell "board {repcount repabove 1} [inc "count]
      ]
      ;repabove 1 returns the loop value of the repeat loop above
      ;the current repeat loop. repabove 2 returns the loop count
      ;above that, and so on
      
      ;if we've made a line, we need to shuffle every line above
      ;it down one, in order to remove it:
      
      if :count = 10 [
        inc "score
        (print "Score: :score)
        ;increase the player's score by one and display the score
        
        repeat repcount - 1 [
          repeat 10 [
            (setcell "board {repcount (repabove 2) - (repabove 1) + 1}
            cell "board {repcount (repabove 2) - (repabove 1)})
          ]
        ]
        
        ;we need to remember to clear the line at the top:
        repeat 10 [setcell "board {repcount 1} empty]
        
        ;now we need to redraw the playfield to match the table:
        
        notrackrender
        cs
        setfc 7
        setpos [-60 -110]
        voxeloid 10 210 10
        voxeloid 120 10 10
        setpos [50 -110]
        voxeloid 10 210 10
        ;draw the border
        
        ;we iterate through the table and if there's a color
        ;recorded in the specific cell, we create a matching
        ;voxel:
        
        repeat 20 [
          repeat 10 [
            setx -60 + 10 * repcount
            sety 100 - 10 * repabove 1
            if not emptyp cell "board {repcount repabove 1} [
              setfc cell "board {repcount repabove 1} voxel 10]
          ]
        ]
        
        trackrender
        
      ]
    ]
    
    label "gameover
    
  ]
  ;game over, man... game over!
  
  print |Game Over!|
  
END

 

Example: Invasion (3D Space Invaders Clone)

Check out this simple space invaders clone, featuring 3D UFO’s and UFO and player movement. This runs well on both the application and in the web version using modest hardware.

Rather than using hatchlings each with its own thread (worker), Invasion creates 15 static turtles representing each UFO, then iterates through moving them using the main (Myrtle) worker, more like a conventional single-threaded application would.

One procedure is used to generate randomly-colored UFO models, which are then assigned to each UFO turtle. The UFO turtles move in a 3D pattern created by ‘rolling’ them (rotating them on the Z axis), moving them left or right using slideleft and slideright, and raising and lowering them (using raise and lower) – a great exploration of 3D movement in turtleSpaces.

The code is commented and follows below. It’s also available inside the WebIDE under the menu FIle -> Browse Published…

NEWTURTLE "myrtle

TO game
  ;invasion is a simple space invaders clone with a 3D twist
  
  reset
  release
  ;reset the workspace and release the non-standard turtles (ufos)
  
  stars:drawstars
  ;draw a starfield using the stars turtle
  
  nokeyclick
  ;don't click keys
  
  noaudiowait
  ;don't pause on audio playback
  
  snappy:newworker [
    pullout 100
    forever [
      repeat 45 [orbitleft 1 wait 5]
      repeat 90 [orbitright 1 wait 5]
      repeat 45 [orbitleft 1 wait 5]
      repeat 45 [orbitup 1 wait 5]
      repeat 90 [orbitdown 1 wait 5]
      repeat 45 [orbitup 1 wait 5]
    ]
  ]
  ;create a new 'worker' to move the camera around the scene
  ;independently of other activity
  
  make "fleet []
  share "fleet
  ;the fleet container holds the list of UFO turtle names
  ;it needs to be shared so all the UFO turtles can edit it
  
  make "score 0
  share "score
  ;the score cotnainer holds the score, the number of UFOs destroyed
  ;the UFOs themselves increment the score, and so it must be shared
  
  repeat 3 [
    repeat 5 [
      newtu word repcount repabove 1
      ;create a new turtle with the name column + row
      ;note: newtu is NOT the same as newturtle, newturtle is a
      ;declaration used in the workspace file (this), while newtu
      ;creates a new turtle during execution and selects it
      ;(we should change its name to instatiate?)
      
      noaudiowait
      
      makemodel
      ;create its UFO model
      
      queue turtle "fleet
      ;add the turtle name to the fleet container
      
      make "shootcount 0
      ;reset its shootcounter to 0
      
      penup
      ;don't draw anything!
      
      setposition {-225 + repcount * 75 -25 + (repabove 1) * 50}
      ;set its position based on its row and column number
      ;curly braces indicate a 'soft list', a list that is evaluated
      ;at runtime. repabove gets the loop count of the repeat enclosing
      ;the current repeat
      
      showturtle
      ;show the UFO
    ]
  ]
  ;create three rows of 5 UFOs each
  
  setturtle myrtle
  ;reselect myrtle
  
  setmodel "spaceship
  ;change myrtle's model to the built-in spaceship model
  
  setmodelscale 2
  ;set the model's scale (size) to a factor of 2
  
  penup back 150
  ;position Myrtle
  
  newworker [
    forever [
      movefleet
      ;run the movefleet procedure
      
      toot 200 + random 1000 2
      ;make a random beep
      
      wait 10
    ]
  ]
  ;create a new 'worker' to move the UFOs 'forever'
  
  make "delay 0
  ;stores the delay between player shots
  
  forever [
    if :delay > 0 [dec "delay]
    ;decrease the delay value by one, if it is above zero
    
    if keyp [
      ;if a key has been pressed:
      
      make "key uppercase readchar
      ;store the uppercase value of the key in the key container
      
      switch "key
      ;use the value of the key value for the following case comparisons:
      
      case "J [repeat 10 [repeat 10 [sl .1]]]
      case "K [repeat 10 [repeat 10 [sr .1]]]
      case "I [repeat 10 [repeat 10 [ra .1]]]
      case "M [repeat 10 [repeat 10 [lo .1]]]
      ;move Myrtle as required
      
      case "A [
        ;should I shoot?
        if :delay = 0 [
          ;yes, I should shoot!
          
          ;we're going to 'hatch' a missile BUT if we just
          ;do that, our detection routine will think we've been shot.
          ;to work around this, we're going to stop rendering our
          ;spaceship while we launch our missile from a position
          ;slghtly forward of our current position.
          
          notrackrender
          ;don't render this turtle's graphics
          
          forward 31
          updatestate
          ;update the state of the turtle. This is usually done while
          ;rendering, so we need to do this manually while rendering is
          ;off
          
          hatch [
            showturtle
            repeat 300 [fd 1]
          ]
          ;hatch our 'missile', which goes forward 300 steps and then 'dies'
          
          back 31
          ;move back to where we were before
          
          trackrender
          ;turn track rendering back on
          
          playsound "pew
          ;play a 'pew' sound
          
          make "delay 20
          ;set the delay container to 20, which causes us to have to wait
          ;before firing again
        ]
      ]
    ]
    
    if nearp 30 [
      ;check to see if we've been hit. If we have:
      
      playsound "explosion
      hideturtle
      print |Boom! Game over!|
      repeat 50 [
        randomfillcolor
        icosphere repcount
        wait 1
      ]
      ;explosion animation
      
      clean finish
      ;finish stops all execution
    ]
    
    wait 1
    ;wait 1/60ths of a second between loops
  ]
  ;do it again
  
END

TO movefleet
  ;this procedure moves the UFOs
  
  if :fleet = emptylist [
    ;if there are no UFOs left:
    print |Great work!|
    playsound "applause
    wait 120
    finish
  ]
  
  foreach "ufo :fleet [
    ;for each of the UFOs remaining:
    
    setturtle :ufo
    ;select the ufo turtle
    
    rollright 10
    ;roll right 10 degrees
    
    if 1 = last :ufo [slideright 5]
    if 1 = first :ufo [lower 5]
    if 2 = last :ufo [slideleft 5]
    if 2 = first :ufo [raise 5]
    if 3 = last :ufo [slideright 5]
    if 3 = first :ufo [lower 5]
    ;do a merry dance
    
    if :shootcount > 0 [dec "shootcount]
    if and 1 = random 20 :shootcount = 0 [
      make "shootcount 20
      hatch [
        right 180 showturtle
        repeat 300 [fd 1 rr 1]
      ]
    ]
    ;fire!
    
    foreach "i near position 50 [
      if "m = second :i [ht
        newworker [repeat 50 [ico repcount wait 1] clean]
        ;boom
        
        make "fleet without turtle :fleet
        ;remove ufo turtle from fleet list
        
        inc "score (print |Score:| :score)
        ;increase the score and tell the user
        
        playsound "explosion
        ;play an 'explosion' sound
        
        myrtle:try [halt hatchlingworker :i] []
        ;stop the missle that shot me (if it still exists)
        
      ]
    ]
    ;you got me!
  ]
  
  tu myrtle
  ;reselect Myrtle
END

NEWTURTLE "snappy


NEWTURTLE "libby

TO makemodel
  ;make the UFO model for the calling turtle
  
  begintag turtle
  makeufo
  endtag
  newmodel word "ufo turtle turtle
  
  clearscreen
  ;this applies only to the calling turtle
  
  setmodel word "ufo turtle
END

TO makeufo
  ;create the UFO itself
  
  penup
  randomfillcolor
  
  make "sides 3 + random 6
  down 90
  rollright 180
  
  torus 10 25 3 :sides
  ;make the outer ring
  
  switch {fc}
  case 1 [setfc 9]
  case 2 [setfc 7]
  case 3 [setfc 11]
  case 4 [setfc 12]
  case 5 [setfc 10]
  case 6 [setfc 2]
  case 7 [setfc 6]
  case 8 [setfc 13]
  case 9 [setfc 1]
  case 10 [setfc 15]
  case 11 [setfc 3]
  case 12 [setfc 14]
  case 13 [setfc 8]
  case 14 [setfc 12]
  case 15 [setfc 5]
  ; select a complementary fill color
  
  dome 25 3 :sides
  ;make the top dome
  
  polyspot 25 :sides
  ;make the bottom spot
  
END

NEWTURTLE "stars

TO drawstars
  ;draw the starfield
  penup
  
  repeat 100 [
    ;do 100 times:
    home
    ;go home
    
    randomvectors
    ;pick a random vector orientation
    
    forward 1000 + random 1000
    up 90
    randomfillcolor
    spot 5 + random 10
    ;create a randomly colored 'star'
  ]
  
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 

Simple Hexagon Pattern

Here is a simple Logo project to get started with. It creates a colorful filled hexagon pattern using the polyspot primitive.

The hexagon pattern zoomed out

TO hex
  
  clearscreen penup
  ;not drawing lines
  
  repeat 10 [
    ;do all of the following ten times
    
    setpos [-149 -270]
    ;sets the 2D turtle position (on the current Z plane)
    ;you can use setposition to set all three co-ordinates
    
    forward repcount * 34
    ;positions the turtle to start the line
    ;you could use some math with setpos to accomplish this
    
    randomfillcolor
    ;choose a random fill color
    
    polyspot 20 6
    ;make a filled polygon of size 20, with 6 sides
    
    repeat 10 [
      ;make ten more polygons
      
      slideright 30
      forward 17
      randfc
      ;randfc is the short form of randomfillcolor
      
      polyspot 20 6]
    ;return to the start of the loop
  ]
  ;finished
END

A turtleSpaces (app) screenshot of the editor

The hexagon pattern viewed from a different 3D angle