一 相关知识
1 choice()函数
- 描述:choice() 方法返回一个列表,元组或字符串的随机项。
- 语法:choice()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法。
- import random
- random.choice( seq )
- 参数:seq -- 可以是一个列表,元组或字符串。
- 返回值:返回随机项。
2 abs()函数
- 描述:abs() 函数返回数字的绝对值。
- 语法:abs( x )
- 参数:x -- 数值表达式。
- 返回值:函数返回x(数字)的绝对值。
3 turtle.dot(size=None, *color)方法
- 功能:绘制一个直径为 size,颜色为 color 的圆点。如果 size 未指定,则直径取 pensize+4 和 2*pensize 中的较大值。
4 turtle.undo()方法
- 撤消 (或连续撤消) 最近的一个 (或多个) 海龟动作。可撤消的次数由撤消缓冲区的大小决定。
5 turtle.write(arg,move=False,align="left",font=("Arial",8,"normal"))
- 描述:书写文本
- 参数:
- arg -- 要书写到 TurtleScreen 的对象, 书写指定的字符串 - 到当前海龟位置
- move -- True/False,如果 move 为 True,画笔会移动到文本的右下角。默认 move 为
False
。 - align -- 指定对齐方式 ("left", "center" 或 right")
- font -- 一个三元组 (fontname, fontsize, fonttype),表示不同的字体
二 代码结构分析
1 游戏环境的创建:画出背景世界/迷宫,即吃豆人和幽灵的活动范围
主要思路:定义了一个地图列表tiles,通过判断地图列表中元素的值是否为1来画出背景世界的基本框架。代码内容包括两部分:square()函数部分和world函数部分,其中列表索引值和地图坐标之间的转换感觉很奇妙,我想不出来。。。
2 吃豆人和幽灵移动部分
主要思路:
首先需要画出吃豆人和幽灵,调用turtle库中的dot函数就可以了
然后是吃豆人和幽灵的移动过程,都是通过改变他们的坐标来实现的,因为吃豆人和幽灵都是在我们画出的背景世界里面移动的,所以要定义一个函数valid()判断其坐标是否有效,即是否在背景世界范围内;
还有一部分是统计吃豆人在移动过程中吃到了多少个豆子,这部分内容是通过统计改变tiles元素值得次数得到的,然后再利用turtle库的write函数将统计得到的数值写入字典state中。
3 游戏结束判断
吃豆人碰到幽灵即为游戏结束的标志,实际过程中通过判断两者坐标的差值是否小于20个坐标单位即可实现。
三 代码
1 """Pacman,classic arcade game. 2 3 Exercises 4 5 1. Change the board. 6 2. Change the number of ghosts. 7 3. Change where pacman starts. 8 4. Make the ghosts faster/slower. 9 5. Make the ghosts smaster. 10 11 """ 12 13 from random import choice # 导入choice函数,随机返回可迭代对象中任意一个元素值 14 from turtle import * # 导入海龟库 15 from freegames import floor,vector # 导入floor函数和vector函数,floor应该表示一种数学运算,类似于取余的逆操作 16 17 state = {'score':0} # 定义字典变量用来统计被吃掉的豆子数 18 path = Turtle(visible=False) # 用来画出吃豆人活动的世界,游戏中的蓝色部分 19 writer = Turtle(visible=False) # 用来将豆子数写入state字典中的画笔 20 aim = vector(5,0) # 用来改变吃豆人的坐标 21 pacman = vector(-40,-80) # 用来表示吃豆人的坐标 22 ghosts = [ 23 [vector(-180,160),vector(5,0)], 24 [vector(-180,-160),vector(0,5)], 25 [vector(100,160),vector(0,-5)], 26 [vector(100,-160),vector(-5,0)], 27 ] # 用来表示四个幽灵的初始坐标 28 tiles = [ 29 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 31 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 32 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 33 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 34 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 35 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 36 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 37 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 38 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 39 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 40 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 41 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 42 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 43 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 44 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 45 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 46 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 47 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49 ] # 用来表示地图坐标,"1"表是在该位置画出一个蓝色的正方形,"0"表示不画 50 51 # 用来画出背景世界的元素,一个边长为20个坐标单位的正方形 52 def square(x,y): # 用来画一个大小为20的正方形 53 "Draw square using path at (x,y)." 54 path.up() # 提起画笔 55 path.goto(x,y) # 将画笔移动到坐标为(x,y)处 56 path.down() # 放下画笔 57 path.begin_fill() # 开始填充图形 58 59 for count in range(4): # 生成一个包含[0,1,2,3]的序列并遍历 60 path.forward(20) # 画笔向前移动20个坐标单位 61 path.left(90) # 画笔方向逆时针旋转90度 62 63 path.end_fill() # 结束填充 64 65 # 实现坐标和地图列表索引值得切换 66 def offset(point): 67 "Return offset of point in titles." 68 x = (floor(point.x,20) + 200)/20 69 y = (180 - floor(point.y,20))/20 70 index = int(x + y * 20) 71 return index 72 73 # 判断吃豆人和幽灵移动的下一个坐标是否在背景世界内 74 def valid(point): # 判断坐标是否在地图坐标中 75 "Return True if point is valid in tiles." 76 index = offset(point) # 调用offset函数,得到index的值,即tiles列表的索引值 77 78 if tiles[index] == 0: # 如果索引index对应的列表titles的元素值为0 79 return False # 返回False 80 81 index = offset(point + 19) # 移动到下一个坐标 82 83 if tiles[index] == 0: # 再次判断 84 return False 85 86 return point.x % 20 == 0 or point.y % 20 == 0 # 当坐标在背景世界范围内,返回1 87 88 # 游戏环境的创建 89 def world(): # 画出背景世界 90 "Draw world using path." 91 bgcolor('black') # 将背景世界的填充颜色设置为黑色 92 path.color('blue') # 将画笔的填充颜色设置为蓝色 93 94 for index in range(len(tiles)): # 生成一个长度为len(tiles)的列表,并利用index遍历,将index看作列表tiles的索引 95 tile = tiles[index] # 读取index索引对应的列表tiles元素的值 96 97 if tile > 0: # 如果列表元素值为"1" 98 x = (index % 20) * 20 - 200 # 通过index得到路径path的横坐标 99 y = 180 - (index // 20) * 20 # 通过index得到路径path的纵坐标 100 square(x,y) # 调用square函数,画出正方形 101 if tile == 1: # 如果列表元素值为1 102 path.up() # 提起画笔 103 path.goto(x + 10,y + 10) # 画笔移动到(x+10,y+10)处 104 path.dot(2,'white') # 调用dot函数,画出一个直径为2个坐标单位的点,用来表示豆子 105 106 # 吃豆人和幽灵移动部分 107 def move(): # 控制吃豆人和幽灵的移动 108 "Move pacman and ghosts." 109 writer.undo() # 撤销writer画笔最近的海归/画笔动作(前面画笔颜色是白色) 110 writer.write(state['score']) # 设定画笔书写对象是字典state中的关键字'score'对应的值 111 112 clear() # 从屏幕中删除writer海龟/画笔的绘图。不移动海龟/画笔。海龟的状态和位置以及其他海龟的绘图不受影响。 113 114 # 吃豆人移动操作 115 if valid(pacman + aim): # 调用valid函数,如果函数返回值为真(即该坐标在可移动的范围内) 116 pacman.move(aim) # 调用move函数,让吃豆人移动到坐标(pacman.x+aim.x,pacman.y+aim.y)处 117 118 index = offset(pacman) # 计算pacman新坐标对应的索引值 119 120 # 统计吃到的豆子的部分 121 if tiles[index] == 1: # 如果index索引对应的列表元素值为1 122 tiles[index] = 2 # 将index索引对应的值更新为2,表示该坐标的豆子已经被吃掉了 123 state['score'] += 1 # 更新分数:每吃一颗豆子,分数加1 124 x = (index % 20) * 20 - 200 # 将index索引值转化为坐标 125 y = 180 - (index // 20) * 20 # 将index索引值转化为坐标 126 square(x,y) # 调用square()函数将世界恢复原样 127 128 # 画出吃豆人 129 up() # 画笔拿起操作 130 goto(pacman.x + 10,pacman.y + 10) # 更新吃豆人的位置,移动到(pacman.x + 10,pacman.y + 10)处 131 dot(20,'yellow') # 画出一个黄色的直径为20个坐标单位的点,用来表示吃豆人 132 133 # 幽灵移动操作 134 for point,course in ghosts: # 遍历四个幽灵 135 if valid(point + course): # 判断幽灵的坐标是否有效,即在背景世界的活动范围里面 136 point.move(course) # 调用move函数,让幽灵移动到坐标(point.x+course.x,point.y+course.y)处 137 else: # 更新course的值,用来控制幽灵的移动 138 options = [ 139 vector(5,0), 140 vector(-5,0), 141 vector(0,5), 142 vector(0,-5), 143 ] # 定义一个列表,用来随机更新course的坐标 144 plan = choice(options) # 从options列表中的四个坐标中任意返回一个坐标 145 course.x = plan.x # 更新course的横坐标 146 course.y = plan.y # 更新course的纵坐标 147 148 # 画出幽灵 149 up() # 提起画笔操作 150 goto(point.x + 10,point.y + 10) # 将画笔移动到坐标(point.x + 10,point.y + 10)处 151 dot(20,'red') # 画出一个直径为20个坐标单位的点,用来表示幽灵 152 153 update() # 更新画布 154 155 # 判断游戏是否结束 156 for point,course in ghosts: # 判断游戏是否结束 157 if abs(pacman - point) < 20: # 如果吃豆人和幽灵碰到一块儿 158 return # 结束游戏 159 160 ontimer(move,100) # 每隔100ma重新调用move函数 161 162 # 实现通过键盘控制吃豆人的移动方向 163 def change(x,y): # 用来改变吃豆人的移动方向 164 "Change pacman aim if valid." 165 if valid(pacman + vector(x,y)): # 调用valid函数判断新的坐标是否在迷宫内 166 aim.x = x # 更新坐标 167 aim.y = y # 更新坐标 168 169 setup(420,420,370,0) # 初始化画布尺寸 170 hideturtle() # 隐藏鼠标/海龟 171 tracer(False) # 保证画图操作(食物和贪吃蛇身体)一次性完成 172 writer.goto(160,160) # 将writer画笔移动到坐标(160,160)处 173 writer.color('white') # 将画笔颜色初始化为白色 174 writer.write(state['score']) # 设定画笔书写对象是字典state中的关键字'score'对应的值 175 # 按键控制模块 176 listen() # 监听,屏幕聚焦 177 onkey(lambda:change(5,0),'Right') # 右转,Right是键盘方向键的右键,不是输入单词Right 178 onkey(lambda:change(-5,0),'Left') # 左转 179 onkey(lambda:change(0,5),'Up') # 向上走 180 onkey(lambda:change(0,-5),'Down') # 向下走 181 world() # 调用world函数,画出背景世界/迷宫 182 move() # 调用move函数,开始游戏 183 done()