# Tag game

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.

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
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
;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

;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

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

showturtle
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
]
;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
```

#### Floaty Turtle: A Flappy Bird clone in Logo

Floaty Turtle is a simple clone of Flappy Bird written in the Logo programming language that turtleSpaces uses.

Open in the integrated development environment (IDE)

```NEWTURTLE "myrtle

TO floaty
;here is yet another example of a relatively complex
;game that can be rather simply implemented in Logo:
;a flappy bird clone!

;to make it work we use two turtles, one is the player
;and the other creates, moves and erases the pipes
;these are two seperate 'workers' or threads

;this procedure kicks off the game, displays a title
;screen, and then manages the player, processing
;input and moving the player appropriately

;the pipemaker turtle and its pipes procedure
;manages the pipes and checks for collisions

;the turtle does not move horizontally,

reset
cleartext
setbackgroundcolor pick [6 7 14 5 10 11 14 15]
;select a random background color from the given list

noaudiowait
;don't wait for audio to finish playing

setmodel [penup back 7 stamp "myrtle]
;we need to offset Myrtle's actual position more
;toward her center for the purposes of this game

setmodelscale 5
;make myrtle big!

playsound "doodoo
;startup sound

pipemaker:newworker [pipes]
;'kicks off' the pipemaker turtle, who
;creates and moves the pipes

;create the title screen:

penup
randomfillcolor
slideleft 180 forward 5
settypesize 60
pushturtle
;'push' the turtle's state (position etc) on
;to a stack, from which it can be 'popped'
;off later

typeset "Floaty
popturtle
;restore the previously pushed state

back 100
randfc
;short for randomfillcolor

pushturtle
typeset "Turtle
popturtle
bk 10 sr 70
;back and slideright

settypesize 10
randfc
typeset |Press any key to float!|
;title screen complete!

home
dn 90 rt 90
;down and right

make "raise 0
;the :raise container holds the current
;value remaining to float Myrtle upwards

forever [

if loopcount = 1 [say "Ready!]
if loopcount = 16 [say "Set!]
if loopcount = 31 [say "Go!]
;the loopcount is the number of times the forever
;loop has executed. Based on that count, say the

if divisorp 100 loopcount [
;every 100 loops pick a random background
setbg pick [6 7 14 5 10 11 14 15]
;from the given list
]
;setbg short for setbackground

dosleep [50] [
;try to maintain an average of 50ms to do
;the following:

if loopcount > 30 [
;if the loopcount is greater than 30:

if :raise = 0 [
;if the value inside the :raise container
;is 0, then lower the turtle 2.5 turtle-units:

lower 2.5
]
[
;otherwise raise the turtle 2.5 turtle-units
;and decrease the value inside the :raise
;container by 2.5
raise 2.5
make "raise :raise - 2.5
]

if keyp [
;if a key is pressed, play a sound,
;remove the key from the keyboard buffer
;and increase the value of the :raise
;container by 20:

playsound "air
clearchar
make "raise :raise + 20
]

clean
;remove the turtle's 'track' -- it's not
;drawing or creating anything so we don't
;need to have it piling up

]
]
]
END
```
```NEWTURTLE "pipemaker

TO pipes
;the pipemaker turtle's job is to
;create the pipes, shift them to the left
;and check if they've hit the turtle

clearscreen
noaudiowait
penup
home
slideright 250
back 120
up 90
;position the turtle appropriately for
;creating the pipes offscreen to the right

begintag "move
endtag
;this tag is used to shift the pipes,
;by replacing its contents with an ever-
;increasing slideleft directive

make "height 100
;this is the starting height of the pipes

make "heights [0 0 0 0 0]
;initialize an 'zeroes' list of pipe heights

make "score 0
make "count 0
;initialize the score and the pipe count

settypesize 20
;set the type size. Type is the text you
;create inside the 3D world

forever [
;do this forever:

sleep [50]
;try to do this stuff in an average of
;50ms -- less or more as needed to keep
;that average:

if divisorp 20 loopcount [
;every 20 loops:

inc "count
;increase the contents of the :count container
;by one

if loopcount > 60 [
;if the loopcount is greater than 60:
playsound "clang
inc "score
say :score
;make a sound, increment :score and say it
]

slideright 100

begintag loopcount
;create a new 'tag' for the pipe
;so we can remove it after it passes the
;left side of the screen

make "col 1 + random 5
;select a random number and put it in :col

setfillcolor item :col [12 9 7 6 11 14 10 13]
;set that color based on :col's index in the
;provided list

make "height :height + (-60 + random 120)
;increase or decrease :height based on a
;random number

if :height < 20 [make "height 30 + random 20] if :height > 120 [make "height 110 - random 20]
;if too high or low, pick a new value higher
;or lower as needed

queue :height "heights
;add the new height to the list of heights

;create the lower pipe:
cylinder 20 :height 50
lower :height
setfillcolor item :col [4 8 6 2 1 12 5 3]
;select the complementary color for the pipe

cylinder 25 20 50
lower 50

;type the pipe number:
down 90 right 90 back 10
if :count > 9 [bk 10]
inscribe :count
if :count > 9 [fd 10]
forward 10 left 90 up 90
;if gameplay is too slow, comment out
;the above five lines

;create the upper pipe:
lower 50
;remember, the turtle is upside down!
cylinder 25 20 50
lower 20
setfillcolor item :col [12 9 7 6 11 14 10 13]
cylinder 20 120 - ypos 50
raise :height + 120

endtag
;close the pipe 'tag'

if loopcount > 119 [erasetag loopcount - 100]
;erase the pipe that has left the screen

ignore pop "heights
;remove its height from the :heights container
;and just throw it away (ignore it)
]

if loopcount > 60 [
;check for crash:
if (or
myrtle:ypos > (-120 + (90 + item 3 :heights))
myrtle:ypos < (-120 + (20 + item 3 :heights))
;the third item in the :heights list is the
;height of the pipe around where the turtle
;is, so we can use that to see if we've hit
;anything
) [
(print |Crash! Final Score: | :score)
print |Press flag icon to play again!|

calm "myrtle
myrtle:run [setfillcolor 1 icosphere 30]
;red ball

audiowait
playsound "crack
playsound "aw

myrtle:clean
;gets rid of the red ball

noaudiowait
playsound "fall

myrtle:repeat 90 [
forward 3 lower 3 wait 1
]
;fall to the ground

audiowait
playsound "crash
finish
;that's all folks

]
]

replacetag "move [slideleft loopcount * 5]
;increase the value of the slideleft
;command in the move tag, shifting the
;pipes to the left

]

;and that's it! Not really much code, is it?

END
```