Mind+Python turtle 课程——09 弹球小游戏
课程目录:
功能介绍
上节课我们通过控制键盘的左右方向来控制小拦板,从而实现弹球小游戏的功能。这节课我们将继续使用键盘来制作贪吃蛇小游戏。首先我们先在屏幕内实现移动的小蛇,之后增加食物角色,当小蛇吃到食物时会增长相应的尺寸,最后增加游戏胜负的判断功能,从而实现贪吃蛇游戏效果。课程会分为三个任务的形式进行,下表为课程任务及涉及到的知识点。
实现功能 | 知识点 | | 巩固学习turtle库函数、列表索引以及学会freegame库函数的使用 | | 巩固学习随机函数、逻辑判断语句、for循环语句和作用域的认识 | | |
项目实践
任务一:移动的蛇
1、任务分析
贪吃蛇的身体是由list(列表)构成的,list中每一个元组代表贪吃蛇在棋盘上的坐标,我们只需在每个坐标上都画上图案,就能制作出一条贪吃蛇来。关于列表的内容可以查看第五节课“模拟3D星空”,只不过这里是坐标点。运动如下图。
没有删除最后一个坐标时贪吃蛇的运动过程
那我们如何让贪吃蛇活蹦乱跳,我们就要写一个move函数。怎么让蛇动起来呢?
其实很简单,我们只需删除list中最后一个坐标,再在蛇头部分插入新的位置。运动如下图(只能看到蛇头在运动)。
移动前 移动后
如何确定新的位置呢,我们就要设定贪吃蛇移动的方向(x,y),将原蛇头位置的坐标在移动方向上进行加减操作。如下图所示。
如果设置初始的坐标为(0,-10),其中x代表贪吃蛇左右移动,y的值代表上下移动。那么当触发向上移动时,假设向上触发得条件为x坐标不变,y坐标加10(具体得数值都可以自己设定)。两个坐标向量进行加法操作,那么会得到(0,0)。这样贪吃蛇就从初始坐标(0,-10)向坐标为(0,0)的点移动了。
2、编写程序
注意在编写程序时,需先安装freegames库,这里我们通过pip模式安装。
import turtle# 导入绘图海龟模块 from freegames import vector,square#导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake snake = [vector(10, 0)]# 初始化snake,而且snake是列表,其元素是向量 position = vector(0, -10) # 用来控制贪吃蛇的移动方向和距离 def change(x, y):# 改变贪吃蛇移动方向部分,通过onkey函数和lambda表达式调用change函数,获得下一个移动坐标(x,y) position.x = x# 将x的值赋给position.x改变贪吃蛇目标的横坐标 position.y = y# 将y的值赋给position.y改变贪吃蛇目标的纵坐标 def move():# 可以让蛇移动的方法 head = snake[-1].copy()# 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头 head.move(position)# 移动一步 turtle.clear()# 清空蛇走过的位置 snake.append(head)# 更新蛇的身体 snake.pop(0)# 移除蛇走过的坐标 for body in snake:# 循环遍历蛇的坐标 square(body.x, body.y, 9, 'black') # 绘制黑色蛇 turtle.update() turtle.ontimer(move, 100)# 定时执行move函数 if __name__ == '__main__':# 主程序 turtle.setup(420, 420, 370, 0)# 创建窗体大小 turtle.hideturtle()# 隐藏箭头显示 turtle.tracer(False)# 关闭绘画效果 move()# 调用让蛇移动的方法 turtle.listen()# 事件*** turtle.onkey(lambda: change(10, 0), 'Right')# 按键盘右键,蛇向右走 turtle.onkey(lambda: change(-10, 0), 'Left')# 按键盘左键,蛇向左走 turtle.onkey(lambda: change(0, 10), 'Up')# 按键盘上键,蛇向上走 turtle.onkey(lambda: change(0, -10), 'Down')# 按键盘下键,蛇向下走 turtle.done()# 停止画笔绘制,但绘图窗体不关闭 | 3、运行效果
运行此段程序,可以看到在弹出的窗口中,小蛇在上面移动,我们通过键盘可以控制其移动的方向。
4、代码释义
4.1代码中freegames库函数的使用
from freegames import vector,square#导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake |
4.1.1代码解释 导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake。
4.1.1freegames库 freegames是Apache2授权的免费Python游戏集合,用于教育和娱乐。这些游戏是用简单的Python代码编写的,是为实验和改变而设计的,代码少且易读并且有丰富的库函数方便调用。
4.2代码中turtle.tracer()函数 turtle.tracer(False)# 关闭绘画效果 |
4.2.1代码解释 启用/禁用海龟动画并设置刷新图形的延迟时间。如果没有关闭时会有snake的绘画过程(程序中的体现可以看到箭头在绘制原点),游戏实现效果会变差。因此这里我们选择关闭绘画过程。
4.2.2使用方法 turtle.tracer(n=None,delay=None),其中n -- 非负整型数;delay -- 非负整型数。如turtle.tracer(False)或者duturtle.tracer(0)表示为图形将一次性画好;turtle.tracer(1)为图形按照正常速度进行,可有可无;turtle.tracer(4)为在循环中,图形将一次画出4次循环的图。
4.3代码中列表索引 head = snake[-1].copy()# 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头 |
4.3.1代码解释 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头。
4.3.2索引 序列中的每个元素都有一个编号,也称为索引。这个索引是从0开始递增的,即下标为0表示第一个元素,下标为1表示第二个元素,依次类推,如下图所示。 索引下标:0 1 2 4 ... n-1 序列的正数索引 Python比较神奇,它的索引可以是负数。这个索引从右向左计数,也就是从最后的一个元素开始计数,即最后一个元素的索引值是-1,倒数第二个元素的索引值是-2,依次类推,如下图所示。 索引下标:-(n-1) -(n-2) -(n-3) ... -2 -1 序列的负数索引
任务二:实现吃食物功能
1、任务分析任务一中我们没有吃到食物是需要删除贪吃蛇尾部的元素,而这里当我们吃到食物时不需要删除贪吃蛇尾部的元素。既当贪吃蛇蛇头的坐标与食物的坐标重合的话,贪吃蛇就吃到了食物。如果贪吃蛇吃到了食物,就在棋盘上随机更新食物。如果随机生成的食物的坐标,恰好与贪吃蛇的位置重合的话,就继续随机产生坐标,直到确保与贪吃蛇的坐标不同的时候。
2、编写程序import turtle# 导入绘图海龟模块 from freegames import vector,square#导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake from random import randrange#导入随机函数的randrange工具,使food随机出现 snake = [vector(10, 0)]# 初始化snake,而且snake是列表,其元素是向量 position = vector(0, -10) # 用来控制贪吃蛇的移动方向和距离 food = vector(0,0)# 初始化food变量,用来表示第一个食物在坐标为(0,0)处 def change(x, y):# 改变贪吃蛇移动方向部分,通过onkey函数和lambda表达式调用change函数,获得下一个移动坐标(x,y) position.x = x# 将x的值赋给position.x改变贪吃蛇目标的横坐标 position.y = y# 将y的值赋给position.y改变贪吃蛇目标的纵坐标 def move():# 可以让蛇移动的方法 head = snake[-1].copy()# 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头 head.move(position)# 移动一步 turtle.clear()# 清空蛇走过的位置 snake.append(head)# 更新蛇的身体 if head == food:# 如果蛇吃到食物 print('Snake:', len(snake))# 根据蛇的长度进行加分 food.x = randrange(-15, 15) * 10 # 随机生成食物x坐标 food.y = randrange(-15, 15) * 10 # 随机生成食物y坐标 else: snake.pop(0)# 移除蛇走过的坐标 turtle.clear()# 清空蛇走过的位置 for body in snake:# 循环遍历蛇的坐标 square(body.x, body.y, 9, 'black') # 绘制黑色蛇 square(food.x, food.y, 9, 'green')# 绘制绿色食物 turtle.update() turtle.ontimer(move, 100)# 定时执行move函数 if __name__ == '__main__':# 主程序 turtle.setup(420, 420, 370, 0)# 创建窗体大小 turtle.hideturtle()# 隐藏箭头显示 turtle.tracer(False)# 关闭绘画效果 move()# 调用让蛇移动的方法 turtle.listen()# 事件*** turtle.onkey(lambda: change(10, 0), 'Right')# 按键盘右键,蛇向右走 turtle.onkey(lambda: change(-10, 0), 'Left')# 按键盘左键,蛇向左走 turtle.onkey(lambda: change(0, 10), 'Up')# 按键盘上键,蛇向上走 turtle.onkey(lambda: change(0, -10), 'Down')# 按键盘下键,蛇向下走 turtle.done()# 停止画笔绘制,但绘图窗体不关闭 | 3、运行效果运行此段程序,运行此段程序,可以看到在弹出的窗口中,小蛇在上面移动,我们通过键盘可以控制其移动的方向。此时如果吃到食物时,贪吃蛇的长度会加长,并且在屏幕任意位置又会重新出现新的食物。
4、代码释义
4.1代码中if __name__ == '__main__'的使用 if __name__ == '__main__':# 主程序 |
4.1.1代码解释 主程序函数,模块可以通过检查自己的 __name__ 来得知是否运行在 main 作用域中,这使得模块可以在作为脚本运行时条件性地执行一些代码,而在被 import 时不会执行。
4.1.2作用域 通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
任务三:完成贪吃蛇游戏
1、任务分析首先我们的贪吃蛇不能朝着当前移动方向的反方向移动,体现在代码中,就是当前方向与改变方向的乘积不能为负值。其次是如果贪吃蛇蛇头的坐标与边框的坐标重合的话,蛇卒。如果贪吃蛇各个部分的坐标有重合的话,就说明贪吃蛇咬到了自己,游戏结束。
2、编写程序from random import randrange#导入随机函数的randrange工具,使food随机出现 from freegames import vector,square#导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake import turtle# 导入绘图海龟模块 food = vector(0,0)# 初始化food变量,用来表示第一个食物在坐标为(0,0)处 snake = [vector(10, 0)]# 初始化snake,而且snake是列表,其元素是向量 position = vector(0, -10) # 用来控制贪吃蛇的移动方向和距离 def change(x, y):# 改变贪吃蛇移动方向部分,通过onkey函数和lambda表达式调用change函数,获得下一个移动坐标(x,y) position.x = x# 将x的值赋给position.x改变贪吃蛇目标的横坐标 position.y = y# 将y的值赋给position.y改变贪吃蛇目标的纵坐标 def is_inside(head):# 判断蛇头是否在窗体内 return -200 < head.x < 190 and -200 < head.y < 190 def move():# 可以让蛇移动的方法 head = snake[-1].copy()# 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头 head.move(position)# 移动一步 if not is_inside(head) or head in snake:# 如果蛇的头部位于边界外或者蛇头在蛇的身体中 square(head.x, head.y, 9, 'red')# 绘制红色蛇头,说明游戏结束 turtle.update()# 更新 return# 用来终止程序的运行,其后语句不会再执行 snake.append(head)# 更新蛇的身体 if head == food:# 如果蛇吃到食物 print('Snake:', len(snake))# 根据蛇的长度进行加分 food.x = randrange(-15, 15) * 10 # 随机生成食物x坐标 food.y = randrange(-15, 15) * 10 # 随机生成食物y坐标 else: snake.pop(0)# 移除蛇走过的坐标 turtle.clear()# 清空蛇走过的位置 for body in snake:# 循环遍历蛇的坐标 square(body.x, body.y, 9, 'black') # 绘制黑色蛇 square(food.x, food.y, 9, 'green')# 绘制绿色食物 turtle.update() turtle.ontimer(move, 100)# 定时执行move函数 if __name__ == '__main__':# 主程序 turtle.setup(420, 420, 370, 0)# 创建窗体大小 turtle.hideturtle()# 隐藏箭头显示 turtle.tracer(False)# 关闭绘画效果 move()# 调用让蛇移动的方法 turtle.listen()# 事件*** turtle.onkey(lambda: change(10, 0), 'Right')# 按键盘右键,蛇向右走 turtle.onkey(lambda: change(-10, 0), 'Left')# 按键盘左键,蛇向左走 turtle.onkey(lambda: change(0, 10), 'Up')# 按键盘上键,蛇向上走 turtle.onkey(lambda: change(0, -10), 'Down')# 按键盘下键,蛇向下走 turtle.done()# 停止画笔绘制,但绘图窗体不关闭 | 3、运行效果基于任务二的条件下,当小蛇碰到边框或者朝着当前移动方向的反方向移动时,绘制出红色蛇头,说明游戏结束。
4、代码释义
4.1代码中lambda库函数的使用 turtle.listen()# 事件*** turtle.onkey(lambda: change(10, 0), 'Right')# 按键盘右键,蛇向右走 turtle.onkey(lambda: change(-10, 0), 'Left')# 按键盘左键,蛇向左走 turtle.onkey(lambda: change(0, 10), 'Up')# 按键盘上键,蛇向上走 turtle.onkey(lambda: change(0, -10), 'Down')# 按键盘下键,蛇向下走 |
4.1.1代码解释 监听键盘事件,当键盘按下相应的按键时执行相应的功能,相对于前面小海龟抓鱼的课程程序,这是一种缩写方式,在这里我们省略掉了新建键盘控制函数的过程。
4.1.2lambda函数 lambda函数又称匿名函数,是指没有名字的函数,应用在需要一个函数但是又不想费神去命名这个函数的场合。通常情况下,这样的函数只使用一次。
项目小结通过本节课程的学习,我们巩固了urtle库函数、学会了列表索引以及freegame库函数的使用,制作了移动的蛇的任务。之后利用随机函数、逻辑判断语句、for循环语句和作用域实现了小蛇吃食物的功能。最后通过键盘控制函数和lambda函数实现了贪吃蛇游戏的功能制作。
项目扩展尝试增加游戏开始和难度选择功能,可以查看之前课程的程序进行设计。
参考程序from random import randrange#导入随机函数的randrange工具,使food随机出现 from freegames import vector,square#导入freegames的向量函数与绘图函数,用square正方形表示food、snake,用vector这个二维向量表示snake import turtle# 导入绘图海龟模块 from time import sleep#导入时间库sleep函数 screen = turtle.Screen()#赋值screen对象 #产生一个输入难度的对话框 difficulty=screen.numinput("难度","请输入游戏的难度(1~3)",minval=1,maxval=3) food = vector(0,0)# 初始化food变量,用来表示第一个食物在坐标为(0,0)处 snake = [vector(10, 0)]# 初始化snake,而且snake是列表,其元素是向量 position = vector(0, -10*difficulty) # 用来控制贪吃蛇的移动方向和距离 def change(x, y):# 改变贪吃蛇移动方向部分,通过onkey函数和lambda表达式调用change函数,获得下一个移动坐标(x,y) position.x = x# 将x的值赋给position.x改变贪吃蛇目标的横坐标 position.y = y# 将y的值赋给position.y改变贪吃蛇目标的纵坐标 def is_inside(head):# 判断蛇头是否在窗体内 return -200 < head.x < 190 and -200 < head.y < 190 def move():# 可以让蛇移动的方法 head = snake[-1].copy()# 确定蛇头的位置,表示复制snake列表的最后一个元素,这里用列表的最后一个元素表示蛇头 head.move(position)# 移动一步 if not is_inside(head) or head in snake:# 如果蛇的头部位于边界外或者蛇头在蛇的身体中 square(head.x, head.y, 9, 'red')# 绘制红色蛇头,说明游戏结束 turtle.write("game over",font=("Arial",40,"normal"))#显示失败 turtle.update()# 更新 return# 用来终止程序的运行,其后语句不会再执行 snake.append(head)# 更新蛇的身体 if head == food:# 如果蛇吃到食物 count=len(snake) count=count+1#计次 print('Snake:', count)# 根据蛇的长度进行加分 if count == 10*difficulty:#到达规定的分数值 turtle.write("victory!",font=("Arial",40,"normal"))#显示失败 turtle.exitonclick()#点击退出 food.x = randrange(-15, 15) * 10 # 随机生成食物x坐标 food.y = randrange(-15, 15) * 10 # 随机生成食物y坐标 sleep(0.01)#推迟0.01秒再执行 else: snake.pop(0)# 移除蛇走过的坐标 turtle.clear()# 清空蛇走过的位置 for body in snake:# 循环遍历蛇的坐标 square(body.x, body.y, 9, 'black') # 绘制黑色蛇 square(food.x, food.y, 9, 'green')# 绘制绿色食物 turtle.update() turtle.ontimer(move, 100)# 定时执行move函数 if __name__ == '__main__':# 主程序 turtle.setup(420, 420, 370, 0)# 创建窗体大小 turtle.hideturtle()# 隐藏箭头显示 turtle.tracer(False)# 关闭绘画效果 move()# 调用让蛇移动的方法 turtle.listen()# 事件*** turtle.onkey(lambda: change(10, 0), 'Right')# 按键盘右键,蛇向右走 turtle.onkey(lambda: change(-10, 0), 'Left')# 按键盘左键,蛇向左走 turtle.onkey(lambda: change(0, 10), 'Up')# 按键盘上键,蛇向上走 turtle.onkey(lambda: change(0, -10), 'Down')# 按键盘下键,蛇向下走 turtle.done()# 停止画笔绘制,但绘图窗体不关闭 |
参考效果附件:代码 |