‘Snake’ written in turtleSpaces Logo

TO snake
  
    
  ;setup:
  
  reset
  ;empties (erases) all containers (variables),
  ;returns the turtles to their default positions,
  ;restores their states to defaults
  
  release
  ;sets all the turtles, except for the default ones
  ;(myrtle, snappy, libby) 'free' - erasing them
  
  penup
  ;don't draw
  
  setbackgroundcolor lightblue
  
  hideturtle
  ;don't show the executing turtle (typically myrtle)
  
  stealth
  ;don't show up in output from the near primitive
  
  noaudiowait
  ;don't delay when playing sounds (the default)
  
  print |Use keys J and K to turn snake, eat apples!|
  ;print instructions to the screen
  
  ;create the 'ground'
  setfillcolor brown
  setpos [-185 -115]
  lower 5
  quad 185 * 2 115 * 2
  
  make "score 0
  make "length 8
  ;the default snake length, in segments
  
  make "queue empty
  ;this snake implementation uses a push / pull queue
  ;to keep track of the snake's turns
  
  repeat :length [push "fd "queue]
  ;all segments start moving forward
  
  newmodel "ball [setfillcolor 12 icosphere 5]
  newmodel "head [setfillcolor 4 icosphere 6]
  newmodel "apple [setfillcolor red icosphere 4]
  ;create turtle models, where ball is a non-head segment
  
  ;create the turtles that make up the starting snake:
  repeat :length [
    nameturtle repcount
    ;create a new turtle
    ;each segment has the number of its segment as its name
    
    control repcount {"wrap "penup "back repcount "* 10 "showturtle}
    ;initialize the new turtle and move it into position
    ;{} denotes a 'softlist', a list whose contents are evaluated
    ;at execution
    
    control repcount [
      setbounds [-185 -115 185 115]
    ]
    ;sets the bounds used for wrapping. [] denotes a 'hardlist', a
    ;list whose contents are fixed and not evaluated
    
    if repcount = 1 [control 1 [setmodel "head]]
    ;if this is the first segment, give it the 'head' model
    else [control repcount [setmodel "ball]]
    ;otherwise, give it the 'ball' (segment) model
  ]
  
  ;set up the 'apple' turtle:
  nameturtle "apple
  control "apple [
    penup setmodel "apple showturtle
    
    dountil (count near position 5) = 1 [
      setpos {-180 + (10 * random 36) -100 + (10 * random 20)}
    ]
  ]
  ;set a random position until such times as the apple is not near
  ;any snake segments (turtles)
  
  ;leading parameters used in comparison operators (=, >, < etc)
  ;must be enclosed in {}'s if more than a single value or primitive
  ;because the parser evaluates right to left
  
  ;the main loop:
  
  forever [
    norender
    ;don't render while we shuffle the snake forward
    
    for {"i 1 :length} [
      make "move item :i :queue
      ;set the move variable to the :i item in the :queue
      
      switch "move
      case "fd [ask :i [forward 10]]
      case "rt [ask :i [right 90 forward 10]]
      case "lt [ask :i [left 90 forward 10]]
      ;switch / case statements execute instructions based on the
      ;contents of the container (variable) provided to switch
      
      ;ask creates a new worker that executes the commands using
      ;the specified turtle (the turtle named by :i)
      ;so, in this for loop we are iterating through the :queue,
      ;moving the turtle segments based on the item present in
      ;each space in the queue (fd, rt or lt)
    ]
    while workers != [] [sleep 1]
    ;sleep execution until the ask workers finish moving their
    ;turtles (the snake). This way, when we enable the render,
    ;we won't catch the snake mid-move.
    
    render
    ;enable rendering again
    
    wait 40 - :score
    ;wait units are 60ths of a second. The higher the score, the
    ;shorter the wait will be
    
    ;user control:
    if keyp [
      ;did the user press a key? If so, do this stuff:
      
      make "input readchar
      ;take the output from the readchar primitive and place it
      ;into the input container. keyp indicates if there is a character
      ;already in the keyboard buffer. If readchar is called when there
      ;is no character in the buffer, it waits.
      
      switch "input
      case "j [push "lt "queue]
      ;if the user pressed the j key, push lt to the front of the queue
      case "k [push "rt "queue]
      ;if the user pressed the k key, push rt to the front of the queue
      otherwise [push "fd "queue]
      ;if no case has been satisfied since switch (the user pressed a key
      ;other than j or k), push fd to the front of the queue
    ]
    else [push "fd "queue]
    ;similarly, if the user hasn't pressed any key, push fd to the front
    ;of the queue
    
    ignore dequeue "queue
    ;ignore (drop) the last value off the end of the :queue
    ;dequeue would ordinarily return this value, ignore sends it into
    ;the ether
    
    ;is there something occupying the same space as the head of the snake?
    if (count (near 1:position 5)) > 1 [
      ;near returns the names of the turtles within the specified distance (5)
      ;count returns the count of the number of turtles returned by near
      ;if there is more than one (the head itself), we will execute the following:
      
      if memberp "apple near 1:position 5 [
        ;is it an apple?
        
        playsound "chomp
        inc "score
        ;increment the value in the score container by one
        
        (print |Yum! Score:| :score)
        ;to pass more than the default number of parameters to a primitive, wrap
        ;it in parentheses (round brackets)
        
        dountil (count near apple:position 5) = 1 [
          control "apple [setpos {-180 + (10 * random 36) -100 + (10 * random 20)}]
        ]
        ;place a new apple
        
        repeat 2 + int (:score / 10) [
          
          queue "pa "queue
          ;a pa or 'pass' will cause these new segments not to move on the next
          ;iteration, until movement commands are 'shuffled' on to their places
          ;in the queue
          
          inc "length
          ;increment the value of the length container by one
          
          nameturtle :length
          ;create a new turtle with the name contained in :length (the new length)
          
          (control :length {"hideturtle "setmodel ""ball "wrap "penup "setpos
          "query :length - 1 leftbracket "position rightbracket "setvectors
          "query :length - 1 leftbracket "vectors rightbracket})
          ;set up the new turtle and make sure it's positioned next to the
          ;previously last turtle, and is facing in the same direction
          
          control :length [setbounds [-185 -115 185 115] showturtle]
          ;set the bounds of the new turtle and show it
          
          wait 10
        ]
        ;add new segment to the snake
        
      ]
      
      ;and if it's not an apple?
      else [
        print "crash! playsound "crash finish
      ]
      ;game over, man! game over!
    ]
  ]
END