20252307 2025-2026-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2523
姓名: 万书林
学号:20252307
实验教师:王志强
实验日期:2026年6月15日
必修/选修: 公选课

1.实验内容

利用Python编写汉堡店小游戏,模拟做汉堡的过程,涵盖以下功能:
1)顾客提交订单,玩家按要求做汉堡提交,获得分数
2)如果提交的汉堡出错或者超时,会扣分
3)牛肉饼需要烤制,汉堡里只能用熟肉饼
4)达到一定分数后,会进入下一关,解锁更多食材
5)可以调整代码修改难度,比如过关分数,总时间等
6)食材图片利用贴图技术
7)加入了震动特效和音乐

本游戏考验玩家鼠标掌控能力(准确快速点到食材),手眼协调能力,反应速度,是不错的电竞能力训练游戏

2. 实验过程及结果

代码如下:
import pygame
import random
import sys
import winsound
import os

================= 配置常量 =================

SCREEN_WIDTH = 900
SCREEN_HEIGHT = 650
FPS = 60
GAME_DURATION = 180 # 游戏总时长(秒)

颜色定义

BG_COLOR = (245, 240, 230) # 温暖的米色背景
PANEL_COLOR = (255, 255, 255) # 面板白
TEXT_COLOR = (60, 60, 60) # 深灰文字
ACCENT_COLOR = (220, 80, 60) # 强调色(红)
SUCCESS_COLOR = (70, 180, 90) # 成功色(绿)
WARNING_COLOR = (240, 180, 40) # 警告色(黄)

原料类型定义

INGREDIENT_TOP_BUN = "top_bun"
INGREDIENT_BOTTOM_BUN = "bottom_bun"
INGREDIENT_LETTUCE = "lettuce"
INGREDIENT_TOMATO = "tomato"
INGREDIENT_CHEESE = "cheese"
INGREDIENT_ONION = "onion" # 新增: 洋葱圈
INGREDIENT_PICKLE = "pickle" # 新增: 酸黄瓜
INGREDIENT_PATTY_RAW = "patty_raw"
INGREDIENT_PATTY_COOKED = "patty_cooked"

基础配菜与高级配菜

BASE_SIDES = [INGREDIENT_LETTUCE, INGREDIENT_TOMATO, INGREDIENT_CHEESE]
ADV_SIDES = [INGREDIENT_ONION, INGREDIENT_PICKLE]

烤制时间(秒)

COOK_TIME = 2.5

class BurgerBuilderGame:
def init(self):
# 加载食材图片
self.ingredient_images = {}
# 定义食材对应的图片文件名
image_files = {
INGREDIENT_TOP_BUN: "top_bun.png",
INGREDIENT_BOTTOM_BUN: "bottom_bun.png",
INGREDIENT_LETTUCE: "lettuce.png",
INGREDIENT_TOMATO: "tomato.png",
INGREDIENT_CHEESE: "cheese.png",
INGREDIENT_PATTY_RAW: "patty_raw.png",
INGREDIENT_PATTY_COOKED: "patty_cooked.png",
INGREDIENT_ONION: "onion.png",
INGREDIENT_PICKLE: "pickle.png"
}

统一图片尺寸 (宽200, 高40),保证汉堡堆叠整齐

self.ing_width = 200
self.ing_height = 40

for ing_type, filename in image_files.items():
try:
img = pygame.image.load(f"images/{filename}").convert_alpha()
# 缩放图片到统一尺寸
self.ingredient_images[ing_type] = pygame.transform.scale(img, (self.ing_width, self.ing_height))
except Exception as e:
print(f"警告: 无法加载图片 {filename},将使用默认色块。错误: {e}")
self.ingredient_images[ing_type] = None
pygame.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("老爹汉堡店 - 进阶版")
self.clock = pygame.time.Clock()
BASE_DIR = os.path.dirname(os.path.abspath(file))
IMAGE_DIR = os.path.join(BASE_DIR, "images")

print(f"正在尝试从以下目录加载图片: {IMAGE_DIR}") # <--- 加上这一行看输出

for ing_type, filename in image_files.items():
full_path = os.path.join(IMAGE_DIR, filename) # 拼接完整路径

try:
# 检查文件是否存在
if not os.path.exists(full_path):
raise FileNotFoundError(f"文件不存在: {full_path}")

img = pygame.image.load(full_path).convert_alpha()
self.ingredient_images[ing_type] = pygame.transform.scale(img, (self.ing_width, self.ing_height))

