20241216 实验四《Python程序设计》实验报告
20241216 2024-2025-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2412
姓名: 曾楷
学号:20241216
实验教师:王志强
实验日期:2025年5月14日
必修/选修: 公选课
一.实验内容
设计一个贪吃蛇的程序,实现移动、计分、自主生成障碍物等等
贪吃蛇1.0视频
二.实验过程及结果
1.初始代码
import turtle
import time
#设置游戏窗口
wn = turtle.Screen()
wn.title("贪吃蛇游戏")
wn.bgcolor("black")
wn.setup(width=600, height=600)
wn.tracer(0)#关闭窗口自动刷新
#蛇头
head = turtle.Turtle()
head.speed(0) #直接显示蛇头
head.shape("square")
head.color("red","yellow")
head.penup()
head.goto(0, 0)
head.setheading(90)
head.direction = "stop"
#定义移动函数
def go_up():
if head.direction != "down":
head.direction = "up"
def go_down():
if head.direction != "up":
head.direction = "down"
def go_left():
if head.direction != "right":
head.direction = "left"
def go_right():
if head.direction != "left":
head.direction = "right"
def move():
if head.direction == "up":
y = head.ycor()
head.sety(y + 20)
if head.direction == "down":
y = head.ycor()
head.sety(y - 20)
if head.direction == "left":
x = head.xcor()
head.setx(x - 20)
if head.direction == "right":
x = head.xcor()
head.setx(x + 20)
#键盘绑定
wn.listen()
wn.onkeypress(go_up, "w")
wn.onkeypress(go_down, "s")
wn.onkeypress(go_left, "a")
wn.onkeypress(go_right, "d")
wn.onkeypress(go_up, "Up")
wn.onkeypress(go_down, "Down")
wn.onkeypress(go_left, "Left")
wn.onkeypress(go_right, "Right")
#主游戏循环
def game():
move()
wn.update()
wn.ontimer(game, 50)
game()
wn.mainloop()
我想对游戏界面制作进行一定的整理
因为上课没有讲相关内容,因此借助了大模型。
大模型使用了turtle库,以下是我认为关键的turtle库代码
#画布
t.turtle.Screen() #创建一个画布对象
t.screen.title() #设置窗口标题
t.screen.bgcolor() #设置背景颜色
t.screen.setup() #设置窗口大小(使用width和height变量分别表示长宽)
t.screen.tracer(n=None, delay=None) '''n=0,禁用屏幕的自动刷新
设置n的值,每绘制n次才刷新一次屏幕
通过delay参数控制每次刷新的延迟时间(毫秒)'''
这之中,我不太能理解screen.tracer(0)的作用,但是在我删除后,发现屏幕出现了明显的闪烁,搜索发现turtle库图像是实时刷新的,也就是说在海龟移动时不断刷新界面,导致出现卡顿。
关于蛇头代码
t.penup() #提起画笔,即移动时不绘制(将蛇头作为画笔,使用该函数就可以在初始时防止拖曳出蛇身)
t.pendown() #放下画笔,移动时绘制
t.pensize(size) #设置画笔粗细
t.pencolor(color) #设置画笔颜色(一开始我使用了这个代码,但是发现蛇头只有轮廓被填充了,于是去查询资料)
t.color("black","yellow")#画笔为“black”,填充为“yellow”
t.xcor() #获取当前x坐标(用来移动)
t.ycor() #获取当前y坐标(用来移动)
t.setx() #改变x坐标
t.sety() #改变y坐标
原本想用forward和downward实现移动,但是转向不好解决于是就利用x,y坐标
2.改进过程
(1)添加边界碰撞检测
def collision()
#检测是否碰撞边界
if head.xcor() > 300 or head.xcor() < -300 or head.ycor() > 300 or head.ycor() < -300:
return True
return False
(2)设置暂停函数
pause = False
def gamepause():
global pause
if pause:
pause = False
else:
pause = True
wn.onkeypress(gamepause,"p")
刚开始使用局部变量pause,发现无法暂停,发现py中变量和c语言一样有局部变量和全局变量之分,于是使用global全局变量
(3)设置重置函数
gameover = False
def resetgame():
global gameover
#重置蛇头位置
head.goto(0, 0)
head.direction = "stop"
gameover = False
可以用在控制gameover后返回原样
(4)设置红苹果
food = turtle.Turtle()
food.speed(0)
food.shape("circle")
food.color("green")
food.penup()
food.goto(0, 100)
(5)设置吃苹果同时随机生成新的苹果
body = []
def check_food():
global score
if head.distance(food) < 20:
# 随机放置新食物
x = random.randint(-280, 280)
y = random.randint(-280, 280)
food.goto(x, y)
# 添加新的身体段
new_body = turtle.Turtle()
new_body.speed(0)
new_body.shape("square")
new_body.color("grey")
new_body.penup()
body.append(new_body)
score += 1
(6)身体移动
def movebody():
if(len(body) > 0):
body[0].goto(head.xcor(), head.ycor())
for i in range(len(body)-1,0,-1):
body[i].goto(body[i-1].xcor(),body[i-1].ycor())
def game():
global gameover
if not gameover and not pause:
move()
movebody()
if collision():
gameover = True
resetgame()
check_food()
wn.update()
wn.ontimer(game, 50)
在这里出现了两个问题,第一个是我的身体把头部覆盖了,也就是所有的身体都在头部出现,我一开始想记录头部上一个走过的坐标,把新的身体接到该坐标上,但是发现代码很复杂,想了很久发现move()和movebody()函数调用顺序交换一下就可以了T-T;这时候第二个问题出现了,在我吃第二个苹果时身体被吞了,也就是我吃一个苹果和两个苹果时身体的长度都是1,我发现是因为movebody函数中i的取值问题,会导致第二节和第一节都在同一位置跟随,通过交换跟随头部与跟随身体两给判断的顺序成功解决!just like this
def movebody():
for i in range(len(body)-1,0,-1):
body[i].goto(body[i-1].xcor(),body[i-1].ycor())
if(len(body) > 0):
body[0].goto(head.xcor(), head.ycor())
def game():
global gameover
if not gameover and not pause:
movebody()
move()
if collision():
gameover = True
resetgame()
check_food()
wn.update()
wn.ontimer(game, 50)
ps:这种顺序问题真的好难想,脑子要燃起来了
(7)身体碰撞检测
初始想法如下
def collision():
#检测是否碰撞边界
if head.xcor() > 290 or head.xcor() < -290 or head.ycor() > 290 or head.ycor() < -290:
return True
# 检测是否碰撞身体
if head.distance(body) < 20:
return True
return False
但是程序一开使就直接结束了,查询发现turtle库中的distance函数只能针对单个turtle,而body使一个列表,所以要用单个元素逐一判断:
def collision():
#检测是否碰撞边界
if head.xcor() > 290 or head.xcor() < -290 or head.ycor() > 290 or head.ycor() < -290:
return True
# 检测是否碰撞身体
for bodies in body:
if head.distance(bodies) < 20:
return True
return False
当然也别忘记把身体和分数归零
def resetgame():
global gameover,score,body
body.clear()
score = 0
#重置蛇头位置
head.goto(0, 0)
head.direction = "stop"
gameover = False
但是这样子身体并没有消失,而是固定在原地,我尝试使用hideturtle()函数隐藏身体
顺便把分数显示和gameover提示做出来
(代码太长了我就没拿过来了,直接看1.1吧)
显示分数和上面大同小异,但是这里我的GAMEOVER一直显示不出来,发现是reset和game函数中的显示隐藏模块出现问题,当检测到碰撞时,立即调用resetgame,导致GAMEOVER被清除,修改后1.1出炉
3.贪吃蛇1.1
增加了一个time.sleep(0.4)更有节奏感[手动狗头]
import turtle
import time
import random
#设置游戏窗口
wn = turtle.Screen()
wn.title("贪吃蛇游戏")
wn.bgcolor("black")
wn.setup(width=600, height=600)
wn.tracer(0)#关闭窗口自动刷新
#蛇头
head = turtle.Turtle()
head.speed(0) #直接显示蛇头
head.shape("square")
head.color("red","yellow")
head.penup()
head.goto(0, 0)
head.setheading(90)
head.direction = "stop"
#蛇身
body = []
#食物
food = turtle.Turtle()
food.speed(0)
food.shape("circle")
food.color("red")
food.penup()
food.goto(0, 100)
#分数
score = 0
scoreboard = turtle.Turtle()
scoreboard.speed(0)
scoreboard.color("white")
scoreboard.penup()
scoreboard.hideturtle()
scoreboard.goto(0, 260)
scoreboard.write("0", align="center", font=("SimHei", 24, "normal"))
#游戏结束
signover = turtle.Turtle()
signover.speed(0)
signover.color("red")
signover.penup()
signover.hideturtle()
signover.goto(0, 0)
#定义移动函数
def go_up():
if head.direction != "down":
head.direction = "up"
def go_down():
if head.direction != "up":
head.direction = "down"
def go_left():
if head.direction != "right":
head.direction = "left"
def go_right():
if head.direction != "left":
head.direction = "right"
def move():
if head.direction == "up":
y = head.ycor()
head.sety(y + 20)
if head.direction == "down":
y = head.ycor()
head.sety(y - 20)
if head.direction == "left":
x = head.xcor()
head.setx(x - 20)
if head.direction == "right":
x = head.xcor()
head.setx(x + 20)
#检测是否死亡或者暂停
gameover = False
pause = False
def gamepause():
global pause
if pause:
pause = False
else:
pause = True
def collision():
#检测是否碰撞边界
if head.xcor() > 290 or head.xcor() < -290 or head.ycor() > 290 or head.ycor() < -290:
return True
#检测是否碰撞身体
for bodies in body:
if head.distance(bodies) < 20:
return True
return False
def check_food():
global score
if head.distance(food) < 20:
#随机放置新食物
x = random.randint(-280, 280)
y = random.randint(-280, 280)
food.goto(x, y)
#添加新的身体段
new_body = turtle.Turtle()
new_body.speed(0)
new_body.shape("square")
new_body.color("yellow","green")
new_body.penup()
body.append(new_body)
score += 1
scoreboard.clear()
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
#移动身体
def movebody():
for i in range(len(body)-1,0,-1):
body[i].goto(body[i-1].xcor(),body[i-1].ycor())
if(len(body) > 0):
body[0].goto(head.xcor(), head.ycor())
def resetgame():
global gameover, score, body
#隐藏所有元素
head.hideturtle()
food.hideturtle()
for bodies in body:
bodies.hideturtle()
#重置数据
body.clear()
score = 0
scoreboard.clear()
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
#重置位置
head.goto(0, 0)
head.direction = "stop"
food.goto(0, 100)
#清除游戏结束提示
signover.clear()
#重新显示蛇头和食物
head.showturtle()
food.showturtle()
gameover = False
#键盘绑定
wn.listen()
wn.onkeypress(go_up, "w")
wn.onkeypress(go_down, "s")
wn.onkeypress(go_left, "a")
wn.onkeypress(go_right, "d")
wn.onkeypress(go_up, "Up")
wn.onkeypress(go_down, "Down")
wn.onkeypress(go_left, "Left")
wn.onkeypress(go_right, "Right")
wn.onkeypress(gamepause,"p")
wn.onkeypress(resetgame,"r")
#主游戏循环
def game():
global gameover
if not gameover and not pause:
movebody()
move()
if collision():
gameover = True
time.sleep(0.4)
head.hideturtle()
food.hideturtle()
for bodies in body:
bodies.hideturtle()
signover.write("GAMEOVER\n按 R 重新开始", align="center", font=("SimHei", 24, "normal"))
wn.update()
else:
check_food()
wn.update()
wn.ontimer(game, 50)
game()
wn.mainloop()
4.后续修改过程
(1)我发现每次吃苹果时新生成的身体都会在(0,0)点闪烁,于是在创建新身体后,直接将其移动到当前蛇尾的位置
def check_food():
global score
if head.distance(food) < 20:
#随机放置新食物
x = random.randint(-280, 280)
y = random.randint(-280, 280)
food.goto(x, y)
#添加新身体
new_body = turtle.Turtle()
new_body.speed(0)
new_body.shape("square")
new_body.color("yellow", "green")
new_body.penup()
if len(body) == 0:
new_body.goto(head.xcor(), head.ycor())
else:
new_body.goto(body[-1].xcor(), body[-1].ycor())
body.append(new_body)
score += 1
scoreboard.clear()
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
(2)发现食物和蛇头会错开,即不在同一网格面内,把食物生成位置设置为20倍数
x = 20*random.randint(-14, 14)
y = 20*random.randint(-14, 14)
贪吃蛇2.0开发过程
我觉得目前的太简单了,因为高中时使用老人机,就想仿照老人机的贪吃蛇游戏,设置地图,做了一个2.0,以下是一部分遇到的问题与解决办法
一开始想通过全局变量obstacle来控制障碍物的生成,但是发现这样我在函数中调用的是这一个变量,也就是没法生成一条墙壁,,想到利用函数,通过调用函数生成障碍
def create_obstacle(x, y):
obstacle = turtle.Turtle()
obstacle.speed(0)
obstacle.shape("square")
obstacle.color("grey")
obstacle.penup()
obstacle.goto(x, y)
obstacles.append(obstacle)
发现在菜单界面可以控制蛇移动,在游戏界面可以按1,2来切换地图,于是使用不同的状态来区分
MENU = 0
PLAYING = 1
GAMEOVER = 2
#初始化状态为菜单
game_state = MENU
突发奇想,可以设置为穿过界面边界,从另外一头过去,也很简单
def collision():
#碰撞边界
if head.xcor() > 280 or head.xcor() < -280 :
head.setx(-head.xcor())
if head.ycor() > 280 or head.ycor() < -280:
head.sety(-head.ycor())
但是发现这样会出现卡墙情况,意识到蛇每次移动距离是20,会出现卡在极限距离的情况出现,修改如下
def collision():
#碰撞边界
if head.xcor() > 280:
head.setx(-280)
elif head.xcor() < -280:
head.setx(280)
if head.ycor() > 280:
head.sety(-280)
elif head.ycor() < -280:
head.sety(280)
最后把2.0代码放在这里
import turtle
import time
import random
#游戏窗口
wn = turtle.Screen()
wn.title("贪吃蛇游戏")
wn.bgcolor("black")
wn.setup(width=600, height=600)
wn.tracer(0) # 关闭窗口自动刷新
#游戏状态
MENU = 0
PLAYING = 1
GAMEOVER = 2
game_state = MENU
#障碍物列表
obstacles = []
#蛇头
head = turtle.Turtle()
head.speed(0) #直接显示蛇头
head.shape("square")
head.color("red", "yellow")
head.penup()
head.goto(0, 0)
head.setheading(90)
head.direction = "stop"
head.hideturtle()
#蛇身
body = []
#食物
food = turtle.Turtle()
food.speed(0)
food.shape("circle")
food.color("red")
food.penup()
food.goto(0, 100)
food.hideturtle()
#分数
score = 0
scoreboard = turtle.Turtle()
scoreboard.speed(0)
scoreboard.color("white")
scoreboard.penup()
scoreboard.hideturtle()
scoreboard.goto(0, 260)
#游戏结束
signover = turtle.Turtle()
signover.speed(0)
signover.color("red")
signover.penup()
signover.hideturtle()
signover.goto(0, 0)
#菜单
menu = turtle.Turtle()
menu.speed(0)
menu.color("white")
menu.penup()
menu.hideturtle()
#障碍物
def createobstacle(x,y):
obstacle = turtle.Turtle()
obstacle.speed(0)
obstacle.shape("square")
obstacle.color("grey")
obstacle.penup()
obstacle.goto(x, y)
obstacles.append(obstacle)
#定义移动函数
def go_up():
if head.direction != "down" and game_state == PLAYING:
head.direction = "up"
def go_down():
if head.direction != "up" and game_state == PLAYING:
head.direction = "down"
def go_left():
if head.direction != "right" and game_state == PLAYING:
head.direction = "left"
def go_right():
if head.direction != "left" and game_state == PLAYING:
head.direction = "right"
def move():
if head.direction == "up":
y = head.ycor()
head.sety(y + 20)
if head.direction == "down":
y = head.ycor()
head.sety(y - 20)
if head.direction == "left":
x = head.xcor()
head.setx(x - 20)
if head.direction == "right":
x = head.xcor()
head.setx(x + 20)
#检测是否死亡或者暂停
pause = False
def gamepause():
global pause
if game_state == PLAYING:
if pause:
pause = False
else:
pause = True
def collision():
#碰撞边界
if head.xcor() > 280:
head.setx(-280)
elif head.xcor() < -280:
head.setx(280)
if head.ycor() > 280:
head.sety(-280)
elif head.ycor() < -280:
head.sety(280)
#碰撞身体
for bodies in body:
if head.distance(bodies) < 20:
return True
#碰撞障碍物
for obstacle in obstacles:
if head.distance(obstacle) < 20:
return True
return False
def check_food():
global score
if head.distance(food) < 20:
while True:
x = 20 * random.randint(-14, 14)
y = 20 * random.randint(-14, 14)
foodpos = True
#检查是否在障碍物上
for obstacle in obstacles:
if abs(x - obstacle.xcor()) < 20 and abs(y - obstacle.ycor()) < 20:
foodpos = False
break
#检查是否在蛇身上
for segment in body:
if abs(x - segment.xcor()) < 20 and abs(y - segment.ycor()) < 20:
foodpos = False
break
if foodpos:
break
food.goto(x, y)
#添加身体
newbody = turtle.Turtle()
newbody.speed(0)
newbody.shape("square")
newbody.color("yellow", "green")
newbody.penup()
if len(body) == 0:
newbody.goto(head.xcor(), head.ycor())
else:
newbody.goto(body[-1].xcor(), body[-1].ycor())
body.append(newbody)
score += 1
scoreboard.clear()
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
#移动身体
def movebody():
for i in range(len(body) - 1, 0, -1):
body[i].goto(body[i - 1].xcor(), body[i - 1].ycor())
if len(body) > 0:
body[0].goto(head.xcor(), head.ycor())
def resetgame():
global game_state, score, body, obstacles
#清除障碍物
for obstacle in obstacles:
obstacle.clear()
obstacle.hideturtle()
obstacles.clear()
#隐藏所有元素
head.hideturtle()
food.hideturtle()
for bodies in body:
bodies.hideturtle()
#重置数据
body.clear()
score = 0
scoreboard.clear()
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
#重置位置
head.goto(0, 0)
head.direction = "stop"
food.goto(0, 100)
#清除游戏结束提示
signover.clear()
#返回菜单
game_state = MENU
show_menu()
def start_game(map_choice):
global game_state, obstacles
#清除菜单
menu.clear()
#显示游戏元素
head.showturtle()
food.showturtle()
for obstacle in obstacles:
obstacle.showturtle()
#创建障碍物
create_map(map_choice)
#重置分数
scoreboard.write(f"{score}", align="center", font=("SimHei", 24, "normal"))
#开始游戏
game_state = PLAYING
def create_map(map_choice):
#清除现有障碍物
for obstacle in obstacles:
obstacle.clear()
obstacle.hideturtle()
obstacles.clear()
#地图1
if map_choice == 1:
# 水平障碍
for x in range(-300, 300, 20):
if 300 >= abs(x) > 10 :
createobstacle(x, 20)
#地图2
elif map_choice == 2:
for x in range(-300, 140, 20):
createobstacle(x, 40)
for y in range(80, 320, 20):
createobstacle(120, y)
def show_menu():
menu.clear()
menu.goto(0, 40)
menu.write("贪吃蛇大作战\n", align="center", font=("SimHei", 36, "bold"))
menu.goto(0, -150)
menu.write("请选择地图\n1:地图一\n2:地图二\n", align="center", font=("华文行楷", 36, "bold italic"))
#键盘绑定
wn.listen()
wn.onkeypress(go_up, "w")
wn.onkeypress(go_down, "s")
wn.onkeypress(go_left, "a")
wn.onkeypress(go_right, "d")
wn.onkeypress(go_up, "Up")
wn.onkeypress(go_down, "Down")
wn.onkeypress(go_left, "Left")
wn.onkeypress(go_right, "Right")
wn.onkeypress(gamepause, "p")
wn.onkeypress(resetgame, "r")
wn.onkeypress(lambda: start_game(1), "1")
wn.onkeypress(lambda: start_game(2), "2")
#主游戏循环
def game():
global game_state
if game_state == PLAYING and not pause:
movebody()
move()
if collision():
game_state = GAMEOVER
time.sleep(0.4)
head.hideturtle()
food.hideturtle()
for obstacle in obstacles:
obstacle.hideturtle()
for bodies in body:
bodies.hideturtle()
signover.write(" GAMEOVER\n按 R 返回菜单", align="center", font=("SimHei", 24, "normal"))
else:
check_food()
wn.update()
wn.ontimer(game, 50)
#显示初始菜单
show_menu()
game()
wn.mainloop()
三、遇到的问题和解决
其实问题在上面已经说了,但是我还是想总结一下,首先我从初始的代码开始,因为准备做贪吃蛇的代码,但是不知道图形化的界面怎么去做,所以使用了csdn和大模型查找绘图的函数库,最后选定了turtle,相关的知识点我在上面也总结了一下,在过程中也遇到了一些问题,比如键盘绑定等等,都一一解决了。
希望以后可以加一个排行榜记录玩家分数,再结合一下之前学的和别人联机~
四、感悟
刚开始选课时我也没什么想法,因为看到学长学姐狠狠夸我强哥,同时经过一学期的c语言学习,对编程也是挺有兴趣的,就报了py课,但是上了课,确实被py的简洁狠狠圈粉,py比c语言简洁了特别特别多,不愧是各大榜单排名第一的算法【手动狗头】不过因为平时学业压力,在py上投入的时间不是特别多,也就是上课时间和做实验投入了时间,也是挺感兴趣的,希望暑假可以逼自己学一下吧T-T(虽然大概率暑假窝在床上玩手机)
做贪吃蛇的时候,最麻烦的是让蛇身跟着动起来。一开始蛇头动了,身子还在原地,查了半天才发现没写蛇身移动的逻辑,后来用列表存蛇身的位置,循环让后面的方块跟着前面的走,总算解决了。还有食物生成的时候,经常刷在蛇身上,后来加了个循环检查位置,才让食物顺利出现在空白处。这次实验花的时间确实挺多的,但是也让我从数据结构那依托答辩那高端的课程中抽离出来,真正做一点想做的,正如我在报告中写的,我高中用的都是老年机,贪吃蛇也可以说陪伴了我挺久的,也有原因是这个比较直观吧,总而言之,解决这一个程序还是挺有成就感的!
这学期最大的感受就是编程真的需要耐心和细心。有时候一个小错误要debug半小时,但解决之后就觉得自己又厉害了一点。如果要说建议的话,希望课上能多一些像贪吃蛇这样的实战项目,大家一起讨论怎么优化代码,比如怎么让蛇移动更流畅,或者怎么加音效,这样会更有意思!
最后最后我一定要说强哥真的是很好很好的老师T-T!!!希望以后有机会可以再让强哥教!!!

浙公网安备 33010602011771号