一、概述
1. 实现的功能:
受网络视频的启发,出于检验近期学习效果的目的,使用python自带的turtle库写了一个贪吃蛇小游戏:
- 画布为500*500个像素
- 边长为10的正方形绘制一个block,作为构成蛇和苹果的基本单位。
- 模块主要包含一个蛇的class。定义了蛇的初始状态、运动方法、运动方向、是否撞到等。
- 函数模块主要有:画block、生成苹果、游戏循环逻辑
实现的功能有:
- 苹果随机出现,且当苹果被吃掉时消失,再随机生成一个苹果
- 蛇的初始长度为5个block,初始方向向东。
- 蛇通过WSAD控制方向
- 当蛇吃掉一个苹果后,身体会增加一个block
- 当蛇撞墙或者撞到身体后游戏结束
逻辑图表达出来大致如下:
2.遗留问题:
部分问题暂时没有好的解决思路暂时搁置
- 蛇可以后退, 但是会被判定撞身体,从而游戏结束。依托于当前turtle.onkey的方法暂时没有想到好办法解决。
- 计分和重置游戏功能暂未加入。(懒虫上脑)
二、遇到的问题及解决思路
1. 如何修改类属性
首先,蛇的运动和方向是基于对类属性修改实现的。于是就涉及到对类属性进行修改的操作。
正常的类方法调用通常会使用(注意类后面的括号):
HungrySnake().draw_snake()
HungrySnake()代表对象已经实例化。如果未实例化调用方法时是会报错的。
但是如果用相同的方法调用这个类的属性时,如下:
HungrySnake().x_add
则会先生成一个实例,然后再在实例中添加一个属性。而此时类的属性依然不变。这种情况下不满足功能。
正常需要修改类属性需要去掉类的括号,如下:
def change_direction(self, x_lab, y_lab):
HungrySnake.x_add = x_lab
HungrySnake.y_add = y_lab
2. lambda的特殊用法
在介绍这个场景之前先说一下turtle.onkey()这个方法,引用 文档② 的内容:
监听键盘按键:onkey/onkeyrelease/onkeypress
turtle.onkey(fun, key)
turtle.onkeyrelease(fun, key)
turtle.onkeypress(fun, key=None)
Parameters:
fun – a function with no arguments or None
key – a string: key (e.g. “a”) or key-symbol (e.g. “space”)
就是说onkey的第一个方法需要是一个函数(fun)。
很奇怪的是,当我们直接调用下面这个方法时,得到的对象并不是一个函数,其类型为None,不能直接用于传参。
HungrySnake().change_direction(-10, 0)
需要使用lambda: 来将其转换成一个function。见下图中的F1和F2。
而lambda 语法: lambda [arg1[,arg2,arg3....argN]]:expression
即冒号前面跟需要传入的参数,后面跟需要return的expression。
三、附录
完整代码如下:
import turtle as tt
import random
x_apple = random.randrange(-24, 24) * 10
y_apple = random.randrange(-23, 24) * 10
eat_sym = 0
class HungrySnake:
# 蛇的初始运动矢量
x_add = 10
y_add = 0
# 蛇的初始状态, 用坐标表示
snake = [[0, 0], [10, 0], [20, 0], [30, 0], [40, 0], [50, 0]]
# 以蛇的坐标为顶点画方块
def draw_snake(self):
for each in self.snake:
square_spot(each[0], each[1], 10, 'black')
# 运动方式: pop队首, append队尾
def move_a_step(self):
self.snake.pop(0)
self.snake.append([self.snake[-1][0] + self.x_add, self.snake[-1][1] + self.y_add])
# 改变运动方向矢量方法, 用于后面响应按键
def change_direction(self, x_lab, y_lab):
HungrySnake.x_add = x_lab
HungrySnake.y_add = y_lab
def is_crash(self):
# 判断是否撞墙
if \
self.snake[-1][0] <= -250 or self.snake[-1][0] >= 240 or self.snake[-1][1] <= -240 or self.snake[-1][
1] >= 260:
return True
# 判断是否撞身体
elif self.snake[-1] in self.snake[:-1]:
return True
else:
False
# 吃到苹果, 在append
def eat_apple(self):
self.snake.append(self.snake[-1])
def square_spot(x, y, width, color):
tt.up()
tt.goto(x, y)
tt.down()
tt.color(color) # 设定填充颜色
tt.begin_fill() # 申明开始填充
# 开始画图
tt.forward(width)
tt.right(90)
tt.forward(width)
tt.right(90)
tt.forward(width)
tt.right(90)
tt.forward(width)
tt.right(90)
tt.end_fill() # 申明填充结束
tt.up()
def show_apple():
global x_apple, y_apple
if HungrySnake().snake[-1] == [x_apple,y_apple]:
x_apple = random.randrange(-24, 24) * 10
y_apple = random.randrange(-23, 24) * 10
HungrySnake().eat_apple()
else:
square_spot(x_apple, y_apple, 10, 'red')
print('apple: %s' % [x_apple,y_apple])
def game_loop():
tt.clear()
show_apple()
HungrySnake().draw_snake()
HungrySnake().move_a_step()
print('snake: %s' % HungrySnake().snake)
print('speed: %s' % [HungrySnake().x_add, HungrySnake().y_add])
print('Crash: %s' % HungrySnake().is_crash())
if not HungrySnake().is_crash():
tt.ontimer(game_loop, 100)
else:
square_spot(HungrySnake().snake[-1][0], HungrySnake().snake[-1][1], 10, 'red')
tt.done()
tt.setup(500, 500)
tt.hideturtle()
tt.tracer(False)
tt.listen()
tt.onkey(lambda: HungrySnake().change_direction(-10, 0), 'a')
tt.onkey(lambda: HungrySnake().change_direction(10, 0), 'd')
tt.onkey(lambda: HungrySnake().change_direction(0, 10), 'w') # 不加lambda, 后面的对象类型是None. 加lambda之后才是函数
tt.onkey(lambda: HungrySnake().change_direction(0, -10), 's')
game_loop()
tt.done()