【问题标题】:Drawing fractal square pattern in Turtle在 Turtle 中绘制分形正方形图案
【发布时间】:2021-11-05 17:02:58
【问题描述】:

我试图让方块看起来像:

但我的代码正在绘制:

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

代码如下:

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()

在右上角,小方块最终尺寸不正确,而在左侧,海龟向前移动太多。

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

【问题讨论】:

  • “在右上角,小方块最终不是正确的尺寸”它们在我看来尺寸正确。 “在左边,乌龟向前移动太多了。”好吧,在绘制左上角方块之前仔细考虑一下乌龟在哪里,因此在绘制它之前,请仔细考虑告诉你要移动多远的规则。您应该会发现,将代码分解为完成子任务的更小函数会很有帮助 - 例如,从特定角开始绘制给定大小的正方形。

标签: python python-3.x recursion turtle-graphics python-turtle


【解决方案1】:

你不能使用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()

【讨论】:

    【解决方案2】:

    很好的尝试!我将建议一种稍微不同的方法,为您的正方形绘图函数添加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()
    

    【讨论】:

    • 不幸的是,我不允许使用传送,这使得这项任务变得如此复杂。不过感谢代码示例,看起来比我的干净很多
    • 嗯,这是一个奇怪的要求。请查看更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    相关资源
    最近更新 更多