在 Turtle 中绘制分形正方形图案

Drawing fractal square pattern in Turtle

我正在尝试让方块看起来像:

但我的代码是绘图:

我不知道我做错了什么,或者我的整个方法是否错误。

代码如下:

import turtle as tt
def recurse(depth, size):
    if depth==0:
        pass
    else:
        if depth%2==0:
            tt.pencolor('blue')
        else:
            tt.color('orange')
        tt.fd(size)
        tt.left(90)
        tt.fd(size)
        tt.left(90)
        tt.fd(size)
        tt.left(90)
        tt.fd(size)
        tt.left(90)
        tt.fd(size)
        tt.left(90)
        tt.fd(size)
        tt.right(90)
        recurse(depth - 1, size / 3)
        tt.penup()
        tt.bk(size)
        tt.pendown()
        recurse(depth-1, size/3)
        tt.penup()

        tt.left(90)

        tt.back(size)
        tt.right(90)
        tt.back(size)
        tt.pendown()

recurse( 4, 100 )
tt.done()

右上角的小方块尺寸不对,左边的乌龟向前移动太多了。

如何编写递归来生成正确的左上角方块?

很好的尝试!我会建议一种稍微不同的方法,为您的方形绘图函数添加 xy 坐标,并使用 t.goto(x, y) 重新定位海龟。这些坐标表示应绘制正方形的左下角,省去您手动移动乌龟的麻烦(尽管技术上可行,但不太干净)。

绘制完一个正方形后,乌龟将始终面向右并准备绘制下一个正方形,因此移动命令保持在最低限度。剩下的就是找出每个角的原点坐标。

对于右上角,很简单:x + sizey + size。对于左上角,它是类似的:仍然是 y + size,但是使用 x - size_of_smaller_squarex 轴偏移正确的量。如果您好奇的话,我还包括了左下角和右下角。

import turtle as t 

def draw_square(depth, size, x=0, y=0, shrink_by=3):
    if depth <= 0:
        return

    t.penup()
    t.goto(x, y)
    t.color(("blue", "orange")[depth%2])
    t.pendown()

    for _ in range(4):
        t.forward(size)
        t.left(90)

    smaller_size = size / shrink_by
    draw_square(depth - 1, smaller_size, x + size, y + size)
    draw_square(depth - 1, smaller_size, x - smaller_size, y + size)
    #draw_square(depth - 1, smaller_size, x - smaller_size, y - smaller_size)
    #draw_square(depth - 1, smaller_size, x + size, y - smaller_size)

if __name__ == "__main__":
    t.speed("fastest")
    draw_square(depth=4, size=100)
    t.exitonclick()

您提到 goto 是被禁止的。您可以遵循保证有效的机械策略:在每次递归调用结束时,始终将乌龟准确地放回到它开始的位置(相同的位置和方向)。这尊重递归的自相似结构。每帧的高级方法是:

  1. 绘制当前框

  2. 每个子框:

    1. 将乌龟移动到正确的位置和方向绘制子框
    2. 生成递归调用
    3. 撤消您在第 3 步中所做的所有动作

这是该策略的正确但冗长且草率的实施:

import turtle as t 

def draw_square(depth, size, shrink_by=3):
    if depth <= 0:
        return

    # draw this box
    t.color(("blue", "orange")[depth%2])
    t.pendown()

    for _ in range(4):
        t.forward(size)
        t.left(90)
    
    t.penup()
    smaller_size = size / shrink_by

    # put the turtle in the top-right facing east and spawn a child
    t.forward(size)
    t.left(90)
    t.forward(size)
    t.right(90)
    draw_square(depth - 1, smaller_size)

    # undo the moves
    t.right(90)
    t.forward(size)
    t.left(90)
    t.backward(size)

    # put the turtle in the top-left facing east and spawn a child
    t.left(90)
    t.forward(size)
    t.right(90)
    t.backward(smaller_size)
    draw_square(depth - 1, smaller_size)

    # undo the moves
    t.forward(smaller_size)
    t.right(90)
    t.forward(size)
    t.left(90)

if __name__ == "__main__":
    t.speed("fastest")
    draw_square(depth=4, size=100)
    t.exitonclick()

虽然这有效,但您可以看到可以消除一些多余的移动,同时仍然保留 属性 乌龟将始终在它们开始时开始的相同位置和方向上结束递归函数。重写:

import turtle as t 

def draw_square(depth, size, shrink_by=3):
    if depth <= 0:
        return

    # draw this box
    t.color(("blue", "orange")[depth%2])
    t.pendown()

    for _ in range(4):
        t.forward(size)
        t.left(90)
    
    t.penup()
    smaller_size = size / shrink_by

    # top-right
    t.forward(size)
    t.left(90)
    t.forward(size)
    t.right(90)
    draw_square(depth - 1, smaller_size)

    # top-left
    t.backward(size + smaller_size)
    draw_square(depth - 1, smaller_size)

    # undo all of the moves to reset the turtle state
    t.forward(smaller_size)
    t.right(90)
    t.forward(size)
    t.left(90)

if __name__ == "__main__":
    t.speed("fastest")
    draw_square(depth=4, size=100)
    t.exitonclick()

这可以通过尝试找到模式并将它们变成循环来变得更清晰;例如,如果您不介意在绘制父框的过程中绘制子项,则可以跳过中间动作。此代码绘制所有 4 个角,但您可以尝试仅将其调整为前 2 个角:

import turtle as t 

def draw_square(depth, size, shrink_by=3):
    if depth <= 0:
        return

    for _ in range(4):
        t.color(("blue", "orange")[depth%2])
        t.forward(size)
        t.right(90)
        draw_square(depth - 1, size / shrink_by)
        t.right(180)

if __name__ == "__main__":
    t.speed("fastest")
    t.pendown()
    draw_square(depth=4, size=100)
    t.exitonclick()

您不能使用 goto(),但您可以使用 stamp() 吗?

我对 @ggorlen (+1) 的优秀最终解决方案的修改,它使用 stamping 而不是 drawing,也没有 goto:

import turtle

COLORS = ['blue', 'orange']
CURSOR_SIZE = 20

def draw_square(depth, size, shrink_by=3):
    if depth:
        turtle.pencolor(COLORS[depth % len(COLORS)])
        turtle.shapesize(size / CURSOR_SIZE)
        turtle.stamp()

        offset = (size + (shrinkage := size / shrink_by)) * 2**0.5 / 2

        for _ in range(4):
            turtle.right(45)
            turtle.forward(offset)
            turtle.left(45)
            draw_square(depth - 1, shrinkage)
            turtle.right(45)
            turtle.backward(offset)
            turtle.left(135)  # undo right and advance corners

if __name__ == "__main__":
    turtle.shape('square')
    turtle.speed('fastest')
    turtle.fillcolor(turtle.bgcolor())
    turtle.penup()

    draw_square(depth=4, size=100)

    turtle.hideturtle()
    turtle.exitonclick()