在 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()
右上角的小方块尺寸不对,左边的乌龟向前移动太多了。
如何编写递归来生成正确的左上角方块?
很好的尝试!我会建议一种稍微不同的方法,为您的方形绘图函数添加 x
和 y
坐标,并使用 t.goto(x, y)
重新定位海龟。这些坐标表示应绘制正方形的左下角,省去您手动移动乌龟的麻烦(尽管技术上可行,但不太干净)。
绘制完一个正方形后,乌龟将始终面向右并准备绘制下一个正方形,因此移动命令保持在最低限度。剩下的就是找出每个角的原点坐标。
对于右上角,很简单:x + size
、y + size
。对于左上角,它是类似的:仍然是 y + size
,但是使用 x - size_of_smaller_square
将 x
轴偏移正确的量。如果您好奇的话,我还包括了左下角和右下角。
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
是被禁止的。您可以遵循保证有效的机械策略:在每次递归调用结束时,始终将乌龟准确地放回到它开始的位置(相同的位置和方向)。这尊重递归的自相似结构。每帧的高级方法是:
绘制当前框
每个子框:
- 将乌龟移动到正确的位置和方向绘制子框
- 生成递归调用
- 撤消您在第 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()
我正在尝试让方块看起来像:
但我的代码是绘图:
我不知道我做错了什么,或者我的整个方法是否错误。
代码如下:
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()
右上角的小方块尺寸不对,左边的乌龟向前移动太多了。
如何编写递归来生成正确的左上角方块?
很好的尝试!我会建议一种稍微不同的方法,为您的方形绘图函数添加 x
和 y
坐标,并使用 t.goto(x, y)
重新定位海龟。这些坐标表示应绘制正方形的左下角,省去您手动移动乌龟的麻烦(尽管技术上可行,但不太干净)。
绘制完一个正方形后,乌龟将始终面向右并准备绘制下一个正方形,因此移动命令保持在最低限度。剩下的就是找出每个角的原点坐标。
对于右上角,很简单:x + size
、y + size
。对于左上角,它是类似的:仍然是 y + size
,但是使用 x - size_of_smaller_square
将 x
轴偏移正确的量。如果您好奇的话,我还包括了左下角和右下角。
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
是被禁止的。您可以遵循保证有效的机械策略:在每次递归调用结束时,始终将乌龟准确地放回到它开始的位置(相同的位置和方向)。这尊重递归的自相似结构。每帧的高级方法是:
绘制当前框
每个子框:
- 将乌龟移动到正确的位置和方向绘制子框
- 生成递归调用
- 撤消您在第 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()