使用 FractalArtMaker 模块在 Python Turtle 中制作分形
FractalArtMaker 是一个用于在 Python 中创建分形艺术的模块 turtle
模块。 您可以通过运行来安装模块 pip3 install FractalArtMaker
这个模块在 No Starch Press 的 Al Sweigart 所著的《递归的递归之书》一书中进行了探讨。 这篇博文将向您展示如何制作如下所示的分形:
您可以通过 https://nostarch.com/recursive-book-recursion 直接从出版商处购买本书,或通过 https://inventwithpython.com/recursion/ 在线阅读
快速开始
要查看一些示例分形,请从交互式 shell 运行以下命令:
>>> import fractalartmaker >>> fractalartmaker.fast() # draw the fractals quickly >>> fractalartmaker.example(1) # pass 1 to 9
制作分形
Fractal Art Maker 的算法有两个主要组成部分:形状绘制函数和递归函数 drawFractal()
功能。
形状绘制函数绘制一个基本形状。 Fractal Art Maker 程序带有两个形状绘图功能, fractalartmaker.drawFilledSquare()
和 fractalartmaker.drawTriangleOutline()
, 但您也可以创建自己的。 我们将形状绘制函数传递给 fractalartmaker.drawFractal()
用作参数。
这 fractalartmaker.drawFractal()
函数还有一个参数,指示递归调用之间形状的大小、位置和角度的变化 fractalartmaker.drawFractal()
.
这 fractalartmaker.drawFractal()
函数使用传递给它的形状绘制函数来绘制分形的各个部分。 这通常是一个简单的形状,例如正方形或三角形。 分形的美丽复杂性来自 fractalartmaker.drawFractal()
为整个分形的每个单独组件递归调用此函数。
这是其中的两个形状绘制函数 fractalartmaker
模块:
def drawFilledSquare(size, depth): size = int(size) # Move to the top-right corner before drawing: turtle.penup() turtle.forward(size // 2) turtle.left(90) turtle.forward(size // 2) turtle.left(180) turtle.pendown() # Alternate between white and gray (with black border): if depth % 2 == 0: turtle.pencolor('black') turtle.fillcolor('white') else: turtle.pencolor('black') turtle.fillcolor('gray') # Draw a square: turtle.begin_fill() for i in range(4): # Draw four lines. turtle.forward(size) turtle.right(90) turtle.end_fill() def drawTriangleOutline(size, depth): size = int(size) # Move the turtle to the top of the equilateral triangle: height = size * math.sqrt(3) / 2 turtle.penup() turtle.left(90) # Turn to face upwards. turtle.forward(height * (2/3)) # Move to the top corner. turtle.right(150) # Turn to face the bottom-right corner. turtle.pendown() # Draw the three sides of the triangle: for i in range(3): turtle.forward(size) turtle.right(120)
Fractal Art Maker 的形状绘制函数有两个参数: size
和 depth
. size 参数是它绘制的正方形或三角形的边长。 形状绘图函数应始终使用参数来 turtle.forward()
是基于 size
这样长度将与每个递归级别的大小成正比。 避免像这样的代码 turtle.forward(100)
或者 turtle.forward(200)
; 相反,使用基于 size
参数,比如 turtle.forward(size)
或者 turtle.forward(size * 2)
. 在 Python 中 turtle
模块, turtle.forward(1)
将海龟移动一个单位,不一定等同于一个像素。
形状绘制函数的第二个参数是递归深度 fractalartmaker.drawFractal()
. 您的形状绘制函数可以忽略此参数,但使用它可能会导致基本形状发生有趣的变化。 例如, fractalartmaker.drawFilledSquare()
形状绘制函数使用深度在绘制白色方块和灰色方块之间交替。 如果您想为 Fractal Art Maker 程序创建自己的形状绘图函数,请记住这一点,因为它们必须接受 size
和 depth
争论。
这 fractalartmaker.drawFractal()
函数具有三个必需参数和一个可选参数: shapeDrawFunction
, size
, specs
,并可选择 maxDepth
. 这 shapeDrawFunction
参数需要一个函数,比如 fractalartmaker.drawFilledSquare
或者 fractalartmaker.drawTriangleOutline
. 这 size
参数需要传递给绘图函数的起始大小。 通常,值介于 100
和 500
是一个很好的起始大小,尽管这取决于您的形状绘制函数中的代码,并且找到正确的值可能需要试验。
这 specs
参数需要一个字典列表,这些字典指定递归形状应该如何改变它们的大小、位置和角度 fractalartmaker.drawFractal()
递归调用自己。 这些规范将在本节后面描述。 阻止 fractalartmaker.drawFractal()
从递归直到它导致堆栈溢出, maxDepth
参数保存次数 fractalartmaker.drawFractal()
应该递归地调用自己。 默认情况下, maxDepth
的值为 8
但如果你想要更多或更少的递归形状,你可以提供不同的值。
递归调用 fractalartmaker.drawFractal()
是基于在规范 specs
列表的字典。 对于每个字典, fractalartmaker.drawFractal()
进行一次递归调用 fractalartmaker.drawFractal()
. 如果 specs 是一个包含一个字典的列表,则每次调用 fractalartmaker.drawFractal()
结果只有一次递归调用 fractalartmaker.drawFractal()
. 如果 specs 是一个包含三个字典的列表,则每次调用 fractalartmaker.drawFractal()
导致三个递归调用 fractalartmaker.drawFractal()
.
specs 参数中的字典为每个递归调用提供规范。 这些词典中的每一个都有键 sizeChange
, xChange
, yChange
, 和 angleChange
. 这些决定了分形的大小、海龟的位置和海龟的航向如何改变递归 fractalartmaker.drawFractal()
称呼。
* sizeChange
(默认是 1.0
) – 下一个递归形状的大小值是当前大小乘以该值。 * xChange
(默认是 0.0
) – 下一个递归形状的 x 坐标是当前 x 坐标加上当前大小乘以该值。 * yChange
(默认是 0.0
) – 下一个递归形状的 y 坐标是当前 y 坐标加上当前大小乘以该值。 * angleChange
(默认是 0.0
) – 下一个递归形状的起始角度是当前起始角度加上这个值。
让我们看一下四个角分形的规范字典,它是在调用时绘制的 fractalartmaker.example(1)
. 打电话给 fractalartmaker.drawFractal()
因为四个角分形通过以下字典列表 specs
范围:
fractalartmaker.drawFractal(fractalartmaker.drawFilledSquare, 350, [{'sizeChange': 0.5, 'xChange': -0.5, 'yChange': 0.5}, {'sizeChange': 0.5, 'xChange': 0.5, 'yChange': 0.5}, {'sizeChange': 0.5, 'xChange': -0.5, 'yChange': -0.5}, {'sizeChange': 0.5, 'xChange': 0.5, 'yChange': -0.5}], 5)
这 specs
列表有四个字典,所以每次调用 drawFractal()
绘制一个正方形将依次递归调用 drawFractal()
再画四次,再画四个正方形。
要确定下一个要绘制的正方形的大小, sizeChange
键乘以当前大小参数。 规格列表中的第一本词典有 sizeChange
的价值 0.5
,这使得下一个递归调用的大小参数为 350 * 0.5
, 或者 175
单位。 这使得下一个方块的大小是前一个方块的一半。 A sizeChange
的价值 2.0
例如,将下一个正方形的大小加倍。 如果字典没有 sizeChange
键,值默认为 1.0
因为尺寸没有变化。
如果你看一下其他三本词典 specs
列表,你会发现他们都有 sizeChange
的价值 0.5
. 它们之间的区别在于它们的 xChange
和 yChange
values 将它们放在当前正方形的其他三个角上。 结果,接下来的四个正方形将以当前正方形的四个角为中心绘制。
词典中的 specs
此示例的列表没有 angleChange
值,所以这个值默认为 0.0
度。 积极的 angleChange
值表示逆时针旋转,负值表示顺时针旋转。
看一下模块中的代码 example()
功能更多的例子。
这 fractalartmaker
模块还有一个 fractalartmaker.fast()
您可以调用函数来快速绘制分形,以及 fractalartmaker.clear()
清除乌龟绘图窗口。
让我们检查模块附带的 9 个示例分形中的每一个的代码。
示例 1 – 四个角
第一个分形是四个角,开始时是一个大正方形。 当函数调用自身时,分形的规范导致在正方形的四个角绘制四个较小的正方形:
# Four Corners: drawFractal(drawFilledSquare, 350, [{'sizeChange': 0.5, 'xChange': -0.5, 'yChange': 0.5}, {'sizeChange': 0.5, 'xChange': 0.5, 'yChange': 0.5}, {'sizeChange': 0.5, 'xChange': -0.5, 'yChange': -0.5}, {'sizeChange': 0.5, 'xChange': 0.5, 'yChange': -0.5}], 5)
打电话给 drawFractal()
这里将最大深度限制为 5
因为任何更多的分形都会使分形变得如此密集以至于难以看到精细的细节。
示例 2 – 螺旋方块
Spiral Squares 分形也从一个大正方形开始,但它在每次递归调用时只创建一个新正方形:
# Spiral Squares: drawFractal(drawFilledSquare, 600, [{'sizeChange': 0.95, 'angleChange': 7}], 50)
这个正方形稍微小一点,旋转了 7 度。 所有正方形的中心不变,所以不需要添加 xChange
和
钥匙 y变化
to
规范。 默认最大深度 8
是 too
小以获得有趣的分形,所以我们将其增加到 50
到 produce
催眠的螺旋图案
示例 3 – 双螺旋方块
双螺旋正方形分形类似于螺旋正方形,不同之处在于每个正方形创建两个更小的正方形。 这会产生一个有趣的扇形效应,因为第二个方块绘制得较晚,并且往往会覆盖之前绘制的方块:
# Double Spiral Squares: drawFractal(drawFilledSquare, 600, [{'sizeChange': 0.8, 'yChange': 0.1, 'angleChange': -10}, {'sizeChange': 0.8, 'yChange': -0.1, 'angleChange': 10}])
创建的方块比它们之前的方块略高或略低,并旋转 10
或者 -10
度。
示例 4 – 三角形螺旋
三角形螺旋分形是螺旋方形的另一种变体,它使用 drawTriangleOutline()
形状绘图功能而不是 drawFilledSquare()
:
# Triangle Spiral: drawFractal(drawTriangleOutline, 20, [{'sizeChange': 1.05, 'angleChange': 7}], 80)
与正方形螺旋不同,三角螺旋从小尺寸开始 20
每个递归级别的单位和大小略有增加。 这 sizeChange
键大于 1.0
,因此形状的大小总是在增加。
这意味着基本情况发生在递归达到深度时 80
,因为大小的基本情况变得小于 1
永远不会达到。
示例 5 – 康威的生命游戏滑翔机
康威的生命游戏是元胞自动机的一个著名例子。 游戏的简单规则会导致有趣且极其混乱的图案出现在二维网格上。 一种这样的模式是由 3×3 空间中的五个单元格组成的滑翔机:
# Conway's Game of Life Glider: third = 1 / 3 drawFractal(drawFilledSquare, 600, [{'sizeChange': third, 'yChange': third}, {'sizeChange': third, 'xChange': third}, {'sizeChange': third, 'xChange': third, 'yChange': -third}, {'sizeChange': third, 'yChange': -third}, {'sizeChange': third, 'xChange': -third, 'yChange': -third}])
此处的滑翔机分形在其五个单元格中的每一个中都绘制了额外的滑翔机。 第三个变量有助于精确设置递归形状在 3 × 3 空间中的位置。
您可以在 https://inventwithpython.com/bigbookpython/project13.html 在线找到康威生命游戏的 Python 实现。 不幸的是,开发康威生命游戏的数学家兼教授约翰康威于 2020 年 4 月死于 COVID-19 并发症。
示例 6 – Sierpiński 三角形
我们在第 9 章中创建了 Sierpiński 三角形分形,但我们的 Fractal Art Maker 也可以通过使用 drawTriangleOutline()
形函数。 毕竟,Sierpiński 三角形是一个等边三角形…