Introduction: Arduino + TFT --> Turtle Graphics
- The inspiration for this Instructable came from memories of the turtle graphics that were a component of TI Logo, a program that ran on the TI 99 4A home computer. And it came from the graphics capabilities of the Adafruit 2.8" TFT shield. Put one on your Arduino Uno, and let's see what happens. When you have downloaded the sketch that comes with this Instructable, you will have a pocket-sized implementation of turtle graphics on your Uno!
What you need for this Instructable
- Arduino Uno
- Adafruit 2.8 " TFT shield.
Assemble the shield on the Arduino, and you are ready to begin.
A note about the pictures: sometimes it can be tricky to get good pictures of the TFT screen. The screen resolution is bright and clear - better than the pictures you see here.
Step 1: Download the Files
The files you need are available on this page. They are:
- TURTLEs.ino
- turtleshapes.h
- Turtle.h
- Turtle.cpp
TURTLEs.ino is the main sketch. Install it using your Arduino IDE; then open tabs for each of the other 3 files, turtleshapes.h, Turtle.h, and Turtle.cpp .
Copy the contents of these three files into their respective tabs.
Turtle.h and Turtle.cpp are the files that provide the turtle graphics methods - the ones that allow the turtle to draw on the screen. turtleshapes.h is a file containing data that allows us to draw the turtle graphic that is used on the opening and closing screens.
Put TURTLEs.ino in your Arduino sketchbook, open the TURTLEs.ino file and then open a separate tab for each of the Turtle.h, Turtle.cpp, and turtleshapes.h files. Copy and paste the contents of these files into their respective tabs.
Now you are ready to begin using turtle graphics commands.Several examples using turtle graphics are included with TURTLEs.ino. When you run the sketch, you will see a graphics demonstration, including some attractive graphics patterns, some basic animation, and some examples using the powerful technique of recursion. The last part of the main sketch, TURTLEs.ino , contains the functions that draw the graphics examples. These example functions are not necessary for you to write turtle graphics routines of your own, so you can delete these functions if you like - or adapt them to draw something very different. When you are starting it is recommended that you try the provided functions with different values - that will be a shortcut to your understanding of turtle behaviour.
Notes: The TURTLEs.ino sketch is liberally commented for the purpose of this Instructable. You can refer to the code to see how the sample graphics were drawn.
Step 2: Introduction to Turtle Graphics
Turtle graphics uses the concept of a 'turtle' as a drawing point (it is similar to a screen cursor) for drawing graphics on a computer screen. Think of a turtle holding a pen, and give it commands to tell it where to move on the screen. If the turtle's pen is up, the turtle moves without drawing. If the pen is down on the drawing surface the turtle draws a line as it moves.
Turtle graphics have been used in versions of the Logo programming language, often to introduce computer concepts to children. Find out more about turtle graphics here: https://en.wikipedia.org/wiki/Turtle_graphics
and about the Logo programming language here: https://en.wikipedia.org/wiki/Logo_(programming_language)
The TI Logo program (the one I am familiar with) came with an excellent training manual written by Harold Abelson; he also wrote versions of the manual for the Mac and Apple II Logo programs. These implementations introduced many of us to the power and utility of the Logo language, including its turtle graphics component. The instructions in Mr. Abelson's manual go far beyond the scope of this Instructable - you would find it very helpful to consult the manual and learn more. Mr. Abelson implemented the first turtle graphics system in 1969. Before that, Logo had a physical turtle (robot) that children could ride on.
Here's an example of how you can make the turtle draw. Tell the turtle to move forward 10 units (the units are screen pixels) and right 90 degrees - you have just drawn a line and turned 90 degrees to the right and the turtle is waiting for its next move. If you do that 4 times, you will have drawn a small square. If you move forward 10 units and turn right 120 degrees you have the start of a small triangle. Do it three times and the figure is complete.
Notice in these examples that when the turtle turns a total of 360 degrees while drawing (90 x 4 or 120 x 3) it will complete a closed figure. To complete a 10-sided figure, turn 36 degrees 10 times. These examples are quite simple. But if you repeat them with some variation - such as turning a few degrees to the right each time the figure is drawn - you will have discovered a simple means of drawing some surprisingly complex patterns. For instance, draw a square, then turn right 40 degrees and do this 9 times. And you might try drawing each of the squares with a different colour.
Step 3: How to Talk to a Turtle
To learn how to use Arduino turtle graphics first learn how to talk to the turtle
The 'turtle' is a computer object created in this Arduino program. It represents the current drawing point for turtle graphics drawing.
Any time you address the turtle object, use the prefix 't.' :
t.forward(40);
t.home();
t.penDown();
t.left(20);
Note: we use the t. (t with a period) to address the turtle object. The semicolon after each line is the normal practice for Arduino sketches.
Where do you put your turtle graphics commands?
Right where you usually put instructions in your Arduino sketches. If you want to draw a shape once, put the turtle graphics commands to draw that shape in the setup() function in your Arduino sketch. If you want to draw a shape over and over (not sure why you would want to, but you might), put the turtle graphics commands in the loop() function in your Arduino sketch.
When you write your own functions that use turtle graphics commands, place them outside the setup() and loop() functions of your Arduino sketch. (In this Instructable you will find the example functions at the end of the sketch - after the loop() function. It seems tidier to keep them there.)
Is there a special library of turtle graphics commands?
Yes. The turtle graphicsmethods are described in the turtle.h header file that defines the methods that will be used, and the turtle.cpp file implements these methods. When you #include the Turtle.h file, you can use turtle graphics.
Step 4: Start With a Line
Here is how to draw a line on the TFT screen:
t.penDown();
t.forward(50);
How long is this line?
This command draws a 50 pixel line on the TFT screen.
Where does it draw?
The drawing starts at the turtle's current position.
In what direction does it draw the line?
It draws in the turtle's current direction. You can change the direction if you wish; for instance tell the turtle to turn right 90 degrees before moving forward. (Use this command: t.right(90); )
What happens if the turtle's pen is not down?
In the above example if the pen was not down the turtle would have moved 50 pixels without drawing.
Here is how to draw the pattern of lines in the photo above:
The screen picture consists of 90 lines, each drawn starting at screen position (160, 240) - which is the point at the bottom middle of the screen. Here is the function that drew the pattern of lines:
for (int i = 360; i > 0; i -=4) // a loop to draw 360 /4 = 90 lines
{
t.moveTo(160, 240); // turtle moves to bottom centre of the screen
t.setHeading(i); // the angle for drawing the lines is decremented by 4 for each line drawn
t.forward(230); // draws a 230 pixel line
}
(This is an example included with the TURTLEs.ino sketch.)
Step 5: Draw a Square
The second picture shows a square that is one of the examples provided with TURTLEs.ino. You will have to run the sketch to see how the colours change and give the impression of a neon sign.
1. Let's draw a square
Enter the following commands in your Arduino setup() function:
t.forward(50);
t.right(90);
t.forward(50);
t.right(90);
t.forward(50);
t.right(90);
t.forward(50);
2. Is there an easier way to draw a square?
Yes - put repeated commands inside a loop.
for(int i = 0; i < 4; i++)
{
t.forward(50);
t.right(90);
}
3. An even easier way?
Yes - use the t.polygon function:
t.polygon(4, 50); // this draws a 4-sided polygon, with sides of 50 pixels.
Step 6: Three Ways to Draw Circles
How to draw a circle:
These photos of the TFT screen show examples of circles drawn using turtle graphics.
One way to draw a circle:
for(int i = 0; i < 360; i++)
{
t.forward(2);
t.right(1);
}
Note: this instructs the turtle to draw a line 2 pixels long and then turn to the right 1 degree - and do this 360 times. That completes a circle.
An easier way to draw a circle:
t.polygon(360, 2);
Note: the polygon method takes 2 parameters: number of sides, and length of each side. A polygon with 360 sides will look like a circle.
A better way to draw a circle:
t.arcRight(300, 360);
Note: the arcRight command takes 2 parameters: length (i.e. circumference) of the arc, and degrees of the arc. An arc of 360 degrees will describe a circle. An arc of 180 degrees will draw a half circle.
The example included with TURTLEs.ino draws the pattern of circles seen in the last picture.
Step 7: How to Use Colours
Change the turtle's pen colour or the screen colour or the text colour
This picture of text on the screen shows the colours available when you are using turtle graphics. Here the methods that you can use to set these colours:
t.setPenColor("pink");
t.setPenColor("green");
t.setScreenColor("blue");
Note: setScreenColor(" "); fills the screen with the specified colour. This covers anything that was drawn on the screen - in effect erasing the screen. If you want to erase a picture element and leave the rest of the screen as it is, re-draw that element using the background colour. (You will find examples in the Step of this Instructable that deals with animation.)
t.setTextColor("brown");
We haven't spoken about text yet.The command to put text on the screen is t.write("text"). By specifying t.setTextColor("brown"); your text will appear in brown colour.
Notice that when we tell the turtle to use a word value (also known as a string value), such as "pink", or "text", these values are placed inside quotation marks. When we tell the turtle to use a numeric value we do not use the quotation marks.
Here are the colours available for use in turtle graphics:
- "green"
- "forest"
- "olive"
- "teal"
- "cyan"
- "blue"
- "navy"
- "magenta"
- "purple"
- "maroon"
- "mauve"
- "brown"
- "beige"
- "gold"
- "orange"
- "yellow"
- "pink"
- "red"
- "white"
- "silver"
- "gray"
- "black"
The colours example shown in the above screen photo is included with TURTLEs.ino.
Step 8: Turtle Graphics Methods and How to Use Them
The above picture uses the t.setHeading(degrees) method as an example of how to use turtle graphics methods. Below you will find a list of all turtle graphics methods that you can use in your sketches.
Important: we use the prefix "t." to address the turtle object. We use quotation marks for string parameters in parentheses; and no quotation marks for numbers or numeric variables in parentheses.
Some methods, such as t.penUp(); and t.clear(); do not accept a parameter. Type these methods with empty parentheses. Some methods, such as t.forward(); and t.back(); require a value in parentheses - so you can tell the turtle how far to travel. Others require multiple parameters, such as t.moveTo(x, y) - in this example you specify both an x-coordinate and a y-coordinate that the turtle will move to.
Here are the turtle graphics methods, with comments added about how to use these methods.
t.forward(distance); // the turtle will move forward the specified distance. The distance is in pixels. If the pen is up, the turtle moves without drawing. If the pen is down, the turtle will draw a straight line as it moves. Note that the turtle remembers the direction it is moving in and it continues in that direction unless it is told to move in another direction.
You can change direction with these methods: t.back(), t.setHeading(), t.right(), t.left(), t.arcRight(), t.arcLeft(). (See below.)
t.back(distance); // similar to t.forward, above, but with t.back(distance) the turtle moves backward.
t.right(degrees); // turns the turtle the specified number of degrees to the right.
t.left(degrees); // turns the turtle the specified number of degrees to the left
t.penUp(); // with its penUp() the turtle will move without drawing
t.penDown(); // with its penDown() the turtle draws as it moves
t.setPenColor("color"); // for this and the t.setScreenColor() and t.setTextColor() methods you can specify any of the colours (see list in previous step of this Instructable). Remember that the colour name is placed inside quotation marks: t.setPenColor("pink"); .
t.setScreenColor("color"); // sets the screen colour. This has the effect of clearing the screen and changing the screen colour.
t.moveTo(x, y); // the TFT screen has 320 x-axis positions and 240 y-axis positions. You can move the turtle to any place on the screen by specifying its x and y position. For instance t.moveTo(0, 0); moves the turtle to the top left corner.
t.home(); // moves the turtle to the centre of screen - which has screen coordinates (160, 120).
t.setHeading(degrees); // you can specify a degree heading from 0 to 360 degrees. 0 is the initial heading, which points to the right side of the screen. 90 will point the turtle to the top of the screen, etc.
t.clear(); // clears the screen by filling it with the current ScreenColor.
t.write("text"); // remember to use quotation marks for words inside parentheses. The turtle will write its text adjacent to the previous text written on the screen. You can also specify where the turtle will write - see t.setTextLocation( ); below.
t.setTextSize(size); // used to change the size of text displayed on the screen. The range is from t.setTextSize(1); which is smallest, to t.setTextSize(4); which is largest.
t.setTextColor("color"); // specify one of the colour names to change the text colour.
t.setTextLocation(x, y); // indicates the screen location where text will next be written.
t.polygon(sides, sideLength); // you can write your own polygon functions if you wish, but this method is a convenient way to draw polygons with equal side length. Specify the number of sides (e.g. 3 will draw an equilateral triangle, 4 will draw a square), and the length of the sides in pixels.
t.star(sides, sideLength, angle); // for drawing a star shape, specify the number of sides (star points) for the star, the length of its side in pixels, and the angle, which determines the star's 'pointiness'.
t.arcRight(arcLength, degrees); // draws an arc of specified length curving to the right. To draw a circle, specify an arc of 360 degrees. For a circle, the arc length will be its circumference.
t.arcLeft(arcLength, degrees); // similar to t.arcRight, but draws an arc curving to the left.
Step 9: Writing Your Own Turtle Graphics Functions.
When you write your own functions they must refer to the turtle object in the function definition as in this example:
void simpleCar(Turtle &t)
The parameter inside the parentheses invokes the turtle object.
The car is placed on the screen with the function simpleCar(t);
This calls the 'simpleCar' function, and specifies that it will use the turtle object named 't'.
This might sound complicated at first, but it boils down to this:
- when you write a function that uses turtle graphics, write (Turtle &t) as the first parameter of the function.
- when you call your function, write (t) as the first parameter of the function call.
The 'Turtle &t' in the function definition means that the function uses the turtle object. Similarly, the 't' in the function call indicates that the function uses the turtle object.
Let's write a function that draws a car. It will be simpler than the car in the later Step dealing with animation, because that car's a bit complicated to draw. Here's the simple version shown above:
void simpleCar(Turtle &t)
{
t.setHeading(90);
t.arcRight(200, 180); // length of arc and degrees = top of car
t.right(90);
t.forward(20); // bottom front of car
t.left(90);
t.arcRight(150, 540); // draw a wheel
t.left(90);
t.forward(30); // middle bottom of car
t.left(90);
t.arcRight(150, 540); // draw another wheel
t.left(90);
t.forward(10); // that's the rest of the bottom of the car - done!
}
Save your function at the end of the TURTLEs.ino sketch.
Here's how to call this function. Put the following 3 lines in the setup() part of the TURTLEs.ino sketch:
t.moveTo(100, 200); // move to bottom of screen - or wherever you'd like to draw the car
t.setPenColor("red"); // - or whatever colour you'd like the car to be
simpleCar(t); // that will draw the car.
Your car might be pointing the wrong way, but you could use t.setHeading() to fix that.
Step 10: Patterns Using Turtle Methods
In this example we use t.arcRight() and t.arcLeft() to show how the turtle methods can easily draw patterns.
This screen picture shows what you see in the Arcs example that is included with TURTLEs.ino. The example consists of 37 lines of arcs that are drawn at successive locations on the screen. Each line of arcs uses 4 separate arcs, t.arcRight(), t.arcLeft(), t.arcRight(), and t.arcLeft() .
This is the code used to draw this pattern:
for (int i = 0; i < 370; i += 10) // i is incremented by 10 to move 10 pixels to the right
{
t.moveTo(i, 22); // x-coordinate and y-coordinate starting point for each line of arcs
t.arcRight(90, 180); // draws semi-circular arc, 90 pixels long
t.arcLeft(90, 180); // draws another
t.arcRight(90, 180); // another
t.arcLeft(90, 180); // another
}
Step 11: Combine Multi-sided Figures to Make More Complex Shapes
Multi-sided figures are easy to draw with turtle graphics - use the t.polygon(sides, length); command to tell the turtle how many sides you want, and how long the sides are.
t.polygon(3, 120);
draws a triangle with each side 120 pixels long.
If you draw 10 figures and turn 36 degrees between each, that equals 360 degrees - a closed figure.
for (int i = 0; i < 10; i ++) // we will draw 10 polygons (in this case, triangles)
{
t.polygon(3, 120); // each is 3-sided, and has a side length of 120 pixels
t.right(36); // turn right 36 degrees
}
It is quite simple to repeat shapes at different angles to make more complex patterns. Here we have drawn 10 triangles and turned 36 degrees between each. 10 x 36 = 360 degrees - a closed figure.
E.g. 20 figures, and turn 18 degrees between each. Nine figures, and turn 40 degrees between each. Etc. Try some different combinations with the triangles example and see what results. You could draw 36 triangles, and turn 10 degrees between each. Also you can try changing the lengths of the sides and the number of sides. Your turtle is a talented artist and helpful assistant.
The TURTLEs.ino sketch includes examples drawn with triangles, hexagons, and decagons. (If you look at the code for the decagons function, you will see that we drew a lot of decagons to make the pattern for that example. When you use a program loop, it is as easy to draw hundreds as it is to draw just a few.)
You will see the values used as parameters for each of our example functions in the setup() portion of the TURTLEs.ino sketch.Try the examples with different values and see how the patterns change.
Step 12: Draw a Spiral
Example 9 shows how to draw a spiral:
for (int i = 4; i < 400; i += 4) // increment i by 4 pixels in each iteration
{
t.forward(i); // i is the length in pixels of each side of the figure
t.right(91); // the offset (91 degree turn and increasing the side length by 4 pixels) produces the spiral
}
Try it, and see how it changes if you specify a different value for the variable i that is used in the sketch. For instance, you could increment i by 2 instead of 4. Sometimes slight changes like these produce an effect that you will prefer. Or introduce colour changes with the t.setPenColor() method. The goal is to create a pattern that you find interesting. A spiral example is included with TURTLEs.ino.
Step 13: The Turtle Screen Is 320 Pixels Wide and 240 Pixels High
You can move to any place on the screen by specifying its location. The first number to specify is the position on the x-axis (the horizontal axis). The second is on the y-axis (the vertical axis).
Examples:
t.moveTo(0, 0); will move the turtle to the top left corner of the screen.
t.moveTo(0, 240); will move the turtle to the bottom left corner.
t.moveTo(0, 120); will move the turtle to the left side of the screen, half-way down the screen.
t.moveTo(320, 0); will move the turtle to the top right corner of the screen.
t.moveTo(320, 240); will move the turtle to the bottom right corner of the screen.
t.moveTo(160, 120); will move the turtle to the middle of the screen - but you can also use the command t.home(); to move to the same place.
Use these same coordinates to specify the location where text will begin. See next page for instructions on how to write text on the screen.
Step 14: Write Words on the Screen
This picture shows words placed at random locations. But you can specify exactly where you want the turtle to write text with the t.setTextLocation() command.
Here is an example of commands you can use to place text on the screen:
t.setTextLocation(0,0); // text will begin at the top left corner
t.write("Hello, world!");
Note that words placed inside parentheses, ("Hello, world!") are inside quotation marks, while numbers or numeric variables are not.
examples:
t.setTextLocation(0, 120) will set the text cursor to the left side, half-way down the screen.
t.setTextSize(1); - there are 4 options for text size, 1 to 4. 1 is smallest, 4 is largest
t.setTextColor("green");- The words in the picture on this page are written in different colours. You can use any of the turtle graphics colours for text colours.
Step 15: Basic Animation and More Complex Figures
To animate a figure:
- draw its shape
- draw over the previous figure using the background colour as the penColor - that erases the previous figure - and
- re-draw the figure using its original colour in a new position. This gives the illusion of movement.
We have included two examples that demonstrate this basic animation: example 8 - spinning star, and example 11 - cars.
Draw more complex figures:
The car example (example 10) shows a more complex figure drawn using turtle graphics. The instructions for this type of drawing take some time to write, but they are not especially complicated. Our car drawing consists of a series of simple shapes that comprise the different parts of a car. Each of these shapes is easy to draw with the t.forward(), t.arcRight(), and t.Right() methods. To draw the car we first draw a rectangle to represent the tailpipe, then an arc for the rear and roof of the car, a straight line for the windshield, and another line and an arc for the front hood. Lines draw the underside of the car and the running board, and arcs draw the wheels.
The car is placed on the screen with the function car(t, size);
The function allows us to specify a car size between 2 (tiny) and 11 (quite large).
A reminder: the 't' in the function call indicates that this function uses our turtle object. Any turtle object functions that you write must include t as the first function parameter.
Step 16: Recursive Drawing
Until now we have told the turtle where to draw and it has done what it was told - even with more complicated drawings such as the car example. But recursion gives the turtle a life of its own. You tell it what to do and then sit back and watch.
Here is a link to Wikipedia's discussion of recursion:
www.wikipedia.org/wiki/Recursion_(computer_science...
What is recursion? For a person writing Arduino turtle graphics functions, it's a different way of thinking. For the turtle that does the drawing, it is a measure of independence: "Just get me started and watch me go!"
Note: the Arduino Uno has limited SRAM memory, but still sufficient to use recursion in our turtle graphics examples. If you try to use too many recursive steps, your functions may not work. However we have not reached that limit in our examples, so let's go ahead and see how this works....
When we invoke recursion we use a function that calls itself. How do we do that? It's easiest to explain with a brief example that draws a V:
void vee(length); // if you run this example, try it with length of 80 pixels
{
t.left(45); // turns left 45 degrees
t.forward(length); // draws left arm of V with length specified - 80 pixels, for example
t.back(length); // returns to beginning point of V
t.right(90); turns right 90 degrees - which is 45 degrees to the right of the original orientation
t.forward(length); // draws right arm of V
t.back(length); // returns to starting point
t.left(45); // returns to original orientation
}
There - that draws a V. Now we are ready to add recursion. After the first line that states t.forward(length); add this line:
vee(length / 2); // this is recursion - a function calling itself!
You have drawn the left arm of the V and now you call the same function to draw another V that is half size. After that is completed, the initial function continues to draw the original, larger V. This is an interesting way of drawing something. It's actually an interesting way of thinking about how things can be drawn.
Now let's add a smaller V at the end of the right arm as well. After the second t.forward(length); add this line again:
vee(length / 2); // this is recursion - a function calling itself!
We are almost done, except for one thing - the function won't work the way it is - there is something to fix. Recursion needs a stop point, it won't continue indefinitely. (This the mistake I made when I first tried to draw a V using recursion - it didn't work for me and I had to get advice.) Add this stop routine at the beginning of the function:
if (length < 10)
{
return;
}
There. Now we have a stop point - when the arms of the V are reduced to less than 10 pixels (note that the length is halved each time the function is called) - the function stops.
Here is the complete recursive vee function:
int length = 80; // a variable that specifies the length of the V's arms
void vee(length); // names the function, with one parameter, which is the length of the V's sides
{
if (length < 10)
{
return; // this stops the recursion when length is less than 10 pixels!
}
t.left(45); // turns left 45 degrees
t.forward(length); // draws left arm of V with length of 80 pixels
vee(length / 2); // this is recursion - function calling itself to draw smaller V!
t.back(length); // returns to bottom of V
t.right(90); // turns right 45 degrees
t.forward(length); // draws right arm of V
vee(length / 2); // this is recursion again!
t.back(length); // returns to starting point
t.left(45); // returns to original orientation
}
Now you can try the function. Try it with different values for the length of the V - watch and see what happens!
EXAMPLE 1 - CONES
This example uses a function we have named rCones to draw two arcs, t.arcLeft() and t.arcRight(), each of 360 pixels in circumference. After drawing the first arc, the function calls itself (! - recursion) - and draws a smaller arc, 30 pixels shorter in circumference. This continues until the stop limit is reached, when the smaller arc is less than 100 pixels in circumference.
EXAMPLE 2 - A SMALL TREE
The rTree example function is similar to the V example above.
EXAMPLE 3 - A TREE WITH MORE BRANCHES
This uses the same rTree function used in example 2, but by specifying a longer branch size the function draws many more branches. It is interesting to see how much detail can be added by simply changing this one parameter.
EXAMPLE 4 - A MORE COMPLEX TREE
The principle is the same - but in this function we are able to specify different lengths of left branches and right branches, and different left angle and right angle for the branches. Also the shorter end branches are drawn a different colour. Our turtle is getting quite artistic. Depending on the values used, the resulting image can be sophisticated. Try some different branch lengths and colours and see what happens.
EXAMPLE 5 - RECURSIVE TRIANGLES
This example draws Sierpinski triangles. Find out more about them at Wikipedia:
Step 17: Try Some Puzzles
1. The hex puzzle
draw a row of hexagons adjacent to one another that go across the screen
Drawing a single hexagon is easy: you can use our polygon method as follows:
t.polygon(6, 50);
But how would you draw a row of adjacent hexagons? (Hints: 1. there are more than way to do this. 2. you do not need recursion - it's not that complicated. 3. This one was drawn without using the t.polygon method.)
2. The triangle puzzle
Similar to the hex puzzle, but fun as well: draw a row of triangles across the screen.
3. The squares puzzle
This will give some practice in recursion.
Good luck! Once you have found solutions to these puzzles you will be able to create puzzles of your own.
Step 18: The End
Acknowledgement
Thanks to Andrew Wendt, who knows how to do things & gave lots of help.