画一棵树
从一个树枝开始,分叉向两端(或者更多端),然后继续从新的树枝进行分叉,…
while True: 树枝 = 树枝.分叉
如果不限制,树可以一直这么长下去…
最后会长成这样:(限制了分叉层数)
对颜色等参数进行一些改变,就可以得到不同的树:
好了,我们已经知道树的生长原理了,现在让我们把这个过程画出来。
turtle 海龟绘图
turtle是Python内置的一个画图库,使用tkinter实现基本图形界面。
它的方法就是用一只海龟作为画笔在屏幕绘图。
更多方法参考标准库中turtle一节。
这里用一个画正方形的例子演示turtle用法:
import turtle # 首先导入turtle库 p = turtle.Turtle() # 初始化画笔p p.speed(1) # 设置速度为1,最慢。 for i in range(4): p.forward(300) # 画笔向前移动300像素点 p.right(90) # 画笔角度右转90度。 turtle.mainloop() #启动绘图。
(注:画笔初始方向朝右)
turtle自带了一些示例,我们可以在命令行输入python -m turtledemo
查看
示例包括特殊图形、噪声、时钟、混色器、森林、分形曲线、nim游戏、画笔、彩虹、penrose、星球、球、三角、排序模拟、树、上下画布、太极图。
里面有很多例子,可以选择。左边可以查看代码,右边点击START运行。
tree
以tree
为例,
下面tree的生成使用了广度优先。把当前层的树枝画完,并且把下一层的放入列表,然后递归处理下一层。
from turtle import Turtle, mainloop from time import perf_counter as clock def tree(plist, l, a, f): """ plist 笔的列表 l 树枝长度 a 树枝转弯角度 f 树枝长度衰减因子 from level to level.""" if l > 3: lst = [] for p in plist: p.forward(l) q = p.clone() p.left(a) #左转 q.right(a) #右转 lst.append(p) lst.append(q) tree(lst, l*f, a, f) def maketree(): p = Turtle() p.setundobuffer(None) p.hideturtle() p.speed(1) p.getscreen().tracer(10,0) p.left(90) #初始朝右,左转90度朝上 p.penup() p.forward(-210) p.pendown() tree([p], 200, 65, 0.635) def main(): a=clock() maketree() b=clock() return "done: %.2f sec." % (b-a) if __name__ == "__main__": msg = main() print(msg) mainloop()
forest
文章开始画的树示是例中的forest
.
也使用了广度优先。代码比较长,就不全放了。只看关键的树的生成:
def tree(tlist, size, level, widthfactor, branchlists, angledist=10, sizedist=5): # benutzt Liste von turtles und Liste von Zweiglisten, # fuer jede turtle eine! if level > 0: lst = [] brs = [] for t, branchlist in list(zip(tlist,branchlists)): t.pensize( size * widthfactor ) t.pencolor( 255 - (180 - 11 * level + symRandom(15)), 180 - 11 * level + symRandom(15), 0 ) t.pendown() randomfd(t, size, level, angledist ) yield 1 for angle, sizefactor in branchlist: t.left(angle) lst.append(t.clone()) brs.append(randomize(branchlist, angledist, sizedist)) t.right(angle) for x in tree(lst, size*sizefactor, level-1, widthfactor, brs, angledist, sizedist): yield None
原始的代码比较难懂,因为他用了yield
来方便后面的交替画树。(带有yield的函数实际上是一个生成器。后面再说生成器。)
我们把yield去掉,改成常规的递归调用。这就是一个典型的广度优先遍历。和上面的tree
基本一样。
def tree(tlist, size, level, widthfactor, branchlists, angledist=10, sizedist=5): if level > 0: lst = [] brs = [] for t, branchlist in list(zip(tlist,branchlists)): t.pensize( size * widthfactor ) t.pencolor( 255 - (180 - 11 * level + symRandom(15)), 180 - 11 * level + symRandom(15), 0 ) t.pendown() #树枝进行生长 randomfd(t, size, level, angledist) for angle, sizefactor in branchlist: t.left(angle) lst.append(t.clone()) #做出选择,并保存 brs.append(randomize(branchlist, angledist, sizedist)) t.right(angle) #back 回撤选择 #递归处理下一层 tree(lst, size*sizefactor, level-1, widthfactor, brs, angledist, sizedist)
微调:
我们对原始代码的pensize, pencolor中的参数进行调整,就可以改变树的颜色和粗细。
RGB颜色对照表 (oschina.net)
用下面方法可以创建一棵树。可以调整参数,创建n棵数。
pen.hideturtle() start(pen, 20, -208) #参数: 笔(Turtle对象),线条宽度,树的层数,宽度因子,[[(树枝改变角度,宽度衰减因子)]] t = tree( [pen], 80, level, 0.1, [[ (45,0.69), (0,0.65), (-45,0.71) ]] )
附录:
完整代码:
tree.py
from turtle import Turtle, mainloop from time import perf_counter as clock def tree(plist, l, a, f): """ plist 笔的列表 l 树枝长度 a 树枝转弯角度 f 树枝长度衰减因子 from level to level.""" if l > 3: lst = [] for p in plist: p.forward(l) q = p.clone() p.left(a) #左转 q.right(a) #右转 lst.append(p) lst.append(q) tree(lst, l*f, a, f) def maketree(): p = Turtle() p.setundobuffer(None) p.hideturtle() p.speed(1) p.getscreen().tracer(10,0) p.left(90) #初始朝右,左转90度朝上 p.penup() p.forward(-210) p.pendown() tree([p], 200, 65, 0.635) def main(): a=clock() maketree() b=clock() return "done: %.2f sec." % (b-a) if __name__ == "__main__": msg = main() print(msg) mainloop()
forest.py
#!/usr/bin/env python3 """ turtlegraphics-example-suite: tdemo_forest.py Displays a 'forest' of 3 breadth-first-trees similar to the one in tree. For further remarks see tree.py This example is a 'breadth-first'-rewrite of a Logo program written by Erich Neuwirth. See http://homepage.univie.ac.at/erich.neuwirth/ """ from turtle import Turtle, colormode, tracer, mainloop from random import randrange from time import perf_counter as clock def symRandom(n): return randrange(-n,n+1) def randomize( branchlist, angledist, sizedist ): return [ (angle+symRandom(angledist), sizefactor*1.01**symRandom(sizedist)) for angle, sizefactor in branchlist ] def randomfd( t, distance, parts, angledist ): for i in range(parts): t.left(symRandom(angledist)) t.forward( (1.0 * distance)/parts ) def tree(tlist, size, level, widthfactor, branchlists, angledist=10, sizedist=5): # benutzt Liste von turtles und Liste von Zweiglisten, # fuer jede turtle eine! if level > 0: lst = [] brs = [] for t, branchlist in list(zip(tlist,branchlists)): t.pensize( size * widthfactor ) t.pencolor( 255 - (180 - 11 * level + symRandom(15)), 180 - 11 * level + symRandom(15), 0 ) t.pendown() randomfd(t, size, level, angledist ) yield 1 for angle, sizefactor in branchlist: t.left(angle) lst.append(t.clone()) brs.append(randomize(branchlist, angledist, sizedist)) t.right(angle) for x in tree(lst, size*sizefactor, level-1, widthfactor, brs, angledist, sizedist): yield None def start(t,x,y): colormode(255) t.reset() t.speed(0) t.hideturtle() t.left(90) t.penup() t.setpos(x,y) t.pendown() def doit1(level, pen): pen.hideturtle() start(pen, 20, -208) t = tree( [pen], 80, level, 0.1, [[ (45,0.69), (0,0.65), (-45,0.71) ]] ) return t def doit2(level, pen): pen.hideturtle() start(pen, -135, -130) t = tree( [pen], 120, level, 0.1, [[ (45,0.69), (-45,0.71) ]] ) return t def doit3(level, pen): pen.hideturtle() start(pen, 190, -90) t = tree( [pen], 100, level, 0.1, [[ (45,0.7), (0,0.72), (-45,0.65) ]] ) return t # Hier 3 Baumgeneratoren: def main(): p = Turtle() p.ht() tracer(75,0) u = doit1(6, Turtle(undobuffersize=1)) s = doit2(7, Turtle(undobuffersize=1)) t = doit3(5, Turtle(undobuffersize=1)) a = clock() while True: done = 0 for b in u,s,t: try: b.__next__() except: done += 1 if done == 3: break tracer(1,10) b = clock() return "runtime: %.2f sec." % (b-a) if __name__ == '__main__': run_time = main() print(run_time) mainloop()
修改后的forest,去掉了yield
#!/usr/bin/env python3 """ turtlegraphics-example-suite: tdemo_forest.py Displays a 'forest' of 3 breadth-first-trees similar to the one in tree. For further remarks see tree.py This example is a 'breadth-first'-rewrite of a Logo program written by Erich Neuwirth. See http://homepage.univie.ac.at/erich.neuwirth/ """ from turtle import Turtle, colormode, tracer, mainloop from random import randrange from time import perf_counter as clock def symRandom(n): return randrange(-n, n + 1) def randomize(branchlist, angledist, sizedist): return [(angle + symRandom(angledist), sizefactor * 1.01 ** symRandom(sizedist)) for angle, sizefactor in branchlist] def randomfd(t, distance, parts, angledist): for i in range(parts): t.left(symRandom(angledist)) t.forward((1.0 * distance) / parts) def tree(tlist, size, level, widthfactor, branchlists, angledist=10, sizedist=5): if level > 0: lst = [] brs = [] for t, branchlist in list(zip(tlist, branchlists)): t.pensize(size * widthfactor) t.pencolor(255 - (180 - 11 * level + symRandom(15)), 180 - 11 * level + symRandom(15), 0) t.pendown() # 树枝进行生长 randomfd(t, size, level, angledist) for angle, sizefactor in branchlist: t.left(angle) lst.append(t.clone()) # 做出选择,并保存 brs.append(randomize(branchlist, angledist, sizedist)) t.right(angle) # back 回撤选择 # 递归处理下一层 tree(lst, size * sizefactor, level - 1, widthfactor, brs, angledist, sizedist) def start(t, x, y): colormode(255) t.reset() t.speed(0) t.hideturtle() t.left(90) t.penup() t.setpos(x, y) t.pendown() def doit1(level, pen): pen.hideturtle() start(pen, 20, -208) t = tree([pen], 80, level, 0.1, [[(45, 0.69), (0, 0.65), (-45, 0.71)]]) return t def doit2(level, pen): pen.hideturtle() start(pen, -135, -130) t = tree([pen], 120, level, 0.1, [[(45, 0.69), (-45, 0.71)]]) return t def doit3(level, pen): pen.hideturtle() start(pen, 190, -90) t = tree([pen], 100, level, 0.1, [[(45, 0.7), (0, 0.72), (-45, 0.65)]]) return t # Hier 3 Baumgeneratoren: def main(): a = clock() p = Turtle() p.ht() tracer(75, 0) u = doit1(6, Turtle(undobuffersize=1)) s = doit2(7, Turtle(undobuffersize=1)) t = doit3(5, Turtle(undobuffersize=1)) b = clock() return "runtime: %.2f sec." % (b - a) if __name__ == '__main__': run_time = main() print(run_time) mainloop()