except Exception as e:
print(f"[严重警告] 无法加载 {filename} -> {e}")
placeholder = pygame.Surface((self.ing_width, self.ing_height))
placeholder.fill((255, 0, 0))
self.ingredient_images[ing_type] = placeholder

字体

self.font_large = pygame.font.SysFont("SimHei", 36)
self.font_medium = pygame.font.SysFont("SimHei", 24)
self.font_small = pygame.font.SysFont("SimHei", 18)

游戏状态

self.score = 0
self.time_left = GAME_DURATION
self.running = True
self.game_over = False
# --- 初始化关卡与难度 ---
self.level = 1 # 当前关卡
self.level_up_score = 500 # 每500分升一级
self.max_level = 5 # 设定最高难度为5级

关卡与解锁

self.level = 1
self.unlocked_sides = BASE_SIDES[:]

顾客耐心

self.customer_patience = 100.0 # 0-100
self.patience_decay = 8.0 # 每秒下降速度

组装与烤盘

self.current_burger = []
self.grill_state = None

拖拽系统

self.dragging_ingredient = None
self.drag_pos = (0, 0)

动画反馈

self.shake_timer = 0
self.feedback_msg = ""
self.feedback_timer = 0
self.feedback_color = SUCCESS_COLOR

初始化订单

self.current_order = None
self._generate_new_order()

---------- 核心逻辑 ----------

def _generate_new_order(self):
"""根据当前关卡生成订单"""
available_sides = self.unlocked_sides[:]
if len(available_sides) < 2:
available_sides = BASE_SIDES[:]

sides = random.sample(available_sides, 2)
self.current_order = {"side_a": sides[0], "side_b": sides[1]}
self.current_burger = []
self.customer_patience = 100.0

检查是否需要升级关卡

if self.score >= 500 and self.level == 1:
self.level = 2
self.unlocked_sides = BASE_SIDES + ADV_SIDES
self._show_feedback("🎉 解锁新配菜! 洋葱圈&酸黄瓜", SUCCESS_COLOR)
try:
winsound.Beep(1000, 300)
except:
pass

def _check_burger_complete(self):
"""严格校验汉堡顺序"""
if len(self.current_burger) != 5: return False
expected = [
INGREDIENT_BOTTOM_BUN,
INGREDIENT_PATTY_COOKED,
self.current_order["side_b"],
self.current_order["side_a"],
INGREDIENT_TOP_BUN
]
return self.current_burger == expected

def _submit_burger(self):
"""提交汉堡并评分"""
if self._check_burger_complete():
self.score += 100
self.feedback_msg = "完美! +100分"
else:
self.score = max(0, self.score - 20)
self.feedback_msg = "做错了! -20分"

self.feedback_timer = 1.5
self._generate_new_order()

--- 新增:检查是否升级 ---

