少儿编程python课件:停车场模拟停车,纯turtle实现.
温馨提示
本教程素材已更新至公众号菜单栏【素材】
如果文章对您有一点点帮助,点
、点
,万分感谢
运行简单版,弹出来一个深灰色窗口,两排停车位,中间一条虚黄线马路,花坛点缀在边上,停车位里已经停了十几辆形状各异的汽车,空了几个位置。每次运行,停的是哪几辆、空哪几个位,都不一样。
运行动态版,多出来一个方向盘图标和一个方向指示。玩家那辆车停在路上,按方向键,车头转,车身跟着转,方向盘也跟着动。按上下键前进后退。左下角有个重置按钮,点一下车回原位。
两个版本用的是同一套底层逻辑,区别在于动态版把键盘操作接进去了,车的转向还用了一套 180 张图切换的方案来模拟平滑旋转。这个方案值得细看。
停车位是怎么画出来的
draw_space 是个挺有意思的函数,画的不是完整的矩形,是一个三面封口、一面开口的停车格。
foriinrange(4):ifi%2==0:forward(5)ifi==f: penup()forward(w-10)pendown()forward(5)else:forward(h)right(90)
四条边,每次 right(90) 转一次,走一圈。偶数边(0、2)是短边,宽60;奇数边(1、3)是长边,高100。
参数 f 控制哪条短边开口。f=0 的时候第0条边(上边)中间抬笔,画出来的停车格开口朝上;f=2 的时候第2条边(下边)中间抬笔,开口朝下。两排停车位朝向相反,一排面向马路上方,一排面向马路下方,靠 f 这个参数切换。
开口的实现方式是:先走5像素画一小段,penup(),再走50像素的空白,pendown(),再走5像素收尾。不用画两条短线,一条线中途抬笔降笔搞定,省了不少坐标计算。
每画完一格,x += w,画笔横移到下一格起点,循环10次,10个格子连着出来。
画完之后,每个格子的左上角和右下角坐标存进 list_space_xy,后面放车要用到:
list_space_xy.append((x, y, x+w, y-h))
图片是怎么变成 turtle 画笔图标的
turtle 本来只能画几何形状,把图片用进来靠的是 register_shape():
foriinlist_shape:register_shape(i)
注册完之后,Pen 对象调 shape("car0.gif") 就能把那个 Pen 的外观换成图片。这个 Pen 本身不画线,penup() 之后纯粹当一个可移动的图片容器用。
动态版要注册的图片更多。车的旋转是每隔2度一张图,0度到358度,一共180张:
foriinrange(0, 360, 2):register_shape(f".\\car\\car_{i}.gif")方向盘从 -45 度到 45 度,共91张:
foriinrange(-45, 46):register_shape(f".\\方向盘\\方向盘_{i}.gif")程序启动的时候把这些图全注册进去,之后切换就是改字符串,很快。
随机停车怎么实现
n = random.randint(10, 19)list1 = random.sample([iforiinrange(20)], n)
random.randint(10, 19) 先决定停几辆,10到19之间随机取一个。
random.sample 从20个车位编号里不重复地抽 n 个,保证每个车位最多停一辆。如果用 random.randint 循环抽,可能抽到重复的,同一个车位叠两辆车。sample 是专门解决这个"不放回抽样"问题的,一行搞定。
抽到车位编号之后,用之前存的坐标列表算出每辆车的摆放位置:
x = list_space_xy[i][0] +30y = list_space_xy[i][1] -50
停车位左上角的 x 加30,y 减50,大约是这个格子的中间位置。偏移量是根据图片大小手工调出来的,不是公式。
方向盘和车身怎么做到同步的
按左键,turn_left() 被触发:
defturn_left():globalshape_num, is_turning, shape_num_p3ifnotis_turning:is_turning = Truere_p3(f='left')shape_num += 2ifshape_num>358: shape_num = 0shape_num_p3 += 2ifshape_num_p3>45: shape_num_p3 = 45set_h()is_turning = False
shape_num 记录车当前朝向的角度,每次左转加2,超过358绕回0。
shape_num_p3 记录方向盘的偏转角度,范围被限制在 -45 到 45 之间,转到头就不再加了。
set_h() 用这两个数字更新图片:
def set_h(): p_car.setheading(shape_num) p_car.forward(2 * direction) p_car.shape(f".\\car\\car_{shape_num}.gif") p3.shape(f".\\方向盘\\方向盘_{shape_num_p3}.gif")
先设方向,再前进2像素(direction 是1或-1,控制前进还是后退),然后把车和方向盘的图片都换成当前角度对应的那张。
松开按键之后不会立刻归中,而是靠 re_p3() 慢慢把方向盘转回来:
def re_p3(f='m'): if f == "right" or f == 'm': while shape_num_p3 >= 2: shape_num_p3 -= 2 p3.shape(f".\\方向盘\\方向盘_{shape_num_p3}.gif") if f == "left" or f == 'm': while shape_num_p3 <= -2: shape_num_p3 += 2 p3.shape(f".\\方向盘\\方向盘_{shape_num_p3}.gif")
每次按键触发之前先调一次 re_p3(),把方向盘归中,然后再偏向新方向。视觉上方向盘在归中和偏转之间来回动,有一点点真实感。
is_turning 这个标志是做什么的
if not is_turning: is_turning = True ... is_turning = False
键盘长按的时候,onkeypress 会高频率重复触发函数。如果上一次转向还没执行完,新的触发又进来了,两次更新互相干扰,图片可能乱跳。
is_turning 是一把简单的锁:进入函数第一件事是检查锁,如果已经是 True 就直接返回,不往下走;执行完了再解锁,改回 False。这种模式叫"重入保护",防止同一段代码同时跑两次。
这里用全局变量来做锁,因为代码量不大,够用了。更复杂的程序里会用线程锁或者其他机制,原理是一样的。
tracer(0) 干什么用的
turtle 默认每画一步都刷新一次屏幕,肉眼能看到线一点一点延伸。画20个停车格,这个过程会很慢,闪来闪去。
tracer(0) 关掉自动刷新,画完之后调一次 update() 统一刷新,用户看到的就是直接画好的结果,没有过程。
动态版用了另一个方式:初始化时 tracer(0),画完所有静态内容之后改回 tracer(1):
tracer(0)# ... 绘制车位、花坛tracer(1)
tracer(1) 之后的操作(键盘交互时移动汽车)会正常刷新,玩家能看到车在动。静态场景快速渲染,动态交互正常显示,两段分开处理。
stamp() 和 Pen.goto() 的区别
花坛的代码用了 stamp():
for i in range(n): p_flower.stamp() p_flower.forward(90)
stamp() 是把当前图标在当前位置"盖个章",留下一个图案印记,自己继续往前走。这和 Pen 对象本身不同——Pen 是一个可以移动的对象,stamp 留下的印记是静态的,不能单独控制。
花坛不需要移动,只需要复制摆放,用 stamp 比创建20个 Pen 对象省内存,也省代码。
几个可以自己改的地方
停车位数量目前是两行各10个,一共20个。draw_space 循环10次,改成8次就是每行8个,车位数量也要对应改,不然随机抽样会越界。
随机停车数量是 random.randint(10, 19),范围在10到19之间,最少停一半。改成 random.randint(5, 15) 就能出现更多空位,练习停车更容易。
动态版每次转向前进2像素,p_car.forward(2 * direction) 这个2改大,车每按一次跑得更远;改小,精度更高,适合练停进窄车位。
最后
这个程序有个设计挺典型:用编号而不是坐标来管对象。停车位存进 list_space_xy,随机抽的是编号,编号对应坐标,摆车的时候再查坐标。抽样的逻辑和坐标计算分开了,改其中一个不影响另一个。
另一个值得注意的是图片切换这个方案——180张图逐帧替换来模拟旋转,不是实时计算旋转角度再渲染。这是个取巧的方法,绕过了 turtle 不支持图片旋转的限制。图片数量多,但每次切换只是改一个字符串,实际运行并不慢。
碰到工具的限制,不一定要换工具,绕过去也是一条路。