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

20222223 2024-2025-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级: 2222
姓名: 李東霖
学号:20222223
实验教师:王志强
实验日期:2025年5月14日
必修/选修: 公选课

一、实验目的

作为计算机科学专业的学生,我在本学期的Python课程学习了必要的知识,是时候大展身手做一个小游戏了,那就先从复刻经典扫雷游戏开始吧。这个项目完全聚焦于Python在游戏开发中的应用,我将详细分享从游戏设计到最终实现的全过程,以及在开发过程中解决的技术挑战与获得的宝贵经验。

二、实验内容

1.游戏架构与核心技术

1.1游戏设计理念

扫雷游戏的核心设计理念围绕以下几个关键点:
简洁直观的界面设计,首次点击绝对安全提供游戏性,实时显示格子状态变化给予反馈,游戏失败后显示所有地雷位置验证判断是否正确。

1.2游戏核心组件

class Cell:
    def __init__(self):
        self.is_mine = False
        self.is_revealed = False
        self.is_flagged = False
        self.neighbor_mines = 0
        self.correctly_flagged = False  # 用于游戏结束时的正确标记检测

class Board:
    def __init__(self, width, height, mines):
        self.width = width
        self.height = height
        self.mines = mines
        self.cells = [[Cell() for _ in range(height)] for _ in range(width)]
        self.first_click = True
        self.game_over = False
        self.win = False
        self.mines_flagged = 0

1.3核心算法实现

安全布雷算法

def place_mines(self, first_x, first_y):
    # 创建安全区域(3×3方格)
    safe_cells = []
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            nx, ny = first_x + dx, first_y + dy
            if 0 <= nx < self.width and 0 <= ny < self.height:
                safe_cells.append((nx, ny))
    
    # 在安全区域外随机布雷
    mines_placed = 0
    while mines_placed < self.mines:
        x = random.randint(0, self.width - 1)
        y = random.randint(0, self.height - 1)
        if (x, y) not in safe_cells and not self.cells[x][y].is_mine:
            self.cells[x][y].is_mine = True
            mines_placed += 1
            # 更新邻居的地雷计数
            self.update_neighbor_counts(x, y)

def update_neighbor_counts(self, x, y):
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            nx, ny = x + dx, y + dy
            if 0 <= nx < self.width and 0 <= ny < self.height:
                self.cells[nx][ny].neighbor_mines += 1

区域展开算法(递归实现)

def reveal_neighbors(self, x, y):
    # 实现空白区域的连锁展开
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            if dx == 0 and dy == 0:  # 跳过自身
                continue
                
            nx, ny = x + dx, y + dy
            if 0 <= nx < self.width and 0 <= ny < self.height:
                cell = self.cells[nx][ny]
                if not cell.is_revealed and not cell.is_flagged:
                    cell.is_revealed = True
                    if cell.neighbor_mines == 0:
                        self.reveal_neighbors(nx, ny)  # 递归调用

2.Pygame界面实现

2.1界面布局设计

def init_ui(self):
    # 创建游戏窗口
    self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption('扫雷游戏')
    
    # 创建游戏状态面板
    self.info_panel = pygame.Surface((WIDTH, 60))
    self.info_panel.fill((230, 230, 230))
    
    # 创建游戏网格区域
    self.grid_panel = pygame.Surface((GRID_WIDTH * GRID_SIZE, GRID_HEIGHT * GRID_SIZE))
    
    # 初始化字体
    self.font = pygame.font.SysFont('Microsoft YaHei', 24)
    self.title_font = pygame.font.SysFont('Microsoft YaHei', 32)

2.2状态信息展示

