pythonturtle-graphicsl-systems

Tree generation using L-systems and Python turtle


I'm trying to generate a simple tree using an L-system. However, when I run the code that I have:

import turtle


def generate(length):
    sentence = "F"
    for j in range(1, length):
        for i in sentence:
            if i == "F":
                sentence += "FF+[+F-F-F]-[-F+F+F]"

        print(sentence)


    gen = turtle.Turtle()
    window = turtle.Screen()
    gen.left(90)

    for k in sentence:
        if k == "F":
            gen.forward(10)
        elif k == "+":
            gen.right(10)
        elif k == "-":
            gen.left(10)
        elif k == "[":
            location = gen.position()
        elif k == "]":
            gen.setposition(location)

I get a weird grain pattern in the turtle window:

turtle window

How can I produce the desired result?


Solution

  • Your += operation appends the application of the production rule to the string but the idea is to replace each occurrence of the "F" character with the result of the production rule.

    You can do this with string#replace, which replaces every occurrence of a substring. You used this before your edit, but didn't assign the return value of the function back to the original sentence variable (replace doesn't work in-place and strings are immutable).

    Either way, the extra inner loop is unnecessary.

    Another important point: the production rule is recursive, so a single location variable is insufficient storage to "remember" the sequence of pushes that might have been performed.

    To elaborate, here's an excerpt of your sentence variable after applying the production rule on multiple iterations:

    ...[-FF+[+F-F-F]-[-F+F+F]+...
    

    It's clear that this has two sequential [ operations before a ] is encountered. This nesting can grow arbitrarily deep. A stack data structure is the best way to keep track of the nested, recursive positions (location can only keep track of the last and state would be corrupted when two push operations occur in succession).

    Whenever a [ operation is encountered, push the position on the stack (represented in Python as a list which only uses append (for push) and pop without any arguments to remove the back of the list). Whenever a ] operation is encountered, pop the stack and set the turtle's position to that location.

    As a minor point, it's a good idea to keep hardcoded values out of the code; group all of your settings/config vars at the top of the block. If you are using a function, make these parameters.

    Here's a working version:

    import turtle
    
    generations = 3
    size = 10
    rule = ("F", "FF+[+F-F-F]-[-F+F+F]")
    sentence = "F"
    positions = []
    gen = turtle.Turtle()
    gen.speed(10)
    gen.left(90)
    
    for i in range(generations):
        sentence = sentence.replace(*rule)
    
    for op in sentence:
        if op == "F":
            gen.forward(size)
        elif op == "+":
            gen.right(size)
        elif op == "-":
            gen.left(size)
        elif op == "[":
            positions.append(gen.position())
        elif op == "]":
            gen.setposition(positions.pop())
    
    turtle.exitonclick()