
期末大作业、Python 课程设计、毕设选题很多同学遇到的最大问题不是“不会写代码”而是“不知道做什么”。网上搜到的游戏源码要么只有半截要么依赖装不上、一运行就报错。最近我把一套 Python 游戏实战项目合集系统地梳理了一遍覆盖控制台小游戏、Pygame/Tkinter 图形游戏、SQLite 数据库排行榜甚至带到了 Flask Web 框架扩展基本就是期末大作业和毕设需要的完整链路。这篇文章不会只罗列项目名而是会挑出几个最有代表性的方向拆开讲项目怎么分类、游戏主循环怎么写、数据库排行榜怎么接、代码怎么组织才像“期末大作业”而不是“一百行脚本”以及如何用这套合集进行二次开发。文章里的代码都能直接复制运行建议边看边敲动手跑一遍比收藏十篇文章都管用。1. 这套 Python 游戏项目合集能解决什么问题1.1 期末大作业和毕设的常见痛点期末大作业通常要求“完整功能 数据库 文档 演示”毕业设计则更看重“工作量 技术含量 可扩展性”。直接背一个游戏源码往往交差没问题但一旦老师问“主循环为什么这样写”“排行榜数据存在哪里”很容易答不上来。游戏项目恰好是综合练习 Python 的好载体。一个完整的游戏涉及输入处理、逻辑判断、数据存储、界面渲染、模块拆分难度梯度非常明显入门可以做“猜数字”“猜单词”训练基本语法。进阶可以做“贪吃蛇”“俄罗斯方块”训练面向对象和 Pygame 事件机制。加入数据库后可以做“排行榜”“用户登录”训练数据持久化。再往后可以接 Flask、WebSocket把单机游戏升级成网页版这就是典型毕设题量。我在整理项目时发现真正适合学习和答辩的不是代码越花哨越好而是结构清楚、能说清楚每一个模块在干什么。下面这张表是从项目合集中提炼出来的 16 个典型方向基本覆盖了从入门到框架的完整路线。难度等级技术重点典型项目方向适合阶段入门级条件判断、循环、随机数猜数字、猜单词、剪刀石头布、21点Python 基础课进阶级函数、列表、字典、文件读写井字棋、迷宫生成、华容道、拼图课程设计图形化面向对象、Pygame/Tkinter贪吃蛇、俄罗斯方块、2048、记忆翻牌、扫雷期末大作业算法强化搜索、递归、博弈算法五子棋人机对战、自动走迷宫、寻路算法演示毕设/竞赛框架扩展Flask、WebSocket、数据库Web 版游戏、在线排行榜、双人对战进阶毕设1.2 推荐学习顺序如果你把这些项目从头到尾刷一遍最容易踩的坑是“中间难度断层”基础语法还没熟练就直接去读 Pygame 源码很容易被事件循环、Surface、碰撞检测吓退。比较合理的顺序是先跑通 2~3 个控制台小游戏把“输入-处理-输出”的循环感建立起来。再用 Pygame 做一个简单图形游戏比如贪吃蛇或 2048重点理解主循环和事件。然后给游戏接上 SQLite 数据库完成排行榜或历史记录功能。最后再考虑加上 Flask 或 Socket做成网页版这也是大作业/毕设最容易出彩的部分。这套路线本质上是从“函数脚本”走向“工程代码”的过程。后面我会按这个顺序把每个环节的关键代码和设计思路拆开讲。2. 环境准备与版本说明2.1 开发环境选择本文示例以 Python 3 为主建议安装 Python 3.8 及以上版本不要用 Python 2。如果你需要向下兼容旧项目也建议通过虚拟环境隔离不要直接改系统默认版本。操作系统方面Windows 和 Linux/macOS 都可以。需要注意的是 Pygame 在 macOS 上如果窗口无法弹出通常和显示环境有关建议在 Windows 或普通桌面 Linux 环境下运行图形项目。编程工具可以选择 PyCharm、VS Code 或者 IDLE前两者更适合阅读多文件项目。安装第三方库时只需要安装用到的库不要一次性把网上搜到的依赖全装一遍。下面这段命令装了 Pygame 和 Flask已经能覆盖本文大部分示例pip install pygame flask如果你使用虚拟环境可以这样创建python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install pygame flask版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果安装过程中出现“网络超时”可以考虑切换为国内镜像源例如pip install pygame flask -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 项目目录结构建议很多同学写 Python 游戏喜欢把所有代码堆在一个main.py里平时跑没问题但期末大作业或毕设要求“模块清晰”建议一开始就按功能拆文件。下面是一个参考结构game_project/ ├── main.py # 程序入口 ├── game/ │ ├── __init__.py │ ├── core.py # 游戏主循环和逻辑 │ ├── sprites.py # 角色、障碍物 │ └── utils.py # 辅助函数 ├── database/ │ ├── db.py # 数据库连接和操作 │ └── init.sql # 建表脚本 ├── resources/ │ ├── images/ │ └── sounds/ └── README.md这样做的第一个好处是“每个文件职责单一”答辩时老师问起来你能直接说出每个模块的作用。第二个好处是后面做扩展时不需要重写全部代码比如把db.py里的 SQLite 换成 MySQL游戏逻辑完全不用动。3. 基础游戏核心控制台小游戏是练逻辑最好的起点3.1 游戏程序的最小模型无论游戏多复杂核心循环都可以拆成三步读取输入键盘、鼠标、文件、传感器。更新游戏状态位置、血量、分数、关卡。渲染/输出结果打印到控制台、绘制到窗口。控制台游戏最大的价值就是先把这个闭环跑通。下面用一个“猜数字”游戏来演示。3.2 猜数字完整实现import random def main(): secret random.randint(1, 100) total 0 print(猜数字游戏范围 1 ~ 100) while True: try: guess int(input(请输入你猜的数字)) except ValueError: print(请输入一个合法整数) continue total 1 if guess secret: print(猜小了) elif guess secret: print(猜大了) else: print(f恭喜你用了 {total} 次猜中了数字 {secret}) break if __name__ __main__: main()运行方式很简单python guess_number.py这段代码虽然短但包含了几个很关键的基础知识点random.randint(1, 100)生成 1 到 100 之间的随机整数。try / except ValueError处理非数字输入这是实际项目中经常被忽略的健壮性问题。break在猜中后跳出循环。if __name__ __main__:用来隔离“作为脚本运行”和“作为模块导入”的行为。3.3 为什么适合入门猜数字虽然简单但它能延伸到很多项目记录用户猜数字次数保存到数据库做成“最少次数排行榜”。增加难度选择比如 1~50、1~500。增加“挑战模式”限定 5 次猜中否则游戏结束。一个控制台小游戏如果能把分数存进数据库再做一个排行榜查询其实已经具备“课程设计”的雏形了。后面我会专门讲数据库部分这里先有一个概念数据库解决的问题是“程序退出后数据仍然保留”。4. 图形化游戏Pygame 贪吃蛇实战4.1 安装 Pygame 与最小窗口Pygame 是 Python 游戏开发最常用的 2D 游戏库。安装完成之后先跑一个最小窗口程序确认环境没问题import pygame pygame.init() screen pygame.display.set_mode((480, 480)) pygame.display.set_caption(Pygame 测试窗口) running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False pygame.quit()这里最关键的是pygame.event.get()它负责把用户操作从系统事件队列里取出来。如果程序不处理事件窗口点“关闭”也不会退出看上去就像卡死了一样。4.2 简化版贪吃蛇完整代码接下来实现一个可以运行的简化版贪吃蛇。我用 20 像素作为一格蛇移动方向通过方向键控制撞墙或撞到自己则游戏结束。import random import sys import pygame # 初始化 pygame.init() width, height 480, 480 cell 20 screen pygame.display.set_mode((width, height)) pygame.display.set_caption(简化版贪吃蛇) clock pygame.time.Clock() # 蛇的初始位置和初始方向 direction (cell, 0) snake [(240, 240), (220, 240), (200, 240)] # 生成食物避免生成到蛇身上 def random_food(): while True: pos ( random.randint(0, width // cell - 1) * cell, random.randint(0, height // cell - 1) * cell, ) if pos not in snake: return pos food random_food() score 0 running True while running: for event in pygame.event.get(): if event.type pygame.QUIT: running False elif event.type pygame.KEYDOWN: if event.key pygame.K_UP and direction ! (0, cell): direction (0, -cell) if event.key pygame.K_DOWN and direction ! (0, -cell): direction (0, cell) if event.key pygame.K_LEFT and direction ! (cell, 0): direction (-cell, 0) if event.key pygame.K_RIGHT and direction ! (-cell, 0): direction (cell, 0) head_x, head_y snake[0] new_head (head_x direction[0], head_y direction[1]) # 撞墙或撞自己游戏结束 if ( new_head[0] 0 or new_head[0] width or new_head[1] 0 or new_head[1] height or new_head in snake ): break snake.insert(0, new_head) if new_head food: score 1 food random_food() else: snake.pop() # 绘制 screen.fill((30, 30, 30)) for x, y in snake: pygame.draw.rect(screen, (0, 180, 0), (x, y, cell - 2, cell - 2)) pygame.draw.rect(screen, (200, 50, 50), (food[0], food[1], cell - 2, cell - 2)) pygame.display.flip() clock.tick(10) # 控制游戏速度 pygame.quit() print(f游戏结束得分{score})4.3 主循环和碰撞检测的关键点这段代码里有几个点需要重点理解因为老师大概率会围绕这些点提问蛇的数据结构用列表存坐标snake[0]是蛇头insert(0, new_head)让蛇头往前长一格。吃到食物和没吃到食物的区别吃了食物蛇长度增加没吃到snake.pop()把尾巴移除看起来蛇在移动。碰撞检测新蛇头坐标是否超出窗口边界或者是否已经在蛇身体的坐标集合里。方向防反转direction ! (0, cell)表示不能直接往下走的时候按“上”变成反向移动。这个版本还有很多可以优化的空间。比如加入“加速”机制每吃一个食物clock.tick(10)中的 10 可以逐渐变大加入暂停功能加入音效和背景图把最高分保存到数据库中等。每一步都是很好的课程设计扩展点。5. 从单机到数据游戏里的数据库设计5.1 为什么游戏项目需要数据库很多控制台或 Pygame 游戏一开始是“无状态”的程序关了分数就没了。但期末大作业如果只有这个功能老师说一句“数据没有持久化”就会很被动。给游戏接入数据库通常是为了实现用户注册与登录。历史最高分记录。排行榜展示。记录游戏局数和通关情况。Python 游戏项目最常用的数据库是 SQLite因为它是 Python 标准库的一部分不需要额外安装服务数据库就是一个本地文件非常适合课程设计和毕设演示。如果以后想换成 MySQL只需要把数据库连接层单独封装游戏逻辑不受影响。5.2 使用 SQLite 建表以排行榜为例一张最简单的成绩表可以这样设计CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, player_name TEXT NOT NULL, cost_time REAL NOT NULL, moves INTEGER NOT NULL, submit_time TEXT NOT NULL DEFAULT (datetime(now, localtime)) );字段含义id自增主键用于唯一标识每条成绩。player_name玩家名称。cost_time花费时间单位秒。moves操作次数。submit_time提交时间默认取本地时间。这里面有几个容易踩坑的地方REAL类型用来存储小数适合存放耗时。不要用name这种关键字作为字段名容易引发歧义。成绩表建议带上submit_time否则无法回答“什么时候玩的”这类需求。5.3 Python 操作 SQLitePython 操作 SQLite 的接口非常简单。连接数据库时如果文件不存在SQLite 会自动创建。import sqlite3 DB_PATH memory_game.db def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, player_name TEXT NOT NULL, cost_time REAL NOT NULL, moves INTEGER NOT NULL, submit_time TEXT NOT NULL DEFAULT (datetime(now, localtime)) ) ) conn.commit() conn.close() def save_score(name, cost, moves): conn sqlite3.connect(DB_PATH) conn.execute( INSERT INTO scores (player_name, cost_time, moves) VALUES (?, ?, ?), (name, cost, moves), ) conn.commit() conn.close() def get_rank(limit5): conn sqlite3.connect(DB_PATH) rows conn.execute( SELECT player_name, cost_time, moves, submit_time FROM scores ORDER BY cost_time ASC, moves ASC LIMIT ?, (limit,), ).fetchall() conn.close() return rows代码中使用的?占位符是 Python sqlite3 模块推荐写法能有效避免 SQL 注入。很多初学教程把变量直接拼到 SQL 字符串里是非常危险的习惯# 不推荐有 SQL 注入风险 conn.execute(fINSERT INTO scores VALUES ({name}, {cost}, {moves})) # 推荐参数化查询 conn.execute(INSERT INTO scores (player_name, cost_time, moves) VALUES (?, ?, ?), (name, cost, moves))如果输入名称包含; DROP TABLE scores; --这类内容直接拼接字符串可能导致数据表被删掉。参数化查询让 SQLite 把用户数据当作纯文本处理这是数据库操作的安全底线。5.4 数据库文件随项目一起开源要注意什么很多开源项目会直接附带.db文件方便用户下载后立即运行。这种做法有两个问题如果有用户隐私数据直接提交到仓库会泄露。.db文件是二进制产物合并冲突很难处理。更推荐的做法是在项目里保留init.sql建表脚本并在程序第一次运行时自动执行建表语句。文档里写明“初始化数据库运行python init_db.py”即可。这样既保证了开箱即用又不会把个人成绩数据带到仓库里。6. 完整项目实战带排行榜的记忆翻牌游戏前面的例子比较分散这一节做一个能直接作为课程设计交付的完整项目基于 Tkinter SQLite 的“记忆翻牌游戏”。它不需要安装 Pygame只要 Python 标准库就能运行而且同时覆盖了 GUI 编程、游戏逻辑和数据库排行榜非常适合期末大作业。6.1 需求分析游戏规则很简单棋盘上有若干张扣着的卡片每两张卡片图案相同。玩家每次翻开两张如果图案相同则配对成功并保持翻开否则自动扣回去。游戏结束后根据完成时间和翻牌次数生成排行榜。功能可以拆成五块初始化卡片并随机打乱。玩家点击卡片显示图案。两次点击后判断是否匹配。全部配对成功时计算耗时。将成绩保存到 SQLite并查看前五名。6.2 项目结构memory_game/ ├── game.py # 游戏主程序 ├── database.py # 数据库初始化与操作 ├── memory_game.db # 程序运行后自动生成 └── README.md # 项目说明为了降低阅读门槛我把核心代码放在一个文件里但在实际项目中建议把数据库部分独立成database.py。6.3 完整代码import random import sqlite3 import time import tkinter as tk from tkinter import messagebox, simpledialog # 卡片图案每个图案出现两次共 3*412 张卡片 SYMBOLS [♠, ♥, ♦, ♣, ★, ●] ROWS, COLS 3, 4 PAIRS (ROWS * COLS) // 2 DB_PATH memory_game.db def init_db(): conn sqlite3.connect(DB_PATH) conn.execute( CREATE TABLE IF NOT EXISTS scores ( id INTEGER PRIMARY KEY AUTOINCREMENT, player_name TEXT NOT NULL, cost_time REAL NOT NULL, moves INTEGER NOT NULL, submit_time TEXT NOT NULL DEFAULT (datetime(now, localtime)) ) ) conn.commit() conn.close() def save_score(name, cost, moves): conn sqlite3.connect(DB_PATH) conn.execute( INSERT INTO scores (player_name, cost_time, moves) VALUES (?, ?, ?), (name, cost, moves), ) conn.commit() conn.close() def get_rank_text(limit5): conn sqlite3.connect(DB_PATH) rows conn.execute( SELECT player_name, cost_time, moves, submit_time FROM scores ORDER BY cost_time ASC, moves ASC LIMIT ?, (limit,), ).fetchall() conn.close() if not rows: return 暂无成绩记录快来抢第一名 lines [] for rank, (name, cost, moves, submit_time) in enumerate(rows, 1): lines.append(f{rank}. {name}{cost} 秒 / {moves} 次{submit_time}) return \n.join(lines) class MemoryGame: def __init__(self, root): self.root root self.root.title(记忆翻牌游戏) self.root.resizable(False, False) self.start_time None self.moves 0 self.matched 0 self.first None self.second None self.lock False self.cards (SYMBOLS * 2)[:ROWS * COLS] random.shuffle(self.cards) self.buttons [] self.create_widgets() self.start_time time.time() def create_widgets(self): info tk.Frame(self.root) info.pack(fillx, padx10, pady8) self.info_label tk.Label( info, textf翻牌次数: 0 配对: 0/{PAIRS}, font(微软雅黑, 12), ) self.info_label.pack(sideleft) tk.Button(info, text重新开始, commandself.restart).pack(sideright) board tk.Frame(self.root) board.pack(padx10, pady10) for i in range(ROWS * COLS): btn tk.Button( board, text?, width6, height3, font(Arial, 20), commandlambda idxi: self.choose(idx), ) btn.grid(rowi // COLS, columni % COLS, padx4, pady4) self.buttons.append(btn) def choose(self, idx): if self.lock or self.buttons[idx][text] ! ?: return self.moves 1 self.buttons[idx][text] self.cards[idx] self.buttons[idx][state] disabled self.update_info() if self.first is None: self.first idx else: self.second idx self.lock True self.root.after(600, self.check_match) def check_match(self): b1 self.buttons[self.first] b2 self.buttons[self.second] if self.cards[self.first] self.cards[self.second]: b1[bg] #90EE90 b2[bg] #90EE90 self.matched 1 else: b1[text] ? b1[state] normal b2[text] ? b2[state] normal self.first None self.second None self.lock False self.update_info() if self.matched PAIRS: self.finish() def update_info(self): self.info_label.config( textf翻牌次数: {self.moves} 配对: {self.matched}/{PAIRS} ) def finish(self): cost round(time.time() - self.start_time, 2) name simpledialog.askstring( 游戏完成, f用时 {cost} 秒翻牌 {self.moves} 次\n请输入你的名字, ) if not name: name 匿名玩家 save_score(name, cost, self.moves) messagebox.showinfo(排行榜, get_rank_text()) def restart(self): self.cards (SYMBOLS * 2)[:ROWS * COLS] random.shuffle(self.cards) self.moves 0 self.matched 0 self.first None self.second None self.lock False for btn in self.buttons: btn.config(text?, statenormal, bgSystemButtonFace) self.start_time time.time() self.update_info() if __name__ __main__: init_db() root tk.Tk() game MemoryGame(root) root.mainloop()6.4 运行与验证在项目目录下运行python game.py预期流程窗口显示 12 张背面为“?”的卡片。点击卡片翻面显示♠、♥等符号。连续点击两张如果不匹配0.6 秒后自动翻回。如果匹配卡片背景变绿并保持打开状态。全部配对完成后弹出输入框要求输入玩家名字。输入名字后弹出排行榜显示当前前五名。第二次运行程序时之前保存的成绩依然还在这就是数据库持久化的效果。可以在终端里查看数据库文件sqlite3 memory_game.db select * from scores;如果没有安装 sqlite3 命令行工具也可以继续用 Python 查询。6.5 如何扩展成毕设这个项目可以作为“期末大作业”直接交但如果你想把它扩展到毕设级别可以考虑以下方向增加不同难度4x4、4x5 卡片布局需要准备更多图案。增加图片资源用本地图片替代字符变成真正意义上的“图片记忆游戏”。增加计时器控件在窗口上实时显示倒计时或正计时。增加用户登录users表 登录窗口记录每个玩家历史成绩。增加统计图表把每日游戏次数、平均耗时用 matplotlib 或 pyecharts 画出来。改成 Web 版用 Flask 渲染页面前端用 JavaScript 实现翻牌逻辑后端用 SQLite 存成绩。每加一个方向工作量和技术含量都会明显提升而且每一项都是能写进答辩 PPT 的“亮点”。7. 从游戏项目走向框架Flask 排行榜接口7.1 为什么要学框架很多同学做 Python 游戏项目时会觉得“我只要会 Pygame 就够了”。实际上企业开发里更常用的是 Web 框架和数据处理框架。游戏项目本身不一定是 Flask 项目但我们可以把游戏里攒下来的数据用 Flask 暴露成一个接口从而把“单机游戏”和“Web 服务”串起来。这也是为什么热搜里“Python 框架”“开源项目”总是一起出现的原因。框架解决的核心问题是怎么在三五行代码里把功能暴露给外部调用。7.2 Flask 返回 JSON 排行榜下面这段代码把上一节 SQLite 中的排行榜数据封装成一个 HTTP 接口。它可以单独运行也可以部署在服务器上供前端调用。from flask import Flask, jsonify import sqlite3 app Flask(__name__) DB_PATH memory_game.db app.route(/api/rank) def rank(): conn sqlite3.connect(DB_PATH) rows conn.execute( SELECT player_name, cost_time, moves FROM scores ORDER BY cost_time ASC, moves ASC LIMIT 5 ).fetchall() conn.close() data [ {name: row[0], time: row[1], moves: row[2]} for row in rows ] return jsonify(data) if __name__ __main__: app.run(debugTrue)运行后访问http://127.0.0.1:5000/api/rank就能看到类似下面的 JSON 输出[ { name: 张三, time: 18.66, moves: 18 }, { name: 李四, time: 25.1, moves: 22 } ]这里需要提醒一个细节浏览器直接访问http://127.0.0.1:5000/api/rank时Flask 在debugTrue模式下会输出彩色日志和错误信息这是开发模式。如果要部署到服务器建议关掉 debug并使用生产级 WSGI 服务器比如waitress或gunicorn。7.3 前端页面可选如果只是想展示排行榜可以再写一个最简单的 HTML 页面用fetch拉取接口数据并渲染成表格。这个在写 Web 版游戏时非常常见核心是把后端数据和前端展示分离。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title游戏排行榜/title /head body h1记忆翻牌游戏排行榜/h1 table idrank-table border1 thead tr th排名/th th玩家/th th用时(秒)/th th翻牌次数/th /tr /thead tbody/tbody /table script fetch(http://127.0.0.1:5000/api/rank) .then((response) response.json()) .then((data) { const tbody document.querySelector(#rank-table tbody); data.forEach((item, index) { const tr document.createElement(tr); tr.innerHTML td${index 1}/td td${item.name}/td td${item.time}/td td${item.moves}/td ; tbody.appendChild(tr); }); }) .catch((error) { console.error(加载排行榜失败, error); }); /script /body /html注意浏览器直接打开本地 HTML 文件时跨域请求可能被拦截。最简单的做法是通过 Flask 提供静态文件服务或者先学习“同源策略”相关概念。8. 常见问题与排查清单写游戏项目和学习开源代码时下面的问题出现频率很高。我把现象、常见原因和解决思路整理成了一个表格方便你在排查时快速定位。问题现象常见原因解决思路pip install pygame很慢或失败默认源下载速度慢使用清华、阿里等国内镜像源安装Pygame 窗口弹出后立刻关闭主循环里没有处理事件或循环直接结束检查while running循环是否进入死循环或提前 breakPygame 键盘按了没反应事件判断逻辑写错或按键冲突导致方向被反转打印事件类型定位再检查方向防反转条件Tkinter 窗口卡死在mainloop()外写长耗时操作或忘记更新界面使用root.after代替阻塞式time.sleep游戏运行后数据库文件没生成程序没执行到init_db()或者数据库路径不可写确认入口调用init_db()检查当前目录权限中文在 pygame/Tkinter 中显示为方框字体或编码不匹配在 Tkinter 中设置中文字体Pygame 需加载中文字体文件用pyinstaller打包后找不到图片/数据库资源使用相对路径但打包后的工作目录变化用sys._MEIPASS处理资源路径参考打包工具文档SQLite 查询返回空结果表名/字段名写错或忘了commit()先打印 SQL检查字段名大小写确认数据已提交游戏运行时成绩不更新数据库连接打开后没有关闭导致文件被锁定每次操作都用with sqlite3.connect(DB_PATH) as conn:或手动close()这里展开说一下两个“最容易耗掉时间”的坑。第一个是pygame安装失败。如果你使用 Python 3.9 以上版本通常情况下直接pip install pygame就能得到预编译包。如果仍然失败可以先升级 pip 再安装python -m pip install --upgrade pip pip install pygame第二个是打包问题。很多同学完成游戏后想用 PyInstaller 打包成 exe 给老师演示结果发现双击运行后窗口一闪而过。这通常是因为资源文件路径根据当前工作目录发生了变化。排查时先在终端里手工运行打包后的 exe看有没有报错信息然后再检查资源路径。9. 期末大作业与毕设选型建议9.1 怎么选项目才不吃亏选项目有一个很现实的原则不要选自己讲不清楚的项目。老师的答辩时间有限一个你能拆成“输入、逻辑、数据、界面”四个模块的项目比一个你只是“能跑起来”但解释不了的项目安全得多。如果你现在还只有一个学期或几周的周期建议优先选“功能成熟、数据库清晰、可以二次开发”的游戏。比如记忆翻牌、贪吃蛇、猜数字加排行榜都是很好的选择。如果你要冲高分可以在这些基础项目上叠加一两个亮点比如 AI 自动翻牌、难度分级、多用户登录、Web 端实时排行榜。9.2 二次开发思路直接抄一套源码交差答辩时很容易露馅。比较聪明的做法是“站在已有项目上做增量开发”。比如拿到一套贪吃蛇源码你可以做这几件事把原来的全局函数改写成类增加可读性。增加“最高分排行榜”用 SQLite 保存。增加“游戏暂停”和“重新开始”功能。增加“障碍物关卡”每过 5 关增加障碍物。这四点每一点都能单独说明工作量不大但“可扩展性”和技术含量看起来完全不同。开源项目的正确学习方法不是读完整个项目而是挑一个具体功能看懂它、改掉它、再扩展它。9.3 答辩高频问题根据我看到的课程设计和毕设答辩情况老师最常问的问题集中在以下几点游戏主循环是怎么工作的为什么不会卡死数据库连接什么时候打开、什么时候关闭排行榜是怎么排名的如果分数相同怎么办你在这个项目里独立完成了哪部分如果用户输入非法数据程序会怎么处理项目还能怎么扩展这些问题前面已经覆盖了一部分。回答时建议先说“我用 XX 模块处理 XX 问题”再给一个具体代码位置。哪怕只答上两句也比背一段“设计模式”强得多。10. 最佳实践与工程建议10.1 面向对象封装很多同学写游戏喜欢用全局变量从长期维护角度看建议用类来管理状态。尤其是“玩家信息”“游戏状态”“数据库操作”这三块适合封装成独立类。一个简单的例子class Player: def __init__(self, name): self.name name self.games 0 self.total_time 0.0 def add_game(self, cost_time): self.games 1 self.total_time cost_time def average_time(self): if self.games 0: return 0.0 return round(self.total_time / self.games, 2)这样做的好处是“数据和方法不散落”后续扩展时逻辑更集中。10.2 数据库访问要短连接SQLite 的常见使用方式有两种一种是长连接程序启动时连接一次退出时关闭一种是短连接每次操作都打开、操作、关闭。对于桌面游戏项目推荐使用短连接。原因很简单SQLite 适合低并发场景频繁开关连接的开销可以接受而且能避免文件锁冲突。如果在游戏主循环里长期持有数据库连接一旦操作过程中出现异常文件锁可能无法及时释放。更稳妥的写法是用with语句import sqlite3 DB_PATH memory_game.db with sqlite3.connect(DB_PATH) as conn: conn.execute(INSERT INTO scores ..., (name, cost, moves))在这个上下文管理器中事务会在with