一、整体结构
我们先来设计一下大概的框架,后面再逐步完善他,在这篇文章最后会有完整的代码给大家。我在前面介绍过turtle函数,在这里我就不重复介绍了,但是还是会点到他的基本操作的。
import turtle
import random
def love(x, y): # 定义画爱心的函数
# ...
def tree(branchLen, t): # 定义递归画树的函数
# ...
# 主程序
myWin = turtle.Screen() # 创建窗口
t = turtle.Turtle() # 创建主画笔
# 初始化设置...
tree(100, t) # 开始画树
myWin.exitonclick() # 点击关闭窗口
在这里大家可能会好奇random函数是什么?其实random
是 Python 内置的随机数生成模块,用于生成各种随机数据。可以生成随机整数、随机小数、随机选择元素,甚至打乱列表顺序等。后面我还是会单独说一下andom函数的,请大家放心。
二、逐步进行完善
1、我们先完善love函数,但是怕大家看不懂,我们还是一步一步来完善吧!
功能:在指定坐标 (x, y)
处绘制一个爱心,并写上文字 "love"。
(1) 创建画笔
lv = turtle.Turtle() # 想象你拿到一支新画笔
lv.hideturtle() # 隐藏画笔的笔尖图标(只显示画的图形)
lv.up() # 抬起画笔(移动时不画线)
lv.goto(x, y) # 把画笔移动到指定的(x,y)坐标位置
-
类比:就像你画画前拿起一支新笔,调整位置但不留下痕迹。
(2) 定义画圆弧的辅助函数
def curvemove(): # 定义一个画圆弧的动作
for i in range(20):
lv.right(10) # 画笔向右转10度
lv.forward(2) # 向前画2像素
-
作用:通过多次“右转 + 前移”画出一个圆弧。
-
类比:像用圆规画弧,每次转一点角度,走一小段。
(3) 设置画笔属性
def curvemove(): # 定义一个画圆弧的动作
for i in range(20):
lv.right(10) # 画笔向右转10度
lv.forward(2) # 向前画2像素
(4) 绘制爱心主体
lv.down() # 落下画笔开始画图
lv.begin_fill() # 开始填充颜色(后续闭合图形会被填充粉色)
lv.left(140) # 向左转140度(调整起始方向)
lv.forward(22) # 向前画22像素(爱心左侧的直线部分)
curvemove() # 调用之前定义的圆弧函数(画左半边的弧)
lv.left(120) # 再向左转120度(调整方向画右半边)
curvemove() # 再画一个圆弧(右半边的弧)
lv.forward(22) # 向前画22像素(爱心右侧的直线部分)
lv.write("YZ", font=("Arial", 12, "normal"), align="center") # 在中心写字
lv.left(140) # 转回初始方向(让画笔复位)
lv.end_fill() # 结束填充(闭合区域被填满粉色)
(5)爱心绘制流程
想象你按以下步骤手动绘制:
- (1)笔尖移动到起始点
- (2)向左转140度,画一条向左上方的线
- (3)向右连续小幅度转20次,每次画一小段,形成一个左半圆弧
- (4)再向左转120度,重复画右半圆弧
- (5)最后闭合图形,形成爱心形状
(6)可视化步骤
左转140°
↗
/ \
/ \← 左转120°后的右半圆弧
/ \
直线22px 曲线
(7)关键点总结
代码部分 | 作用 | 类比 |
---|---|---|
lv = turtle.Turtle() | 创建新画笔 | 拿一支新彩笔 |
lv.goto(x, y) | 定位到坐标点 | 把笔尖移到纸的某个位置 |
curvemove() | 画圆弧的重复动作 | 用圆规画弧 |
begin_fill()/end_fill() | 填充闭合区域颜色 | 用彩笔涂满封闭区域 |
(8)love函数的整体完善如下:
def love(x, y):
lv = turtle.Turtle() # 创建一个新的海龟对象(专门用来画爱心)
lv.hideturtle() # 隐藏海龟图标
lv.up() # 抬起画笔(移动时不画线)
lv.goto(x, y) # 移动到指定坐标
# 定义内部函数:画圆弧
def curvemove():
for i in range(20):
lv.right(10) # 右转10度
lv.forward(2) # 画2像素
# 设置画笔属性
lv.color('red', 'pink') # 边框红色,填充粉色
lv.speed(10000000) # 最快速度
lv.pensize(1) # 线条粗细
# 开始绘制爱心
lv.down() # 落下画笔
lv.begin_fill() # 开始填充颜色
lv.left(140) # 左转140度
lv.forward(22) # 画直线22像素
curvemove() # 画左侧圆弧
lv.left(120) # 左转120度
curvemove() # 画右侧圆弧
lv.forward(22) # 画直线22像素
lv.write("love", font=("Arial", 12, "normal"), align="center") # 在爱心中心写文字
lv.left(140) # 复位方向
lv.end_fill() # 结束填充
2、现在我们来逐步完善tree
函数
功能:递归绘制树枝,当树枝较短时在末端画爱心。
(1) 终止条件:树枝太短就停止生长
if branchLen > 5: # 如果树枝长度>5才继续
# 后续代码...
else:
return # 停止生长
-
类比:就像真实树枝不可能无限变细,当树枝太短(≤5像素)时停止分叉。
(2) 短树枝末端画爱心
if branchLen < 20: # 短树枝(嫩枝)
t.color("green") # 嫩枝设为绿色
t.pensize(...) # 随机粗细(模拟自然变化)
t.forward(branchLen) # 画这段树枝
love(t.xcor(), t.ycor()) # 在末端画爱心(开花)
t.backward(branchLen) # 退回原点(回到分叉点)
return # 结束当前分支的生长
-
类比:树枝末端长出一朵花(爱心),之后不再分叉。
(3) 长树枝分叉逻辑
# 画当前树枝
t.pensize(...) # 随机粗细
t.forward(branchLen) # 向前画主树枝
# 向右分叉
ang = random.uniform(15, 45) # 随机右转角度(15~45度之间)
t.right(ang) # 右转
tree(branchLen - 随机减少长度, t) # 画右侧更短的树枝(递归调用自己)
# 向左分叉
t.left(2 * ang) # 左转两倍角度(回到原点后左转)
tree(branchLen - 随机减少长度, t) # 画左侧更短的树枝
# 复位
t.right(ang) # 转回原来的方向
t.backward(branchLen) # 退回分叉起点
-
类比:
-
先画一段主树枝。
-
向右随机转一个角度,分叉出一根更短的树枝。
-
再向左转更大的角度,分叉出另一根短树枝。
-
回到分叉点,准备画其他分支。
-
(4)递归过程图解
假设初始 branchLen=100
:
第1层:画100长的树枝
右转30度 → 第2层:画85长的树枝(100-15)
右转25度 → 第3层:画70长的树枝
...
直到长度<20时画爱心
左转60度 → 第2层:画80长的树枝(100-20)
右转40度 → 第3层:画65长的树枝
...
(5)关键点总结
代码部分 | 作用 | 类比 |
---|---|---|
tree(branchLen - ..., t) | 递归生成更短的分支 | 树枝分叉后变细变短 |
ang = random.uniform(15,45) | 随机分叉角度 | 自然生长的树枝角度不规则 |
t.right(ang) 和 t.left(2*ang) | 先右转分叉,再左转更大的角度分叉 | 左右对称分叉 |
backward(branchLen) | 退回分叉起点 | 画完分支后回到主干 |
(6)tree
函数函数具体完善如下:
def tree(branchLen, t):
if branchLen > 5: # 递归终止条件:树枝长度≤5时停止
if branchLen < 20: # 短树枝末端画爱心
t.color("green") # 短树枝设为绿色
t.pensize(random.uniform((branchLen +5)/4-2, (branchLen+6)/4+5)) # 随机粗细
t.down() # 落下画笔
t.forward(branchLen) # 画短树枝
love(t.xcor(), t.ycor()) # 在当前位置画爱心
t.up() # 抬起画笔
t.backward(branchLen) # 退回原位
t.color("brown") # 恢复树枝颜色
return # 结束递归
# 长树枝逻辑
t.pensize(random.uniform(...)) # 随机设置粗细
t.down()
t.forward(branchLen) # 画当前树枝
# 递归生成分支
ang = random.uniform(15, 45) # 随机分支角度
t.right(ang) # 右转随机角度
tree(branchLen - random.uniform(12, 16), t) # 画右侧分支(长度随机减少)
t.left(2 * ang) # 左转两倍角度
tree(branchLen - random.uniform(12, 16), t) # 画左侧分支
t.right(ang) # 复位方向
t.up() # 抬起画笔
t.backward(branchLen) # 退回原位
3、我们来完善主程序吧!
(1)创建窗口和画笔
myWin = turtle.Screen() # 创建一张画布(想象拿出一张白纸)
t = turtle.Turtle() # 创建一支画笔(笔尖默认在画布中心)
t.hideturtle() # 隐藏笔尖图标(只显示画出的图形)
-
效果:你有了画布和一支看不见笔尖的“魔法笔”。
(2)画笔初始设置
t.speed(1000) # 设置画笔移动速度为最快(避免绘制动画卡顿)
t.left(90) # 笔尖向左转90度(默认朝右→转后朝上,像竖直方向)
- 类比:把笔竖直立起来,准备从上往下画树。
(3)定位到树干起点
t.up() # 抬起笔尖(移动时不画线)
t.backward(200) # 笔尖向下移动200像素(从中心下移,到树干底部)
t.down() # 落下笔尖(开始画线)
-
类比:把笔竖直立起来,准备从上往下画树。
- 操作流程:
-
抬笔 → 避免移动时画线
-
向下移动 → 到树干起点
-
落笔 → 准备画树干
(4)绘制树干
t.color("brown") # 设置颜色为棕色(像树干的颜色)
t.pensize(32) # 设置笔触粗细32像素(粗树干)
t.forward(60) # 向上画60像素(树干高度)
-
效果:在画布下方画出一段粗壮的棕色树干。
(5)开始画树枝
tree(100, t) # 调用tree函数,从树干顶端开始画树枝(初始长度100)
(6)保持窗口显示
myWin.exitonclick() # 点击画布任意位置关闭窗口
-
作用:防止程序运行完自动关闭窗口,保持图形显示。
(7)完整流程示意图
1. 创建画布和笔
2. 笔朝上,抬笔移动到下方(树干起点)
3. 落笔画棕色粗树干
│
↑ 60像素
4. 在树干顶端调用 tree(100) 开始递归分叉
├─右分支
└─左分支
...
5. 点击关闭窗口
(8)参数调整建议
参数 | 修改示例 | 效果变化 |
---|---|---|
t.backward(200) → t.backward(300) | 树干起点更低 | 整棵树位置更靠下 |
t.pensize(32) → t.pensize(50) | 树干更粗 | 更粗壮的树干 |
t.forward(60) → t.forward(100) | 树干更高 | 更长的主干 |
tree(100, t) → tree(150, t) | 初始树枝更长 | 树冠更茂密 |
(9)主程序完整代码如下:
myWin = turtle.Screen() # 创建绘图窗口
t = turtle.Turtle() # 创建主画笔对象
t.hideturtle() # 隐藏海龟图标
t.speed(1000) # 最快绘制速度
t.left(90) # 初始方向朝上(默认向右)
t.up() # 抬起画笔
t.backward(200) # 向下移动200像素(从底部开始画树干)
t.down() # 落下画笔
t.color("brown") # 树干颜色
t.pensize(32) # 树干粗细
t.forward(60) # 画树干(高度60像素)
tree(100, t) # 开始递归画树枝(初始长度100)
myWin.exitonclick() # 点击窗口关闭
4、执行流程
-
初始化窗口和画笔。
-
绘制棕色树干。
-
递归绘制树枝:
-
长树枝:分左右两支,随机角度和长度。
-
短树枝:末端画绿色并添加爱心。
-
-
点击窗口关闭。
三、完整代码如下:
import turtle
import random
def love(x, y): # 定义画爱心的函数
lv = turtle.Turtle()
lv.hideturtle()
lv.up()
lv.goto(x, y) # 定位到(x,y)
# 定义内部函数:画圆弧
def curvemove(): # 画圆弧
for i in range(20):
lv.right(10)
lv.forward(2)
# 设置画笔属性
lv.color('red', 'pink')
lv.speed(10000000)
lv.pensize(1)
# 开始绘制爱心
lv.down()
lv.begin_fill()
lv.left(140)
lv.forward(22)
curvemove()
lv.left(120)
curvemove()
lv.forward(22)
lv.write("love", font=("Arial", 12, "normal"), align="center") # 写上表白的人的名字
lv.left(140) # 画完复位
lv.end_fill()
def tree(branchLen, t):
if branchLen > 5: # 剩余树枝太少要结束递归
if branchLen < 20: # 如果树枝剩余长度较短则变绿
t.color("green")
t.pensize(random.uniform((branchLen + 5) / 4 - 2, (branchLen + 6) / 4 + 5))
t.down()
t.forward(branchLen)
love(t.xcor(), t.ycor()) # 传输现在turtle的坐标
t.up()
t.backward(branchLen)
t.color("brown")
return
t.pensize(random.uniform((branchLen + 5) / 4 - 2, (branchLen + 6) / 4 + 5))
t.down()
t.forward(branchLen)
# 以下递归
ang = random.uniform(15, 45)
t.right(ang)
tree(branchLen - random.uniform(12, 16), t) # 随机决定减小长度
t.left(2 * ang)
tree(branchLen - random.uniform(12, 16), t) # 随机决定减小长度
t.right(ang)
t.up()
t.backward(branchLen)
myWin = turtle.Screen()
t = turtle.Turtle()
t.hideturtle()
t.speed(1000)
t.left(90)
t.up()
t.backward(200)
t.down()
t.color("brown")
t.pensize(32)
t.forward(60)
tree(100, t)
myWin.exitonclick()
运行结果如下: