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!!!希望以后有机会可以再让强哥教!!!

五、参考资料

《Markdown公式用法大全》
《10分钟轻松学会 Python turtle 绘图》

posted @ 2025-06-06 22:02  masterzk01  阅读(54)  评论(0)    收藏  举报