Turtle Basic 2019
A very useful, advanced, early book is “Turtle Geometry” by Harold Abelson and Andrea diSessa, two MIT scientists, published in 1980. The MIT group had been working 10 years or more at that time with Turtle geometry, Logo and Lisp. The book is written with emphasis on the mathematical ideas, not any specific implementation. The ideas are just as valid now as then. We can use their substantial, challenging concepts and implement them 40 years later. Our examples can go beyond what they show, because our tools are so much better.
I am doing a turtle graphics implementation using Autocad, creating a turtle class in excel VBA. Excel is simply providing the VBA editor. The principles and code would would work just as well in dot net, and identically in vba native in autocad or bricscad.
Position and Heading are the two basic turtle properties. Heading will be in degrees, between 0 and 360, not including 360. VBA and autocad demand radians. Our subs will do the conversion. Position consists of variables X and Y (and Z when the turtle sprouts wings some day). We will use type Double for all.
Class variables are either Private or Public. If a variable is Public it can be set directly. That should do for X and Y. If Heading is public, then Turtle.Heading = 361 is possible. We want to convert values to between 0 and 359.99. Get and Let manage Private class variables. These can be named anything you want but they cannot have the same name as the variable they control.
Basic turtle commands are FORWARD #, BACK #, RIGHT # and LEFT #. These are methods or procedures that change Position or Heading properties. PENUP and PENDOWN change a boolean PEN variable.
In a Class Module, not a standard code module –
Public x1 As Double Public y1 As Double Public x2 As Double Public y2 As Double Private pheading As Double Public pen As Boolean
Current Position is x1, y1. The other endpoint of the line the turtle is going to draw is x2, y2. The heading is private. All others are public.
Insert Procedure Property from the VBA pulldown will insert a stub procedure to get a start on controlling a private variable. These normally have the same name. You would use the same name to read and write, which is how every property works. But lets use Heading for the read property and SetHeading for the write property. So instead of something like
Turtle1.heading = turtle1.heading + 45
Turtle1.setheading = turtle1.heading + 45
In the class module is the Let, the Get, and the convertang function. The only way to set the Heading is thru Let which converts every angle to a value between 0 and 360, no matter how many consecutive turns or negative values added.
Public Property Get heading() As Double heading = pheading End Property Public Property Let setheading(ByVal ang As Double) pheading = convertang(ang) End Property
wordpress is really hard to deal with, it thinks these inequality signs are html tags
The basic line drawing function is FORWARD #. Position and Heading are known. The only value required is distance. The other end of the line, x2, y2, has to be calculated with Cos and Sin, which require radians, not degrees. The value of the Pen boolean is checked to see if the line is to be drawn. The line is drawn with normal autocad vba methods. Finally the endpoint x2,y2 is made the new current position.
Public Sub forward(dist As Double) 'assumes x1 y1 and heading x2 = x1 + dist * Cos(ang2rad(pheading)) y2 = y1 + dist * Sin(ang2rad(pheading)) If pen Then Call drawline(x1, y1, x2, y2) End If 'updates to new position x1 = x2 y1 = y2 End Sub Sub drawline(x1 As Double, y1 As Double, x2 As Double, y2 As Double) 'internal sub to draw line Dim acadline As acadline Dim pt1(0 To 2) As Double Dim pt2(0 To 2) As Double pt1(0) = x1: pt1(1) = y1: pt1(2) = 0 pt2(0) = x2: pt2(1) = y2: pt2(2) = 0 Set acadline = acadDoc.ModelSpace.AddLine(pt1, pt2) Update End Sub
Since this is autocad, or something very much like it, we could definitely have turtle properties for line width, color and type. But we will save those for later.
Back is the same as Forward, except we add 180 to the direction, but we do not change the Heading variable.
x2 = x1 + dist * Cos(ang2rad(pheading + 180)) y2 = y1 + dist * Sin(ang2rad(pheading + 180))
Changing Heading with Left # and Right #
Public Sub left(ang As Double) setheading = pheading + ang End Sub Public Sub right(ang As Double) setheading = pheading - ang End Sub
The degree to radian utility and Penup / Pendown
Function ang2rad(ang As Double) As Double ang2rad = ang * Pi / 180 End Function Public Sub penup() pen = False End Sub Public Sub pendown() pen = True End Sub
In a standard code module, the class has to be instantiated, that means a new specific turtle object has to be created using the class template. If CTurtle is the name of the class module, then –
Dim turtle1 As CTurtle Set turtle1 = New CTurtle
The variables are set with default value zero, so that debug.print turtle1.x1 would print 0. In the class module you can set initial values.
Private Sub Class_Initialize() pen = True pheading = 90 'traditional turtle direction End Sub
To begin drawing, lets start with a basic polygon. An interior angle of an equilateral triangle is 60 degrees. The exterior angle is 120 degrees. The exterior angle is the angle you would turn if you were walking the lines of the triangle. Its the continuation of the line you are on past the vertex onto the next line. Its “how much the turtle must turn in drawing the vertex” (p.7 – Abelson).
Sub turtle_triangle() connect_acad Dim i As Integer Dim turtle As CTurtle Set turtle = New CTurtle turtle.setheading = 60 For i = 1 To 3 turtle.fd 10 turtle.right 120 Next i End Sub
In practice to save a little typing, I will do some of the declarations in public and subs.
Sub turtle_triangle() init_turtle turtle1.setheading = 60 For i = 1 To 3 turtle1.fd 10 turtle1.right 120 Next i End Sub
To generalize a polygon, the total exterior angles sum up to 360 – the turtle makes one full turn to come back to its initial heading. The simplest polygon, or N-gon where N stands for number of sides, would have one degree turns and have 360 sides.
Sub turtle_polygon(num_sides As Integer, angle As Double, len_side As Double) init_turtle For i = 1 To num_sides turtle1.fd len_side turtle1.right angle Next i End Sub
This has to be called by something with arguments. You have to know what the arguments are to get good regular polygons. I am sticking with double as type for angle, not integer. Num_sides has to be integer, because a half side is not realistic, and also num_sides is used as a counter in a for loop. A double actually would run there without error, but sooner or later you would miss your expected result by one item due to a rounding error.
Here is a demo which makes a variety of polygons.
Sub polygon_demo() turtle_polygon 3, 120, 1 turtle_polygon 4, 90, 2 turtle_polygon 5, 72, 3 turtle_polygon 6, 60, 4 turtle_polygon 7, (360 / 7), 5 turtle_polygon 8, 45, 6 turtle_polygon 9, 40, 7 turtle_polygon 10, 36, 8 turtle_polygon 11, (360 / 11), 9 turtle_polygon 12, 30, 10 End Sub
Given the number of sides, and given the fact that the exterior angles always add up to 360, the turn angle is just 360 / num_sides. In the case of 7 and 11 sides, the angle is not an integer. Its a repeating decimal. I let vba calculate the number and feed whatever precision it wants to autocad, and it closes the polygon fine.
Since the angle is calculated we could put that into the sub and take it out of the arguments list. We could do it the other way, calculate the number of sides from the angle, but thats trickier because num_sides is an integer and we might not always get an integer from a division.
So we can calculate angle, take it out of the argument list, and get exactly the same results.
Sub turtle_polygon2(num_sides As Integer, len_side As Double) init_turtle Dim angle As Double angle = 360 / num_sides For i = 1 To num_sides turtle1.fd len_side turtle1.right angle Next i End Sub
But what about this idea of knowing the angle and not knowing the number of sides? The polygon is done when the turtle turns exactly 360 degrees. If the angle is a double though, some angles are not multiples of 360. If we are not going to control the loop with the number of sides as a counter, we need a way to stop when the turtle returns to its original heading and an emergency stop in case it never does. Using doubles for heading, there is no guarantee it will ever exactly be 360 degrees once it starts, due to double rounding imprecision.
Here is a Do Loop where the test is at the end of the loop, so heading starts out zero, or whatever, it adds one increment, then it checks before running the loop again. In addition I have an emergency counter to stay out of an infinite loop. The parameters are length of the side and angle. No opinion expressed about how many sides there will be. In practice it turns out and is logical, if the angle is an integer, the maximum number of sides will be 360.
Angle 45 should give us an octagon, and it does, with 8 lines.
Sub do_poly2() connect_acad Set turtle1 = New CTurtle poly_demo2 1, 45 End Sub
90 gives a square, 120 a triangle, 36 gives a 10-gon. What if I try to draw a 7-gon? First lets let VBA do the math.
poly_demo2 1, (360 / 7)
Looks like it works fine, but when I list all the lines, I get 201 (or whatever my max counter is). They are on top of one another, I dont see any separation, I dont see any difference in the listing, 8 decimal places, but there is a difference out there somewhere. It looks like a septagon. It appeared to work before because we were controlling the number of sides, and here we are not.
We knew there would be non-closing figures. Any angle, integer or not, that is not a multiple of 360 is not going to give a closed polygon. For any angle that is a multiple of 360 we get a simple convex polygon. Some angles are not multiple of 360 but a multiple of 360 times an integer. For instance, 108 * 10 = 360 * 3. If we input 108 the figure goes all the way around 3 times before the heading is exactly zero again, and it draws a total of 10 lines.
poly_demo2 1, 108