20244117 实验四《Python程序设计》实验报告
20244117 2024-2025-2 《Python程序设计》实验x报告
课程:《Python程序设计》
班级: 2441
姓名: 魏凡翕
学号:20244117
实验教师:王志强
实验日期:2025年5月13日
必修/选修:专选课
一.实验内容及分析设计
(一)实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等。
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒。
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等。
本次实验我选择编写一个扫雷的游戏程序。
(二)实验分析
1.选题分析:本次实验的内容选择较为自由,考虑到个人能力水平,并且综合个人兴趣爱好,我选择了做一个扫雷的小游戏程序。
2.程序编写分析:扫雷游戏的核心机制包括:一个由格子组成的棋盘,部分格子包含地雷;玩家可以揭开格子或标记可能有地雷的格子;揭开地雷格子游戏结束;揭开所有非地雷格子游戏胜利;数字表示周围8个格子中的地雷数量。需要根据这些机制特色编写相应的程序以实现这几种功能。同时,为了提升游戏体验,编写程序使得玩家在第一次点击格子时不会点到地雷所在的格子。
(三)实验设计
整个扫雷游戏的程序编写我将其分为四个大的部分:首先是准备阶段,这个阶段需要下载pygame、定义游戏的核心参数、设置游戏程序的像素、设置字体等,为后续的程序编写打下基础;然后编写游戏运行的核心机制:比如游戏的初始化、格子的存储信息以及揭开格子的运行逻辑、定义地雷设置的算法,在这一部分我打算实现一些小功能以优化游戏体验,比如玩家第一次点击时不会点击到有地雷的格子,这就要求程序在玩家第一次点击之后再在随机格子中防止地雷;随后是图形绘制,需要选择游戏的背景填充颜色、绘制状态栏、网格、重置按钮等;最后要创建扫雷游戏的主循环,包括遍历当前帧、进行鼠标点击处理、重置按钮检测、网格交互处理、渲染当前帧和更新屏幕显示、编写游戏推出模块等部分。以上就是我设计的扫雷游戏程序编写的基本框架。
二. 实验过程及结果
(一)实验过程
1.在正式编写程序前,需要下载pygame。

然后,使用import模块导入必要的库并初始化Pygame引擎,其中“pygame”用于图形界面和用户输入处理,“random”用于随机放置地雷,“sys”可以提供退出程序功能。

之后,定义游戏的核心参数和视觉样式,比如窗口尺寸、网格行列数、单个格子的像素大小、地雷总数等,以及游戏界面需要用到的颜色。

之后,创建游戏窗口,并命名为“扫雷游戏”。主绘图表面是400*450像素,把顶部50像素预留为状态栏,剩余400x400像素为游戏网格。

然后,设置并初始化字体。这里考虑到字体兼容问题,使用try-exceot结构提供两种字体方案,优先使用微软雅黑字体,如果不兼容则使用黑体。

2.之后,就需要定义扫雷游戏的核心机制了。首先应当定义游戏的初始化方法,使得每次运行程序时自动初始化游戏状态。

随后,定义格子的状态和游戏重置的机制。利用数字存储格子状态,-1存储有地雷的信息,0表示周围没有地雷,1-8表示格子周围的地雷数量;利用“revealed”记录格子是否被翻开;利用“flagged”记录格子是否被标记为地雷。

然后需要定义地雷设置的算法。为了强化游戏体验,使用if结构使得在玩家第一次点击格子之后再放置地雷;使用while循环保证地雷是随机放置的并且不会重复放置;使用for循环使得每放置一颗地雷,周围8个格子存储的数字+1。

然后定义格子被点击时的运行逻辑,此处需要和上面的程序统一,对第一次点击的格子做特殊处理,使得首次点击的格子存储的值为零,即它本身和周围8格都没有地雷;使用if语句进行踩雷判定,在点击到有地雷的格子时重置游戏;使用while语句实现DPS算法,使得以点击的格子为中心向外拓展,遇到有数字的格子则停止拓展,值为零的格子则继续拓展。


最后定义胜利条件,即所有没有地雷的格子都被揭开时获得游戏胜利。

