20242121《Python程序设计》实验四报告

20242121《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2421
姓名: 陈云龙
学号:20242121
实验教师:王志强
实验日期:2025年5月14日
必修/选修:公选课

一、 实验内容
使用python编写一个扫雷小游戏
二、实验要求
程序能运行,功能丰富。(需求提交源代码,并建议录制程序运行的视频)
综合实践报告,要体现实验分析、设计、实现过程、结果等信息,格式规范,逻辑清晰,结构合理
在实践报告中,需要对全课进行总结,并写课程感想体会、意见和建议等
程序代码托管到码云
三、实验过程及结果

  1. 导入必要的库
首先需要导入  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("", lambda event, x=i, y=j: self.right_click(x, y))
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的帮助下写一个游戏。
非常期待与王老师的下一次相遇。
posted @ 2025-06-10 21:21  cylshushu  阅读(78)  评论(0)    收藏  举报