20242121《Python程序设计》实验四报告
20242121《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2421
姓名: 陈云龙
学号:20242121
实验教师:王志强
实验日期:2025年5月14日
必修/选修:公选课
一、 实验内容
使用python编写一个扫雷小游戏
二、实验要求
程序能运行,功能丰富。(需求提交源代码,并建议录制程序运行的视频)
综合实践报告,要体现实验分析、设计、实现过程、结果等信息,格式规范,逻辑清晰,结构合理
在实践报告中,需要对全课进行总结,并写课程感想体会、意见和建议等
程序代码托管到码云
三、实验过程及结果
- 导入必要的库
首先需要导入 tkinter 用于创建图形界面, random 用于随机布雷。
import tkinter as tk
import random
2.创建游戏主类
定义 Minesweeper 类,初始化游戏参数(棋盘大小、地雷数),并调用界面创建方法。
class Minesweeper:
def __init__(self, root, rows=10, cols=10, mines=10):
self.root = root # 主窗口
self.root.title("扫雷游戏") # 窗口标题
# 游戏参数(可修改为其他数值)
self.rows = rows # 行数
self.cols = cols # 列数
self.mines = mines # 地雷数
# 核心数据结构
self.board = [] # 存储地雷分布(True=雷)
self.revealed = [] # 存储格子状态(-1=未翻开,数字=雷数,-2=标记雷)
self.remaining_mines = mines # 剩余可标记雷数
self.game_over = False # 游戏状态(是否结束)
# 创建界面组件
self.create_widgets()
# 初始化游戏
self.reset_game()
3.创建界面组件
绘制顶部的重置按钮、雷数计数器,以及棋盘格子按钮。
def create_widgets(self):
# 顶部控制面板(重置按钮和雷数显示)
top_frame = tk.Frame(self.root)
top_frame.pack(pady=10)
# 重置按钮(初始为笑脸图标)
self.reset_btn = tk.Button(top_frame, text="🌕", font=('Arial', 14),
command=self.reset_game)
self.reset_btn.pack(side=tk.LEFT, padx=5)
# 剩余雷数标签
self.mine_label = tk.Label(top_frame, text=f"剩余雷:{self.remaining_mines}",
font=('Arial', 12))
self.mine_label.pack(side=tk.LEFT, padx=10)
# 创建棋盘按钮(3x3网格,每行每列一个按钮)
self.buttons = [] # 存储所有按钮,方便后续操作
for i in range(self.rows):
row = []
for j in range(self.cols):
# 创建按钮,绑定左键点击事件
btn = tk.Button(self.root, text="", width=3, height=1,
font=('Arial', 12, 'bold'),
command=lambda x=i, y=j: self.left_click(x, y))
# 绑定右键点击事件(通过<Button-3>事件)
btn.bind("<Button-3>", lambda event, x=i, y=j: self.right_click(x, y))
btn.grid(row=i+1, column=j, padx=1, pady=1) # 按钮从第1行开始排列
row.append(btn)
self.buttons.append(row)
4.重置游戏(初始化地雷和界面)
点击“重新开始”时调用,随机生成地雷位置,并清空界面。
def reset_game(self):
self.game_over = False
self.remaining_mines = self.mines
self.mine_label.config(text=f"剩余雷:{self.remaining_mines}")
# 初始化地雷分布:随机在棋盘放置地雷
self.board = [[False for _ in range(self.cols)] for _ in range(self.rows)]
mines_placed = 0
while mines_placed < self.mines:
i = random.randint(0, self.rows-1)
j = random.randint(0, self.cols-1)
if not self.board[i][j]: # 确保不重复布雷
self.board[i][j] = True
mines_placed += 1
# 初始化所有格子为未翻开状态
self.revealed = [[-1 for _ in range(self.cols)] for _ in range(self.rows)]
for i in range(self.rows):
for j in range(self.cols):
btn = self.buttons[i][j]
btn.config(text="", bg="SystemButtonFace", state="normal") # 恢复按钮状态
self.reset_btn.config(text="🌕") # 重置按钮图标为笑脸
5.处理左键点击(翻开格子)
点击格子时,判断是否是地雷,若是则游戏失败;否则计算周围雷数并展开。
def left_click(self, i, j):
if self.game_over or self.revealed[i][j] != -1:
return # 游戏结束或格子已翻开,直接返回
if self.board[i][j]: # 踩到地雷,游戏失败
self.reveal_all_mines() # 显示所有地雷
self.game_over = True
self.reset_btn.config(text="💥") # 按钮图标变爆炸
return
# 正常翻开格子,计算周围雷数并展开
self.reveal_cell(i, j)
self.check_win() # 检查是否胜利
6.处理右键点击(标记地雷)
右键点击时标记或取消标记地雷,更新剩余雷数显示。
def right_click(self, i, j):
if self.game_over or self.revealed[i][j] != -1:
return # 游戏结束或格子已翻开,无法标记
if self.revealed[i][j] == -1: # 未翻开状态
if self.remaining_mines > 0: # 还有剩余标记次数,标记地雷
self.revealed[i][j] = -2
self.buttons[i][j].config(text="🚩", bg="lightblue") # 显示红旗标记
self.remaining_mines -= 1
else: # 取消标记
self.revealed[i][j] = -1
self.buttons[i][j].config(text="", bg="SystemButtonFace") # 清除标记
self.remaining_mines += 1
self.mine_label.config(text=f"剩余雷:{self.remaining_mines}") # 更新剩余雷数
7.展开格子(递归展开空白区域)
当翻开的格子周围雷数为0时,自动展开相邻格子,类似DFS算法。
def reveal_cell(self, i, j):
if self.revealed[i][j] != -1 or self.game_over:
return # 已翻开或游戏结束,不处理
# 计算当前格子周围的地雷数
mine_count = self.count_mines(i, j)
self.revealed[i][j] = mine_count # 记录雷数
# 更新按钮显示:显示雷数或空白(雷数为0时不显示数字)
btn = self.buttons[i][j]
if mine_count > 0:
btn.config(text=str(mine_count), state="disabled", bg="lightgray")
else:
btn.config(state="disabled", bg="lightgray") # 空白格子设为浅灰色
# 如果周围雷数为0,递归展开相邻的8个格子
if mine_count == 0:
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
ni, nj = i + dx, j + dy
if 0 <= ni < self.rows and 0 <= nj < self.cols:
self.reveal_cell(ni, nj) # 递归调用自身展开相邻格子
8.计算周围地雷数
遍历当前格子周围8个方向,统计地雷数量。
def count_mines(self, i, j):
count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0: # 跳过自身
continue
ni, nj = i + dx, j + dy
# 检查坐标是否在棋盘范围内,并统计地雷数
if 0 <= ni < self.rows and 0 <= nj < self.cols and self.board[ni][nj]:
count += 1
return count
9.游戏失败时显示所有地雷
踩到地雷后,显示所有地雷位置,标记正确的雷显示红旗,错误标记的雷显示叉号。
def reveal_all_mines(self):
for i in range(self.rows):
for j in range(self.cols):
btn = self.buttons[i][j]
if self.board[i][j]: # 是地雷,显示炸弹图标
btn.config(text="💣", bg="red", fg="white")
else:
# 非地雷但被错误标记的格子,显示叉号
if self.revealed[i][j] == -2:
btn.config(text="❌", bg="lightcoral")
btn.config(state="disabled") # 禁用所有按钮
10.检查是否胜利
遍历所有格子,若所有非地雷格子都已翻开,则游戏胜利。
def check_win(self):
for i in range(self.rows):
for j in range(self.cols):
# 只要存在未翻开的非地雷格子,就未胜利
if self.revealed[i][j] == -1 and not self.board[i][j]:
return
# 所有非地雷格子已翻开,胜利!
self.game_over = True
self.reset_btn.config(text="🎉") # 按钮图标变庆祝
# 胜利时,正确标记的雷显示绿色红旗
for i in range(self.rows):
for j in range(self.cols):
if self.revealed[i][j] == -2 and self.board[i][j]:
self.buttons[i][j].config(bg="lightgreen")
11.运行游戏
创建主窗口并启动游戏循环。
if name == "main":
root = tk.Tk()
game = Minesweeper(root) # 初始化游戏(默认10x10棋盘,10颗雷)
root.mainloop() # 启动界面循环
完整代码:
import tkinter as tk
import random
class Minesweeper:
def init(self, root, rows=10, cols=10, mines=10):
self.root = root
self.root.title("扫雷游戏")
self.rows = rows
self.cols = cols
self.mines = mines
self.board = [] # 存储地雷分布(True=雷,False=非雷)
self.revealed = [] # 存储已翻开的格子(-1=未翻开,数字=周围雷数,-2=标记为雷)
self.remaining_mines = mines # 剩余可标记雷数
self.game_over = False # 游戏状态
创建界面组件
self.create_widgets()
self.reset_game()
def create_widgets(self):
# 顶部控制面板
self.top_frame = tk.Frame(self.root)
self.top_frame.pack(pady=5)
self.reset_btn = tk.Button(self.top_frame, text="🌕", font=('Arial', 14),
command=self.reset_game)
self.reset_btn.pack(side=tk.LEFT, padx=5)
self.mine_label = tk.Label(self.top_frame, text=f"剩余雷:{self.remaining_mines}",
font=('Arial', 12))
self.mine_label.pack(side=tk.LEFT, padx=10)
创建棋盘按钮
self.buttons = []
for i in range(self.rows):
row = []
for j in range(self.cols):
btn = tk.Button(self.root, text="", width=3, height=1,
font=('Arial', 12, 'bold'),
command=lambda x=i, y=j: self.left_click(x, y))
btn.grid(row=i + 1, column=j, padx=1, pady=1)
btn.bind("
row.append(btn)
self.buttons.append(row)
def reset_game(self):
self.game_over = False
self.remaining_mines = self.mines
self.mine_label.config(text=f"剩余雷:{self.remaining_mines}")
初始化地雷分布(随机布雷)
self.board = [[False for _ in range(self.cols)] for _ in range(self.rows)]
mines_placed = 0
while mines_placed < self.mines:
i, j = random.randint(0, self.rows - 1), random.randint(0, self.cols - 1)
if not self.board[i][j]:
self.board[i][j] = True
mines_placed += 1
初始化已翻开状态
self.revealed = [[-1 for _ in range(self.cols)] for _ in range(self.rows)]
for i in range(self.rows):
for j in range(self.cols):
self.buttons[i][j].config(text="", bg="SystemButtonFace", state="normal")
self.reset_btn.config(text="🌕")
def left_click(self, i, j):
if self.game_over or self.revealed[i][j] != -1:
return
if self.board[i][j]:
# 踩到雷,游戏失败
self.reveal_all_mines()
self.game_over = True
self.reset_btn.config(text="💥")
return
计算周围雷数并展开
self.reveal_cell(i, j)
self.check_win()
def right_click(self, i, j):
if self.game_over or self.revealed[i][j] != -1:
return
if self.revealed[i][j] == -1:
if self.remaining_mines > 0:
self.revealed[i][j] = -2 # 标记为雷
self.buttons[i][j].config(text="🚩", bg="lightblue")
self.remaining_mines -= 1
else:
# 取消标记
self.revealed[i][j] = -1
self.buttons[i][j].config(text="", bg="SystemButtonFace")
self.remaining_mines += 1
self.mine_label.config(text=f"剩余雷:{self.remaining_mines}")
def reveal_cell(self, i, j):
if self.revealed[i][j] != -1 or self.game_over:
return
计算周围雷数
mine_count = self.count_mines(i, j)
self.revealed[i][j] = mine_count
if mine_count == 0:
# 自动展开相邻格子
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
ni, nj = i + dx, j + dy
if 0 <= ni < self.rows and 0 <= nj < self.cols:
self.reveal_cell(ni, nj)
更新按钮显示
if mine_count > 0:
self.buttons[i][j].config(text=str(mine_count), state="disabled", bg="lightgray")
else:
self.buttons[i][j].config(state="disabled", bg="lightgray")
def count_mines(self, i, j):
count = 0
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
ni, nj = i + dx, j + dy
if 0 <= ni < self.rows and 0 <= nj < self.cols and self.board[ni][nj]:
count += 1
return count
def reveal_all_mines(self):
for i in range(self.rows):
for j in range(self.cols):
if self.board[i][j]:
self.buttons[i][j].config(text="💣", bg="red")
else:
self.buttons[i][j].config(state="disabled")
def check_win(self):
for i in range(self.rows):
for j in range(self.cols):
if self.revealed[i][j] == -1 and not self.board[i][j]:
return
self.game_over = True
self.reset_btn.config(text="🎉")
self.reveal_all_mines() # 胜利时显示标记正确的雷
if name == "main":
root = tk.Tk()
game = Minesweeper(root)
root.mainloop()
三、问题解决
1. 右键点击无效:
确保 btn.bind("<Button-3>", ...) 正确绑定,部分系统可能需要用 <Button-2> (需自行测试)。
2. 格子点击无反应:
检查 state="normal" 是否在重置时正确设置(禁用状态的按钮无法点击)。
3. 地雷数量不对:
检查 reset_game 中布雷循环是否正确,确保 mines_placed < self.mines 。
4.
在 Tkinter 中,同一个父容器(如 root)不能同时用 pack() 和 grid() 方法管理子控件。你的代码在 self.root 上既用了 pack()(top_frame),又直接在 self.root 上用了 grid()(棋盘按钮),导致冲突。
解决:把棋盘按钮放到一个新的 Frame 容器里,然后用 grid 管理。
四、点击观看演示视频(Bilibili)
https://t.bilibili.com/1076847884224167941?share_source=pc_native
五、课程总结
王老师人真的很好,我觉得十分人性化,课堂气氛活跃,通过这学期王老师的python公选课,我还是多多少少学到不少东西,从最开始只能写个"hello word!"代码,到现在能在ai的帮助下写一个游戏。
非常期待与王老师的下一次相遇。

浙公网安备 33010602011771号