计算当前应该处于第几级 (分数 // 升级所需分数 + 1)

new_level = min((self.score // self.level_up_score) + 1, self.max_level)

if new_level > self.level:
self.level = new_level
# 升级时给个提示 (可选)
self.feedback_msg = f"🎉 升级了! 进入第 {self.level} 关!"
# 调整难度
self._adjust_difficulty()

def _show_feedback(self, msg, color):
self.feedback_msg = msg
self.feedback_color = color
self.feedback_timer = 1.5

def _adjust_difficulty(self):

reduction = (self.level - 1) * 10
self.time_left = max(30, self.time_left - reduction) # 至少保留30秒

def update(self, dt):
if self.game_over: return

self.time_left -= dt
if self.time_left <= 0:
self.time_left = 0
self.game_over = True
return

顾客耐心衰减

self.customer_patience -= self.patience_decay * dt
if self.customer_patience <= 0:
self.customer_patience = 0
self.score = max(0, self.score - 50)
self._show_feedback("顾客等不及走了! -50分", ACCENT_COLOR)
try:
winsound.Beep(150, 500)
except:
pass
self.shake_timer = 0.4
self._generate_new_order()

烤盘更新

if self.grill_state and not self.grill_state["done"]:
elapsed = pygame.time.get_ticks() / 1000.0 - self.grill_state["start_time"]
if elapsed >= COOK_TIME:
self.grill_state["done"] = True
try:
winsound.Beep(1200, 100)
except:
pass

动画计时器

if self.shake_timer > 0: self.shake_timer -= dt
if self.feedback_timer > 0: self.feedback_timer -= dt

---------- 渲染 ----------

def draw(self):
# 屏幕震动效果
offset_x, offset_y = 0, 0
if self.shake_timer > 0:
offset_x = random.randint(-5, 5)
offset_y = random.randint(-5, 5)

self.screen.fill(BG_COLOR)

if self.game_over:
self._draw_game_over()
pygame.display.flip()
return

绘制UI面板

self._draw_hud()
self._draw_customer_panel()
self._draw_ingredient_buttons()
self._draw_grill()
self._draw_assembly_area()

绘制拖拽中的食材

if self.dragging_ingredient:
rect = pygame.Rect(self.drag_pos[0] - self.ing_width // 2,
self.drag_pos[1] - self.ing_height // 2,
self.ing_width, self.ing_height)

if self.dragging_ingredient in self.ingredient_images and self.ingredient_images[self.dragging_ingredient]:
self.screen.blit(self.ingredient_images[self.dragging_ingredient], rect.topleft)
else:
color = self._get_ingredient_color(self.dragging_ingredient)
pygame.draw.rect(self.screen, color, rect, border_radius=8)
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2, border_radius=8)

反馈消息

if self.feedback_timer > 0:
msg_surf = self.font_large.render(self.feedback_msg, True, self.feedback_color)
self.screen.blit(msg_surf, (SCREEN_WIDTH // 2 - msg_surf.get_width() // 2 + offset_x, 300 + offset_y))

pygame.display.flip()

def _draw_hud(self):
score_text = self.font_medium.render(f"分数: {self.score}", True, TEXT_COLOR)
time_text = self.font_medium.render(f"剩余: {int(self.time_left)}s", True,
ACCENT_COLOR if self.time_left < 20 else TEXT_COLOR)
level_text = self.font_small.render(f"关卡: Lv.{self.level}", True, SUCCESS_COLOR)
self.screen.blit(score_text, (320, 15))
self.screen.blit(time_text, (500, 15))
self.screen.blit(level_text, (780, 20))

def _draw_customer_panel(self):
pygame.draw.rect(self.screen, PANEL_COLOR, (320, 50, 250, 70), border_radius=10)
face = "😊" if self.customer_patience > 50 else ("😐" if self.customer_patience > 20 else "😡")
face_surf = self.font_large.render(face, True, TEXT_COLOR)
self.screen.blit(face_surf, (330, 55))

bar_bg = pygame.Rect(380, 70, 170, 20)
pygame.draw.rect(self.screen, (220, 220, 220), bar_bg, border_radius=5)
bar_w = int(170 * max(0, self.customer_patience) / 100)
bar_color = SUCCESS_COLOR if self.customer_patience > 50 else (
WARNING_COLOR if self.customer_patience > 20 else ACCENT_COLOR)
pygame.draw.rect(self.screen, bar_color, (380, 70, bar_w, 20), border_radius=5)

order_text = f"订单: {self._get_ing_name(self.current_order['side_a'])} + {self._get_ing_name(self.current_order['side_b'])}"
ot_surf = self.font_small.render(order_text, True, TEXT_COLOR)
self.screen.blit(ot_surf, (380, 95))

def _draw_ingredient_buttons(self):
self.ingredient_btns = []
all_ingredients = [
("上面包", INGREDIENT_TOP_BUN, (240, 200, 100)),
("下面包", INGREDIENT_BOTTOM_BUN, (240, 200, 100)),
("生菜", INGREDIENT_LETTUCE, (100, 200, 100)),
("番茄", INGREDIENT_TOMATO, (220, 80, 80)),
("芝士", INGREDIENT_CHEESE, (255, 220, 100)),
("生肉饼", INGREDIENT_PATTY_RAW, (160, 82, 45)),
]
if INGREDIENT_ONION in self.unlocked_sides:
all_ingredients.insert(5, ("洋葱圈", INGREDIENT_ONION, (200, 180, 220)))
if INGREDIENT_PICKLE in self.unlocked_sides:
all_ingredients.insert(6, ("酸黄瓜", INGREDIENT_PICKLE, (120, 180, 120)))

start_y = 140
for i, (name, ing_type, color) in enumerate(all_ingredients):
rect = pygame.Rect(20, start_y + i * 55, 260, 45)
pygame.draw.rect(self.screen, color, rect, border_radius=8)
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2, border_radius=8)
text = self.font_medium.render(name, True, TEXT_COLOR)
self.screen.blit(text, (rect.centerx - text.get_width() // 2, rect.centery - text.get_height() // 2))
self.ingredient_btns.append((rect, ing_type))

def _draw_grill(self):
grill_rect = pygame.Rect(320, 140, 250, 90)
pygame.draw.rect(self.screen, PANEL_COLOR, grill_rect, border_radius=10)
pygame.draw.rect(self.screen, TEXT_COLOR, grill_rect, 2, border_radius=10)

label = self.font_small.render("🔥 烤盘", True, TEXT_COLOR)
self.screen.blit(label, (330, 145))

if self.grill_state:
elapsed = pygame.time.get_ticks() / 1000.0 - self.grill_state["start_time"]
progress = min(elapsed / COOK_TIME, 1.0)
bar_w = int(200 * progress)
color = SUCCESS_COLOR if self.grill_state["done"] else ACCENT_COLOR
pygame.draw.rect(self.screen, (220, 220, 220), (340, 180, 200, 15), border_radius=4)
pygame.draw.rect(self.screen, color, (340, 180, bar_w, 15), border_radius=4)
status = "✅ 点击取下" if self.grill_state["done"] else "烤制中..."
st_surf = self.font_small.render(status, True, TEXT_COLOR)
self.screen.blit(st_surf, (340, 200))
else:
hint = self.font_small.render("点击[生肉饼]放置", True, (180, 180, 180))
self.screen.blit(hint, (340, 185))
self.grill_rect = grill_rect

def _draw_assembly_area(self):
area_rect = pygame.Rect(320, 250, 550, 370)
pygame.draw.rect(self.screen, PANEL_COLOR, area_rect, border_radius=10)
pygame.draw.rect(self.screen, TEXT_COLOR, area_rect, 2, border_radius=10)

title = self.font_medium.render("🍔 组装区", True, TEXT_COLOR)
self.screen.blit(title, (340, 260))

stack_x = 450
base_y = 580
for i, ing in enumerate(self.current_burger):
y = base_y - i * 35
rect = pygame.Rect(stack_x, y, self.ing_width, self.ing_height)

如果有图片,绘制图片;否则绘制色块

if ing in self.ingredient_images and self.ingredient_images[ing]:
self.screen.blit(self.ingredient_images[ing], (stack_x, y))
else:
color = self._get_ingredient_color(ing)
pygame.draw.rect(self.screen, color, rect, border_radius=8)
pygame.draw.rect(self.screen, TEXT_COLOR, rect, 2, border_radius=8)

submit_rect = pygame.Rect(700, 550, 140, 50)
pygame.draw.rect(self.screen, SUCCESS_COLOR, submit_rect, border_radius=10)
pygame.draw.rect(self.screen, TEXT_COLOR, submit_rect, 2, border_radius=10)
sub_text = self.font_medium.render("提交订单", True, (255, 255, 255))
self.screen.blit(sub_text, (submit_rect.centerx - sub_text.get_width() // 2,
submit_rect.centery - sub_text.get_height() // 2))
self.submit_btn_rect = submit_rect

def _draw_game_over(self):
self.screen.fill((40, 40, 40))
go_text = self.font_large.render("⏰ 营业时间结束!", True, (255, 255, 255))
sc_text = self.font_large.render(f"最终得分: {self.score}", True, WARNING_COLOR)
restart_text = self.font_medium.render("按 R 重新开始 / 按 Q 退出", True, (180, 180, 180))
self.screen.blit(go_text, (SCREEN_WIDTH // 2 - go_text.get_width() // 2, 220))
self.screen.blit(sc_text, (SCREEN_WIDTH // 2 - sc_text.get_width() // 2, 290))
self.screen.blit(restart_text, (SCREEN_WIDTH // 2 - restart_text.get_width() // 2, 380))

---------- 辅助方法 ----------

@staticmethod
def _get_ingredient_color(ing):
mapping = {
INGREDIENT_TOP_BUN: (240, 200, 100), INGREDIENT_BOTTOM_BUN: (240, 200, 100),
INGREDIENT_LETTUCE: (100, 200, 100), INGREDIENT_TOMATO: (220, 80, 80),
INGREDIENT_CHEESE: (255, 220, 100), INGREDIENT_ONION: (200, 180, 220),
INGREDIENT_PICKLE: (120, 180, 120), INGREDIENT_PATTY_COOKED: (160, 82, 45),
}
return mapping.get(ing, (180, 180, 180))

@staticmethod
def _get_ing_name(ing):
mapping = {
INGREDIENT_TOP_BUN: "上面包", INGREDIENT_BOTTOM_BUN: "下面包",
INGREDIENT_LETTUCE: "生菜", INGREDIENT_TOMATO: "番茄",
INGREDIENT_CHEESE: "芝士", INGREDIENT_ONION: "洋葱圈",
INGREDIENT_PICKLE: "酸黄瓜", INGREDIENT_PATTY_COOKED: "牛肉饼",
}
return mapping.get(ing, "?")

---------- 事件处理 ----------

def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
return

if self.game_over:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.init()
elif event.key in (pygame.K_q, pygame.K_ESCAPE):
self.running = False
return

if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
pos = pygame.mouse.get_pos()

for rect, ing_type in self.ingredient_btns:
if rect.collidepoint(pos):
if ing_type == INGREDIENT_PATTY_RAW:
self._put_patty_on_grill()
else:
self.dragging_ingredient = ing_type
self.drag_pos = pos
return

if hasattr(self, 'grill_rect') and self.grill_rect.collidepoint(pos):
self._take_patty_from_grill()

if hasattr(self, 'submit_btn_rect') and self.submit_btn_rect.collidepoint(pos):
self._submit_burger()

if event.type == pygame.MOUSEMOTION and self.dragging_ingredient:
self.drag_pos = pygame.mouse.get_pos()

if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
if self.dragging_ingredient:
pos = pygame.mouse.get_pos()
assembly_rect = pygame.Rect(320, 250, 550, 370)
if assembly_rect.collidepoint(pos) and len(self.current_burger) < 5:
self.current_burger.append(self.dragging_ingredient)
self.dragging_ingredient = None

def _put_patty_on_grill(self):
if self.grill_state is None:
self.grill_state = {"start_time": pygame.time.get_ticks() / 1000.0, "done": False}

def _take_patty_from_grill(self):
if self.grill_state and self.grill_state["done"]:
self.grill_state = None
if len(self.current_burger) < 5:
self.current_burger.append(INGREDIENT_PATTY_COOKED)

---------- 主循环 (补全部分) ----------

def run(self):
while self.running:
dt = self.clock.tick(FPS) / 1000.0
self.handle_events()
self.update(dt)
self.draw()
pygame.quit()
sys.exit()

================= 程序启动入口 =================

if name == "main":
game = BurgerBuilderGame()
game.run()

实验分析说明

分析:该程序设计为一个单机版的时间管理游戏,核心玩法循环为:接单->烤肉->拖拽组装->提交->结算,使用pygame图形渲染,random随机订单,winsound音效

os文件路径处理

游戏机制:限时挑战(180秒)可调整,关卡系统,难度递增

核心设计:字典映射处理食材和图片的对应关系,将食材统一缩放,保证汉堡堆叠在一起时视觉整齐

交互逻辑:拖拽系统,通过self.dragging_ingredient记录当前抓取的食材

烤制系统:模拟真实的烹饪时间(COOK_TIME = 2.5s)。点击生肉饼放入烤架,进度条走完变绿后方可取下,取下时自动替换为“熟肉饼”加入汉堡栈。
订单验证:采用了严格顺序校验。汉堡必须严格按照 底面包, 熟肉饼, 配菜B, 配菜A, 顶面包 的顺序堆叠才视为正确。

实验结果(视频)

3. 实验过程中遇到的问题和解决过程

1)食材图片上传失败:解决过程:在LLM的指导下,加入了保护代码,规范了文件名和目录,成功加载上传
2)提交汉堡逻辑出错:经查,汉堡肉饼和配菜的顺序搞错了,导致无法正确提交,修改代码后正常
3)最初想尝试爬虫相关实验设计,但是由于技术不够,多次出现错误无法达到目标,并且被警告有违法风险,遂放弃

其他(感悟、思考等)

全课总结:

本学期的python公选课使我初步了解了python这一编程语言,使我获益良多,从基础的语法到进阶的技术,如socket通信,爬虫,让我了解了这一编程语言的魅力,我学会了基本的控制语句,选择循环等,还有C语言没有涉及的元组,字典,正则表达式等概念,以及面向对象的编程语言特点,老师用生动的“盖浇饭”例子让我对其理解,探索了较为复杂的python语言应用

感想体会:

学习了这门公选课,我了解体会到了python这一编程语言的作用与魅力,理解了那句:人生苦短,我用python,加强了我的编码水平,初步学会了一门新的编程语言并加以简单应用,课程中面对复杂的实验有时会很头疼,尝试了好久却找不到正确方法会很崩溃,但是在自己的努力下搞定也很有成就感,王老师也是一位专业能力强,讲课幽默的老师,课程很有趣也干货满满,期待在其他课程中再次与王老师见面

意见与建议

我觉得以后的课程可以增加一下动手操作实验的比重,但是可以适当降低难度,也期待老师发明更有创意更有趣的课堂模式

posted @ 2026-06-16 14:48  wm129  阅读(9)  评论(0)    收藏  举报