def draw_info_panel(self):
    # 绘制旗帜计数
    flags_text = self.font.render(f"旗帜: {self.mines_flagged}/{self.mines}", True, BLUE)
    self.info_panel.blit(flags_text, (20, 20))
    
    # 绘制游戏状态
    status_text = "游戏进行中"
    status_color = BLACK
    if self.game_over:
        status_text = "游戏结束!"
        status_color = RED
    elif self.win:
        status_text = "恭喜获胜!"
        status_color = GREEN
        
    status_render = self.font.render(status_text, True, status_color)
    self.info_panel.blit(status_render, (WIDTH - status_render.get_width() - 30, 20))
    
    # 绘制重置按钮
    pygame.draw.rect(self.info_panel, (180, 180, 180), 
                   (WIDTH//2 - 50, 10, 100, 40))
    reset_text = self.font.render("重置", True, BLACK)
    self.info_panel.blit(reset_text, (WIDTH//2 - reset_text.get_width()//2, 20))

2.3游戏网格渲染

def draw_grid(self):
    for x in range(self.width):
        for y in range(self.height):
            cell = self.cells[x][y]
            rect_x = x * GRID_SIZE
            rect_y = y * GRID_SIZE
            
            # 绘制格子背景
            if cell.is_revealed:
                if self.game_over and cell.is_flagged and not cell.is_mine:
                    pygame.draw.rect(self.grid_panel, LIGHT_RED, (rect_x, rect_y, GRID_SIZE, GRID_SIZE))
                else:
                    pygame.draw.rect(self.grid_panel, WHITE, (rect_x, rect_y, GRID_SIZE, GRID_SIZE))
            else:
                pygame.draw.rect(self.grid_panel, GRAY, (rect_x, rect_y, GRID_SIZE, GRID_SIZE))
            
            # 绘制格子内容
            if cell.is_revealed:
                if cell.is_mine:
                    # 绘制地雷(核心位置)
                    pygame.draw.circle(self.grid_panel, BLACK, 
                                      (rect_x + GRID_SIZE//2, rect_y + GRID_SIZE//2), 
                                      GRID_SIZE//3)
                    # 错误标记提示
                    if self.game_over and not cell.is_flagged:
                        pygame.draw.circle(self.grid_panel, RED, 
                                          (rect_x + GRID_SIZE//2, rect_y + GRID_SIZE//2), 
                                          GRID_SIZE//4, 2)
                elif cell.neighbor_mines > 0:
                    # 绘制数字
                    text = self.font.render(str(cell.neighbor_mines), True, 
                                          NUMBER_COLORS[cell.neighbor_mines])
                    self.grid_panel.blit(text, 
                                       (rect_x + GRID_SIZE//2 - text.get_width()//2, 
                                        rect_y + GRID_SIZE//2 - text.get_height()//2))
            
            # 绘制旗帜标记
            if cell.is_flagged:
                if self.game_over and cell.correctly_flagged:
                    color = GREEN
                else:
                    color = RED
                    
                pygame.draw.polygon(self.grid_panel, color, [
                    (rect_x + 8, rect_y + 8),
                    (rect_x + GRID_SIZE - 8, rect_y + 15),
                    (rect_x + 8, rect_y + GRID_SIZE - 8)
                ])
            
            # 绘制格子边框
            pygame.draw.rect(self.grid_panel, DARK_GRAY, 
                           (rect_x, rect_y, GRID_SIZE, GRID_SIZE), 1)

3.游戏交互设计

3.1鼠标事件处理

def handle_events(self):
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        
        if event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            
            # 检查重置按钮点击
            if WIDTH//2 - 50 <= mouse_x <= WIDTH//2 + 50 and 10 <= mouse_y <= 50:
                self.__init__(GRID_WIDTH, GRID_HEIGHT, MINES)
                continue
            
            # 转换到网格坐标
            grid_x = (mouse_x - GRID_OFFSET_X) // GRID_SIZE
            grid_y = (mouse_y - GRID_OFFSET_Y) // GRID_SIZE
            
            if 0 <= grid_x < self.width and 0 <= grid_y < self.height:
                if event.button == 1:  # 左键翻开格子
                    self.reveal(grid_x, grid_y)
                elif event.button == 3:  # 右键标记旗帜
                    self.toggle_flag(grid_x, grid_y)

3.2游戏状态转换

def reveal(self, x, y):
    cell = self.cells[x][y]
    
    # 跳过已翻开或已标记的格子
    if cell.is_revealed or cell.is_flagged or self.game_over or self.win:
        return
    
    # 首次点击处理
    if self.first_click:
        self.place_mines(x, y)
        self.first_click = False
    
    cell.is_revealed = True
    
    # 触雷处理
    if cell.is_mine:
        self.game_over = True
        self.reveal_all_mines()
        return
    
    # 展开空白区域
    if cell.neighbor_mines == 0:
        self.reveal_neighbors(x, y)
    
    # 检查胜利条件
    self.check_win()

3.3游戏结束处理

def reveal_all_mines(self):
    """揭示所有地雷位置并检测错误标记"""
    for x in range(self.width):
        for y in range(self.height):
            cell = self.cells[x][y]
            
            if cell.is_mine and not cell.is_flagged:
                cell.is_revealed = True  # 显示未标记的地雷
            elif cell.is_flagged and not cell.is_mine:
                cell.is_revealed = True  # 显示错误的标记
            elif cell.is_flagged and cell.is_mine:
                cell.correctly_flagged = True  # 正确标记的旗帜

def check_win(self):
    """检查是否满足胜利条件"""
    for x in range(self.width):
        for y in range(self.height):
            cell = self.cells[x][y]
            
            # 所有非地雷格子都已翻开
            if not cell.is_mine and not cell.is_revealed:
                return
                
            # 所有地雷都已正确标记
            if cell.is_mine and not cell.is_flagged:
                return
    
    self.win = True

三、实验结果

代码已上传到gitee仓库:扫雷
视频在此
运行截图:
image

image

(40个雷我是真的赢不了啊,我知道自己菜,别喷我)

image

四、实验总结

1.遇到的问题

1.1递归展开导致的堆栈溢出问题

问题描述:
当玩家点击空白区域时,游戏需要自动展开相邻的所有空白格子。最初我使用递归算法实现这一功能:

def reveal_neighbors(x, y):
    for dx, dy in [(0,1), (1,0), (0,-1), (-1,0)]:
        nx, ny = x+dx, y+dy
        if 0 <= nx < width and 0 <= ny < height:
            if not cells[nx][ny].revealed and cells[nx][ny].value == 0:
                cells[nx][ny].revealed = True
                reveal_neighbors(nx, ny)  # 递归调用

但在大型网格上测试时,游戏经常崩溃,Python抛出"RecursionError: maximum recursion depth exceeded"错误。
解决方案:
使用队列替代递归,实现迭代式区域展开:

from collections import deque

def reveal_neighbors_iterative(start_x, start_y):
    queue = deque([(start_x, start_y)])
    
    while queue:
        x, y = queue.popleft()
        for dx, dy in [(0,1), (1,0), (0,-1), (-1,0)]:
            nx, ny = x+dx, y+dy
            if 0 <= nx < width and 0 <= ny < height:
                cell = cells[nx][ny]
                if not cell.revealed and not cell.flagged:
                    cell.revealed = True
                    if cell.value == 0:  # 空白格子
                        queue.append((nx, ny))  # 添加到队列

这种方法完全避免了递归深度限制,即使在最大尺寸的网格上也能稳定运行。

1.2游戏界面渲染性能问题

问题描述:
在游戏网格较大时(如24×24),每次刷新都重绘整个界面导致帧率明显下降,游戏体验卡顿。
借助AI工具后,发现pygame.draw.rect()和pygame.draw.circle()是主要性能瓶颈。
解决方案:
实现脏矩形渲染技术,只重绘发生变化的区域:

class RenderOptimizer:
    def __init__(self):
        self.dirty_cells = set()  # 记录需要重绘的格子
    
    def mark_dirty(self, x, y):
        """标记需要重绘的格子"""
        self.dirty_cells.add((x, y))
    
    def optimized_render(self):
        """只重绘标记过的格子"""
        for (x, y) in self.dirty_cells:
            # 计算格子位置
            rect_x = x * CELL_SIZE + MARGIN_X
            rect_y = y * CELL_SIZE + MARGIN_Y
            
            # 绘制单个格子
            self.draw_single_cell(x, y, rect_x, rect_y)
        
        self.dirty_cells.clear()  # 清空标记
    
    def draw_single_cell(self, x, y, rect_x, rect_y):
        """高效绘制单个格子"""
        cell = self.cells[x][y]

1.3首次点击踩雷问题

问题描述:
传统扫雷游戏中,第一次点击永远不会是地雷。但我的初始实现在某些情况下,首次点击仍可能触雷。
布雷算法在游戏初始化时就放置了所有地雷,而不是在第一次点击后。
解决方案:
重构布雷逻辑,延迟到第一次点击后执行:

def on_first_click(self, x, y):
    """处理第一次点击"""
    # 在点击位置周围创建安全区
    safe_zone = set()
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            safe_zone.add((x+dx, y+dy))
    
    # 放置地雷(避开安全区)
    mines_placed = 0
    while mines_placed < self.total_mines:
        mx = random.randint(0, self.width-1)
        my = random.randint(0, self.height-1)
        
        # 跳过安全区内的位置
        if (mx, my) in safe_zone:
            continue
        
        if not self.cells[mx][my].is_mine:
            self.cells[mx][my].is_mine = True
            mines_placed += 1
            self.update_neighbor_counts(mx, my)  # 更新周围格子数字
    
    # 揭示点击的格子
    self.reveal(x, y)

这种方法保证了玩家第一次点击绝对安全,符合扫雷游戏的传统规则。

2.实验总结

这次开发扫雷游戏的经历,像是一场充满惊喜的探险。最初搭建游戏框架时,我怎么也想不到自己会为一堆小小方块的展开效果如此费神。当递归算法在大地图上频繁崩溃时,望着满屏的错误提示,那种挫败感至今难忘。但正是这种困境教会了我重要一课:解决问题不仅需要技术,更需要灵活的思维。改用队列算法后,看着方块流畅展开的样子,比什么都更有成就感。
调试过程中的煎熬与突破同样记忆犹新。记得有个周末,我为了找出布雷位置随机的bug,在屏幕前坐了整整六个小时。反复追踪代码路径,添加日志,最终发现是个不起眼的循环边界错误。那一刻的欣喜若狂,恐怕只有经历过的人才会懂。
整个项目做下来,最深切的体会就是要给思考留够空间。开始时埋头就写的习惯让我吃了不少苦头,后期的设计先行策略反而事半功倍。当看到玩家们在各种设备上体验游戏时,那些绞尽脑汁的日夜都化作了满足的笑容。这段经历就像是编程世界给我的一封信:写代码不仅要用脑,更要用心;技术不仅是解决难题的工具,更是创造连接的艺术。

五、课程总结

《python程序设计》这门课对我来说真的很实在。最开始感觉就像学一门新语言的手册,明白了那些基础的词汇和句子怎么写——像怎么存个数字、名字,怎么决定电脑执行不同的动作。学完这些,心里就有点底了,起码能写点能跑起来的小玩意儿了。

最实用的就是学了那些“容器”——列表、字典啥的。以前要是整理一堆数据,我可能会很头大,现在知道怎么用它们来装不同类型的信息,找起来也方便多了。还有“函数”这个东西,简直是救星。学会了把常用的操作打包成一个命令,需要的时候直接调出来用就行,不用一遍遍重复写同样的代码,真的大大提高了效率,代码看着也清爽多了。

后面学着怎么让程序跟文件打交道,比如读取一个文本文件或者存点数据进去。虽然简单,但感觉一下子把程序变活了,能处理点实际东西了。学面向对象那块儿(就是类和对象),说实话开始有点抽象,但后来慢慢理解了,觉得这是一种更好的组织代码的方式,尤其是模仿现实世界的东西,让大点的程序写起来思路更清晰了。

总的来看,这门课最大的收获不是只记住了那些概念和规则(当然这些也很重要),而是感觉自己真的学到了一种新能力。从开始的磕磕绊绊,连个符号打错都找半天(调试真的很磨练耐心!),到现在能独立用Python解决一些小问题,尤其是处理数据和自动化一些简单任务。虽然跟复杂的项目还有距离,但它让我明白了编程没那么神秘,就是解决问题的另一种工具,Python在这方面特别友好也特别强大。

posted @ 2025-06-09 22:36  doronlee  阅读(37)  评论(0)    收藏  举报