3.绘制部分。首先是游戏的填充背景,这里选择使用白色来作为背景色。

然后,进行状态栏的绘制。这里选用灰色作为状态栏的颜色,使得格子在被翻开之前呈现灰色,在被翻开之后呈现白色;并生成特定标识到固定位置用以提示游戏中地雷的数量;以及游戏结束时的显示图标绘制,这里使用红色的图标提示游戏的胜利或者失败,图标显示在游戏格子的正上方。

接下来需要绘制网格,在这里使用了双重循环(GRID_SIZE × GRID_SIZE)遍历整个网格,其中外层循环y控制行,内层循环x控制列。然后为每个单元格创建pygame.Rect对象,单元格的位置由x和y值来确定,x值刻画水平坐标,y值刻画垂直坐标,并且为了避开状态栏,单元格的y值需要+50像素偏移。随后要对不同类型的单元格作不同的处理,对于已经被点开的单元格,绘制成白底黑框的矩形,如果格子中有地雷还需要在格子中央绘制实心黑点代表地雷,如果格子中需要显示数字则用对应颜色渲染数字并居中表示;对于未点开的单元格,绘制成灰底黑框的矩形,如果被标记,绘制成红色旗帜,旗帜使用四个点坐标绘制成三角形形状。

然后就是绘制游戏重置按钮,使得玩家可在任意时刻重新开始新的游戏。按钮为矩形样式,位置设置在右上角,颜色设置为深灰色,按钮中央有重置二字,并且文字处于按钮的正中。

4.最后需要创建扫雷游戏的主循环。首先利用game=Minesweeper()创建游戏核心逻辑实例。随后定义running=True,利用while循环使游戏持续运行到running变为False,实现游戏的状态持续更新和渲染。然后是事件队列的获取,利用for event in pygame.event.get():遍历当前帧的所有待处理事件,包括鼠标点击、键盘输入、关闭游戏窗口等;以及使用if循环进行退出检测,实现点击退出按钮时中止主循环,关闭游戏。

还要进行鼠标点击处理,实现由像素到网格坐标的转换。

使用if循环进行重置按钮检测,检查是否点击了重置按钮。

然后进行网格交互处理,event.button的值为1时是鼠标左键点击了格子,这时需要把格子揭开;值为3是右键点击了格子,这时给对应的格子标记上旗帜标记或取消标记,标记可以用来标记玩家认为可能的地雷的格子。

然后绘制游戏。使用game.draw()渲染当前帧,包括清空画布、绘制状态栏、绘制所有网格格子和绘制重置按钮;使用pygame.display.flip()更新屏幕显示,将后台缓冲区的图形交换到前台显示,这样可以避免屏幕闪烁,确保画面流畅。

最终编写游戏退出模块。使用pygame.quit()释放pygame资源;用sys.exit()实现安全退出。

