为绘制科赫雪花,需要定义一个递归函数drawKochSF()。这个函数根据点 A 和点 B 的坐标计算点 P 1 、 P 2 和 P 3 (见图1.2)的坐标;再递归地调用自己,为越来越短的线段执行同样的计算,直到满足基线条件;最后使用模块turtle绘制片段。要查看完整的项目代码,请参阅1.7节“完整代码”,也可见本书配套源代码中的“/koch/koch.py”。
在函数drawKochSF()中,首先计算为绘制图1.2所示基本片段图案所需的所有点的坐标。
def drawKochSF(x1, y1, x2, y2, t):
d = math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))
r = d/3.0
h = r*math.sqrt(3)/2.0
p3 = ((x1 + 2*x2)/3.0, (y1 + 2*y2)/3.0)
p1 = ((2*x1 + x2)/3.0, (2*y1 + y2)/3.0)
c = (0.5*(x1+x2), 0.5*(y1+y2))
n = ((y1-y2)/d, (x2-x1)/d)
p2 = (c[0]+h*n[0], c[1]+h*n[1])
函数drawKochSF()被定义为将线段 AB 的端点的 x 和 y 坐标作为参数,该线段定义了图1.4所示雪花中的一个片段。该函数还将turtle对象t作为参数,该参数用于完成实际的绘图工作。接下来,这个函数将计算图1.2所示的所有参数,这些参数在1.1.2小节“构建科赫雪花”中讨论过。首先计算的是d—— A 到 B 的距离。将这个距离除以3得到r,这是构成片段的各条线段(总共4条)的长度。然后根据r计算h,这是片段中间的三角形的高。
接下来计算其他参数,它们都是元组,包含 x 坐标和 y 坐标。元组p3和p1定义了片段中间的三角形底边的两个端点,元组c表示p1和p3的中点,而n是与线段 AB 垂直的单位向量。通过结合使用c、h和n,计算出了p2——片段中间的三角形的顶点坐标。
在函数drawKochSF()的下一部分中,使用递归将第1个片段分解成越来越小的片段。
❶ if d > 10:
# 第1个片段
❷ drawKochSF(x1, y1, p1[0], p1[1], t)
# 第2个片段
drawKochSF(p1[0], p1[1], p2[0], p2[1], t)
# 第3个片段
drawKochSF(p2[0], p2[1], p3[0], p3[1], t)
# 第4个片段
drawKochSF(p3[0], p3[1], x2, y2, t)
首先确定了递归停止条件❶。如果d(线段 AB 的长度)大于10像素,就继续递归,这是通过调用函数drawKochSF() 4次实现的。每次调用drawKochSF()时,都传入了一组不同的参数,这些参数是根据构成片段的4条线段的端点坐标确定的,而这些端点坐标已在函数开头计算得到。例如,在❷处,为线段 AP 1 调用了函数drawKochSF(),而此后几次函数调用分别针对的是线段 P 1 P 2 、 P 2 P 3 和 P 3 B 。在这些递归调用中,都将根据新的点 A 和点 B 坐标重复之前的计算,并判断d是否依然大于10像素,如果是就再次递归调用drawKochSF() 4次,以此类推。
下面来看看线段 AB 小于10像素时的情况,这是此递归算法的基线条件。小于这个阈值后将不再递归,而是将构成单个片段的4条线段返回,为此使用了模块turtle中的方法up()、down()和setpos(),这些方法在1.1.3小节“使用海龟绘图法绘图”中介绍过。
else:
# 绘制中间的角
t.up()
❶ t.setpos(p1[0], p1[1])
t.down()
t.setpos(p2[0], p2[1])
t.setpos(p3[0], p3[1])
# 绘制两侧的边
t.up()
❷ t.setpos(x1, y1)
t.down()
t.setpos(p1[0], p1[1])
t.up()
❸ t.setpos(p3[0], p3[1])
t.down()
t.setpos(x2, y2)
首先绘制了由点 P 1 、 P 2 和 P 3 构成的角❶,然后绘制了线段 AP 1 ❷和 P 3 B ❸。由于在函数drawKochSF()的开头完成了所有必要的计算,因此实际绘图工作很简单,将合适的坐标传递给方法setpos()即可。
函数main()创建并设置一个turtle对象,再调用drawKochSF()。
def main():
print('Drawing the Koch Snowflake...')
t = turtle.Turtle()
t.hideturtle()
# 绘制科赫雪花
try:
❶ drawKochSF(-100, 0, 100, 0, t)
❷ drawKochSF(0, -173.2, -100, 0, t)
❸ drawKochSF(100, 0, 0, -173.2, t)
❹ except:
print("Exception, exiting.")
exit(0)
# 等用户在屏幕上单击后退出
❺ turtle.Screen().exitonclick()
从图1.4可知,要绘制3个片段,确保最终输出为六角对称的雪花图形。为此,调用了drawKochSF() 3次:对于第1个片段,点 A 和点 B 的坐标分别为(−100, 0)和(100, 0)❶;对于第2个片段,坐标为(0, −173.2)和(−100, 0)❷;对于第3个片段,坐标为(100, 0)和(0, −173.2)❸。请注意,这些坐标与前面在程序test_turtle.py中绘制三角形时使用的坐标相同。请尝试确定这些坐标是如何计算出来的。(提示: )
为捕获绘图期间可能发生的异常,将对函数drawKochSF()的调用放在一个Python try块中。例如,用户在绘图期间关闭了窗口,将引发异常,可在except块中捕获此异常❹,然后输出一条消息并退出程序。如果用户没有终止绘图过程,将执行代码turtle.Screen().exitonclick()❺,等待用户单击将窗口关闭。