Search code examples
pythonpython-3.xpython-turtlehilbert-curve

How can I make the most efficient Hilbert Curve with Turtle [Python 3]?


I'm trying to make a Hilbert Curve that takes as little time as possible to complete. Here is the code so far (adapted from Hilbert curve using turtle graphics and recursion)

from turtle import *
from win32api import GetSystemMetrics



def hilbert_curve(amt, facing, n, start_at_corner=True) -> None:
    if start_at_corner:
        ht()
        up()
        goto(x=(- (GetSystemMetrics(0) - 30) / 2), y=(- (GetSystemMetrics(1) / 2 - 50)))
        down()

    if n < 1:
        return

    try:    # Only here because I find error messages annoying
        left(facing * 90)
        hilbert_curve(amt, - facing, n - 1, False)
        fd(amt)
        right(facing * 90)
        hilbert_curve(amt, facing, n - 1, False)
        fd(amt)
        hilbert_curve(amt, facing, n - 1, False)
        right(facing * 90)
        fd(amt)
        hilbert_curve(amt, - facing, n - 1, False)
        left(facing * 90)

    except Terminator:
        from sys import exit
        exit()


screen = getscreen()
speed(0)
hilbert_curve(5, 1, 15)
screen.mainloop()

The issue with this is that the turtle makes a lot of unnecessary turns - at the start and at all connections - I understand why this happens but I don't know how to fix it.

If there are any other things I can change in above code to make the turtle faster, suggestions are welcome!


Solution

  • Short of rethinking the program's entire approach, here's a quick fix that provides noticeable speed up. We'll use tracer() and update() to precisely control the graphics. We don't want to hide any of the drawing (which is possible with tracer()) but rather only draw when there is a line to draw, treating all the turtle's turns as internal logic calculations, displaying only the final heading:

    from turtle import Screen, Turtle
    
    def hilbert_curve(distance, facing, n):
        if n < 1:
            return
    
        turtle.left(facing * 90)
        hilbert_curve(distance, -facing, n - 1)
        turtle.forward(distance)
        screen.update()
    
        turtle.right(facing * 90)
        hilbert_curve(distance, facing, n - 1)
        turtle.forward(distance)
        screen.update()
    
        hilbert_curve(distance, facing, n - 1)
        turtle.right(facing * 90)
        turtle.forward(distance)
        screen.update()
    
        hilbert_curve(distance, -facing, n - 1)
        turtle.left(facing * 90)
    
    screen = Screen()
    screen.tracer(False)
    
    turtle = Turtle()
    turtle.hideturtle()
    turtle.penup()
    turtle.goto(10 - screen.window_width()/2, 20 - screen.window_height()/2)
    turtle.pendown()
    
    hilbert_curve(5, 1, 15)
    
    screen.tracer(True)
    screen.mainloop()
    

    If, on the other hand, you don't care about watching the drawing and simply want to fill the plane as quickly as possible with a Hilbert curve, then remove the screen.update() calls from the above code and change this line:

    screen.tracer(False)
    

    to instead be:

    screen.tracer(1000)
    

    to fill the window with the fractal in a few seconds.