(二)实验结果
整个程序就此编写完成,完整的源代码如下,此处添加的是可折叠的代码块:
点击查看代码
import pygame
import random
import sys
# 初始化pygame
pygame.init()
# 游戏常量
WIDTH, HEIGHT = 400, 450
GRID_SIZE = 10
CELL_SIZE = 40
MINE_COUNT = 15
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
DARK_GRAY = (150, 150, 150)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
COLORS = [BLUE, (0, 128, 0), RED, (0, 0, 128), (128, 0, 0),
(0, 128, 128), BLACK, (128, 128, 128)]
# 创建游戏窗口
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("扫雷游戏")
# 字体
font = pygame.font.SysFont('Arial', 20)
large_font = pygame.font.SysFont('Arial', 30)
class Minesweeper:
def __init__(self):
self.reset_game()
def reset_game(self):
self.board = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
self.revealed = [[False for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
self.flagged = [[False for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
self.game_over = False
self.win = False
self.mines_left = MINE_COUNT
self.first_click = True
self.place_mines()
def place_mines(self):
# 第一次点击后再放置地雷,确保第一次点击不是地雷
if not self.first_click:
mines_placed = 0
while mines_placed < MINE_COUNT:
x, y = random.randint(0, GRID_SIZE-1), random.randint(0, GRID_SIZE-1)
if self.board[y][x] != -1: # -1表示地雷
self.board[y][x] = -1
mines_placed += 1
# 更新周围格子的数字
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
nx, ny = x + dx, y + dy
if 0 <= nx < GRID_SIZE and 0 <= ny < GRID_SIZE and self.board[ny][nx] != -1:
self.board[ny][nx] += 1
def reveal(self, x, y):
if self.game_over or self.flagged[y][x]:
return
if self.first_click:
self.first_click = False
# 确保第一次点击的位置是0
while True:
self.reset_game()
self.first_click = False
self.place_mines()
if self.board[y][x] == 0:
break
if self.board[y][x] == -1: # 点到地雷
self.game_over = True
self.revealed[y][x] = True
return
# 使用DFS揭示空白区域
stack = [(x, y)]
while stack:
cx, cy = stack.pop()
if not (0 <= cx < GRID_SIZE and 0 <= cy < GRID_SIZE) or self.revealed[cy][cx] or self.flagged[cy][cx]:
continue
self.revealed[cy][cx] = True
if self.board[cy][cx] == 0:
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dx != 0 or dy != 0:
stack.append((cx + dx, cy + dy))
# 检查是否胜利
self.check_win()
def toggle_flag(self, x, y):
if not self.game_over and not self.revealed[y][x]:
self.flagged[y][x] = not self.flagged[y][x]
if self.flagged[y][x]:
self.mines_left -= 1
else:
self.mines_left += 1
def check_win(self):
for y in range(GRID_SIZE):
for x in range(GRID_SIZE):
if self.board[y][x] != -1 and not self.revealed[y][x]:
return
self.game_over = True
self.win = True
def draw(self):
# 绘制背景
screen.fill(WHITE)
# 绘制状态栏
pygame.draw.rect(screen, GRAY, (0, 0, WIDTH, 50))
mines_text = font.render(f"剩余地雷: {self.mines_left}", True, BLACK)
screen.blit(mines_text, (10, 15))
if self.game_over:
status_text = "你赢了!" if self.win else "你输了!"
status_render = large_font.render(status_text, True, RED)
screen.blit(status_render, (WIDTH//2 - status_render.get_width()//2, 10))
# 绘制网格
for y in range(GRID_SIZE):
for x in range(GRID_SIZE):
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE + 50, CELL_SIZE, CELL_SIZE)
if self.revealed[y][x]:
pygame.draw.rect(screen, WHITE, rect)
pygame.draw.rect(screen, DARK_GRAY, rect, 1)
if self.board[y][x] == -1: # 地雷
pygame.draw.circle(screen, BLACK, rect.center, CELL_SIZE//3)
elif self.board[y][x] > 0: # 数字
text = font.render(str(self.board[y][x]), True, COLORS[self.board[y][x]-1])
screen.blit(text, (x * CELL_SIZE + CELL_SIZE//2 - text.get_width()//2,
y * CELL_SIZE + 50 + CELL_SIZE//2 - text.get_height()//2))
else:
pygame.draw.rect(screen, GRAY, rect)
pygame.draw.rect(screen, DARK_GRAY, rect, 1)
if self.flagged[y][x]: # 旗帜
pygame.draw.polygon(screen, RED, [
(x * CELL_SIZE + CELL_SIZE//2, y * CELL_SIZE + 50 + CELL_SIZE//4),
(x * CELL_SIZE + CELL_SIZE//4, y * CELL_SIZE + 50 + CELL_SIZE//2),
(x * CELL_SIZE + CELL_SIZE//2, y * CELL_SIZE + 50 + CELL_SIZE*3//4),
(x * CELL_SIZE + CELL_SIZE//2, y * CELL_SIZE + 50 + CELL_SIZE//4)
])
# 绘制重置按钮
reset_rect = pygame.Rect(WIDTH - 100, 10, 80, 30)
pygame.draw.rect(screen, DARK_GRAY, reset_rect)
reset_text = font.render("重置", True, BLACK)
screen.blit(reset_text, (WIDTH - 100 + 40 - reset_text.get_width()//2, 15))
# 创建游戏实例
game = Minesweeper()
# 游戏主循环
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = event.pos[0] // CELL_SIZE, (event.pos[1] - 50) // CELL_SIZE
# 检查是否点击了重置按钮
if 300 <= event.pos[0] <= 380 and 10 <= event.pos[1] <= 40:
game.reset_game()
elif 0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE:
if event.button == 1: # 左键点击
game.reveal(x, y)
elif event.button == 3: # 右键点击
game.toggle_flag(x, y)
# 绘制游戏
game.draw()
pygame.display.flip()
pygame.quit()
sys.exit()
这里是运行程序的视频:
https://www.bilibili.com/video/BV1Bzj9zVERJ/?spm_id_from=333.1387.list.card_archive.click&vd_source=d94fb707d5586654483cd488c609bce4
三. 实验过程中遇到的问题和解决过程
问题1:不知道扫雷游戏的程序编写思路。
问题1解决方案:上网查找资料,询问课程上老师指导下载的NexChatGPT,最终确定了一个扫雷游戏需要实现什么功能,并且如何实现。
问题2:原代码运行时无法显示出汉字,而显示矩形方框。
问题2解决方案:查阅资料并询问同学,得知问题的原因是字体不兼容。于是使用了try-except结构设置了备选字体,更换字体后显示正常。
其他(感悟、思考等)
(1)课程总结:在《Python程序设计》这门课程中,我对Python语言有了比较基本的认识。从最初介绍Python基本情况、标识符、保留字、类与对象等,到介绍序列、集合、元组、字典,再到最后介绍爬虫、类库、异常等;从最初使用print()输出”hello world“,到学习for循环、while循环,再到最后使用套接字创建服务器和客户端......我一路走来学习许多新的知识,见识到了python语言的强大之处,并越来越能够体会到它在日常生活中发挥的巨大作用。在这个过程中,我的编程能力也得到了锻炼,从以前对编程一无所知到现在可以使用python实现自己心中的某些构想。我能够看到自己的成长,因此我认为这门课程对于自己的帮助是很大的。我也非常感谢老师孜孜不倦的教导,我几次被点名都回答不上来问题但是老师也没有说什么,在提问后还会把相关的知识点讲得很清楚,对我实在是大有益处。
从心理情感上来说,选择这门课程还是一个比较艰难的决定,毕竟我是一名文科生,理科的思维有限,对编程更是完全不懂。在一开始,python的基础知识还算比较好理解,但是到了介绍序列之后,整体的难度感觉一下子就上来了,当时觉得python不像一开始自己认为的或者是老师说的这样简单,不过最后好在还是坚持下来了,感觉还是很有成就感,坚持去做了一件自己可能会抵触的事情,最后还受益匪浅。我觉得在这个过程中,不仅是收获了知识,更是培养了能力,去做一件自己可能不那么喜欢做的、但是却对自我发展有利的事情的能力。而且在编写程序的过程中,我还养成了认真仔细的习惯,因为程序的编写一定是要求严谨的,就倒逼自己尽量少出差错。
这门课程给了我思考的内容。在学习socket编程技术时,我了解到这种技术在现代社会中的广泛应用,但是在以前我对它一无所知,我觉得自己也应当去学习一些基本的有关计算机的知识,才能更好地适应现代社会。
(2)实验总结:本次实验的自由性很大,最开始比较茫然,不知道要做什么,最后根据兴趣做好了实验,觉得还是很开心。
当然,本次实验的难度是很大的,自己去做一个程序还是面临不小的挑战,要自己去学习很多知识,了解这个程序的基本运行逻辑是什么,还是知道需要用什么代码去达成自己想要的效果。最后做出来的成果还是比较令我满意。在这个过程中,我切实地感受到了自学对自己python能力的提高,最后理解了本次实验对自己python能力巨大的锻炼作用,更加深化了自己对python的认识。
(3)意见和建议:从序列那里整个课程的难度明显加大,但是整体的速度感觉比较快,感觉可以在介绍这一部分时设当放慢速度。
老师写代码的速度比较快,有些时候跟不上,可以适当放慢速度。
最后讲网络爬虫时可以不用做那么多次实际操作,可以多一点对程序编写的讲解。
浙公网安备 33010602011771号