20244207 2024-2025-2 《Python程序设计》实验四报告
20244207 2024-2025-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2442
姓名: 赵文萱
学号:20244207
实验教师:王志强
实验日期:2025年5月26日
必修/选修: 公选课
一、实验内容
Python综合应用:爬虫、数据处理、可视化、机器学习、神经网络、游戏、网络安全等。
例如:编写从社交网络爬取数据,实现可视化舆情监控或者情感分析。
例如:利用公开数据集,开展图像分类、恶意软件检测等。
例如:利用Python库,基于OCR技术实现自动化提取图片中数据,并填入excel中。
例如:爬取天气数据,实现自动化微信提醒。
例如:利用爬虫,实现自动化下载网站视频、文件等。
例如:编写小游戏:坦克大战、贪吃蛇、扫雷等等。
二、实验分析
1.需求分析
①基础功能:实现不同形状方块的生成、移动、旋转与下落。
②碰撞检测:判断方块与边界、已堆积方块的碰撞,阻止错误移动。
③消除机制:当某一行被填满时,消除该行并使上方方块下落。
④游戏界面:绘制游戏区域、分数显示、方块预览等。
⑤用户交互:通过键盘控制方块移动、旋转和加速下落。
2.难点分析
①方块旋转:处理不同形状方块在旋转时的坐标变换,避免超出边界或与已有方块重叠。
②碰撞问题:检测方块与游戏边界、底部及其他方块的碰撞。
③消除与下落:消除已满的行并让剩余方块下落。
三、实验设计和过程
1.点击终端,输入“pip install pygame”,完成Pycharm环境搭建。

2.用import导入pygame库,用于游戏开发,导入random库用于随机生成方块。用pygame.init()初始化Pygame库,加载所有Pygame模块。定义窗口尺寸为宽300px,高600px,定义方块尺寸为30px,便于后续坐标计算。pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))表示创建一个指定尺寸的游戏窗口。用pygame.display.set_caption("俄罗斯方块")设置游戏窗口的标题。

3.我学习了RGB的工作原理,了解到每种颜色都有一个强度值,通常用0到255之间的数字表示。0表示该颜色完全关闭,255表示该颜色完全开启。例如,纯红色可以表示为RGB(255, 0, 0),纯绿色是RGB(0, 255, 0),纯蓝色是RGB(0, 0, 255)。当三种颜色强度都为0时,得到黑色RGB(0, 0, 0);都为255时,得到白色RGB(255, 255, 255)。通过改变红、绿、蓝三种颜色的强度组合,就可以创建出各种不同的颜色。
于是我用RGB值定义背景为黑色、网格为灰色,并定义了7种方块颜色,与后续SHAPES中的形状一一对应(比如COLORS[0]对应I型)。

4.我使用字典SHAPES定义不同类型方块的形状及其旋转形态。这里我学习了二维数组,以便描述俄罗斯方块的形状和旋转形态。1表示该位置有方块,0或省略表示空白。矩阵的行对应形状的垂直方向(y轴),列对应水平方向(x轴)。形状的初始位置根据矩阵的宽度(列数)计算,确保水平居中。旋转形态通过切换矩阵实现,每个形状包含1个或多个旋转形态矩阵。以图中的直线型“I”型为例,[1, 1, 1, 1]表示1行×4列,即水平直线。[1],[1],[1],[1]表示4行×1列,即垂直直线,以此类推设计7个方块形状。每个形状的旋转形态数量不同(如I型有2种,J型有4种),通过索引切换旋转状态。

5.创建一个二维列表grid,表示游戏区域的网格。grid[y][x]表示坐标(x,y)处的方块颜色,None表示空,非None为颜色值。网格为10列20行,每个单元格初始值为None。当方块落下并固定后,对应的单元格将存储方块对象。这个是用于记录已固定的方块,判断移动和旋转时是否与已有方块重叠并进行满行消除。

6.封装方块的形状、旋转状态、位置和颜色,使代码结构化,便于复用和维护。len(self.matrix[0])为当前形态的宽度,居中位置为10//2 - 4//2 = 5-2=3,即x=3,从第3列开始,y=0确保方块从顶部开始下落。我用到了rotate(),用 old_rotation 记录旋转前的索引,以便后续回退。用self.rotation_index + 1指向下一个旋转形态(如从0→1,1→2)len(self.rotations)表示取模运算实现循环切换(如形态数为4时,索引3→0)。根据新索引从 rotations 列表中获取对应的形态矩阵,更新 self.matrix。

7.将移动到底部的方块位置和颜色存入grid,标记为已固定,用于后续的碰撞检测和满行判断。clear_lines筛选未填满的行,使用列表推导式保留至少有一个空单元格的行。再计算消除行数,原网格有20行,剩余行数为len(new_grid),差值即为消除的满行数,比如消除2行,就是cleared_lines=2。

8.用draw_grid绘制网格的边框和已固定的方块。 j * BLOCK_SIZE和i * BLOCK_SIZE将网格坐标(j,i)转换为像素坐标,确保位置准确。这样可以实时绘制当前移动的方块,跟随键盘操作或自动下落更新位置。通过piece.x和piece.y获取方块的当前网格坐标,结合单元格偏移量(j,i)计算像素位置,确保方块在网格中正确显示。

9.输入clock用于控制游戏帧率(clock.tick(60)限制为60FPS)。我用fall_time和fall_speed实现方块自动下落的定时器逻辑。初始方块随机选择形状,确保每次游戏开局不同。

10.左右键:调用move(dx, 0)水平移动方块;下键:调用move(0, 1)加速下落;上键:调用rotate()旋转方块。通过valid_space()在move()和rotate()内部自动处理碰撞检测。

11.每fall_speed毫秒(500ms)触发一次自动下落(move(0, 1))。若下落失败(move()返回False),说明方块已无法移动,则将方块固定到grid(add_piece_to_grid),检查并消除满行(clear_lines)。创建随机形状的新方块,若新方块生成时与已有方块重叠(valid_space返回False),说明游戏区域已满,结束循环(running=False)。

12.最后设计程序入口,确保当脚本直接运行时,执行main()函数,启动游戏,避免模块导入时意外触发游戏运行。

13.展示结果:
点击查看代码
import pygame
import random
pygame.init()
SCREEN_WIDTH = 300
SCREEN_HEIGHT = 600
BLOCK_SIZE = 30
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("俄罗斯方块")
BLACK = (0, 0, 0)
GRAY = (128, 128, 128)
WHITE = (255, 255, 255)
COLORS = [
(0, 255, 255),
(0, 0, 255),
(255, 165, 0),
(255, 255, 0),
(0, 255, 0),
(128, 0, 128),
(255, 0, 0)
]
SHAPES = {
'I': [
[[1, 1, 1, 1]],
[[1],
[1],
[1],
[1]]
],
'J': [
[[1, 0, 0],
[1, 1, 1]],
[[1, 1],
[1, 0],
[1, 0]],
[[1, 1, 1],
[0, 0, 1]],
[[0, 1],
[0, 1],
[1, 1]]
],
'L': [
[[0, 0, 1],
[1, 1, 1]],
[[1, 0],
[1, 0],
[1, 1]],
[[1, 1, 1],
[1, 0, 0]],
[[1, 1],
[0, 1],
[0, 1]]
],
'O': [
[[1, 1],
[1, 1]]
],
'S': [
[[0, 1, 1],
[1, 1, 0]],
[[1, 0],
[1, 1],
[0, 1]]
],
'T': [
[[0, 1, 0],
[1, 1, 1]],
[[1, 0],
[1, 1],
[1, 0]],
[[1, 1, 1],
[0, 1, 0]],
[[0, 1],
[1, 1],
[0, 1]]
],
'Z': [
[[1, 1, 0],
[0, 1, 1]],
[[0, 1],
[1, 1],
[1, 0]]
]
}
grid = [[None for _ in range(10)] for _ in range(20)]
class Piece:
def __init__(self, shape):
self.shape = shape
self.rotations = SHAPES[shape]
self.rotation_index = 0
self.matrix = self.rotations[self.rotation_index]
self.x = 10 // 2 - len(self.matrix[0]) // 2
self.y = 0
self.color = COLORS[list(SHAPES.keys()).index(shape)]
def rotate(self):
# 尝试旋转
old_rotation = self.rotation_index
self.rotation_index = (self.rotation_index + 1) % len(self.rotations)
self.matrix = self.rotations[self.rotation_index]
if not valid_space(self):
self.rotation_index = old_rotation
self.matrix = self.rotations[self.rotation_index]
def move(self, dx, dy):
old_x = self.x
old_y = self.y
self.x += dx
self.y += dy
if not valid_space(self):
self.x = old_x
self.y = old_y
return False
return True
def valid_space(piece):
for i, row in enumerate(piece.matrix):
for j, cell in enumerate(row):
if cell:
x = piece.x + j
y = piece.y + i
if x < 0 or x >= 10 or y < 0 or y >= 20:
return False
if grid[y][x]:
return False
return True
def add_piece_to_grid(piece):
for i, row in enumerate(piece.matrix):
for j, cell in enumerate(row):
if cell:
grid[piece.y + i][piece.x + j] = piece.color
def clear_lines():
global grid
new_grid = [row for row in grid if any(cell is None for cell in row)]
cleared_lines = 20 - len(new_grid)
for _ in range(cleared_lines):
new_grid.insert(0, [None for _ in range(10)])
grid = new_grid
return cleared_lines
def draw_grid():
for i in range(20):
for j in range(10):
rect = pygame.Rect(j * BLOCK_SIZE, i * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, GRAY, rect, 1)
if grid[i][j]:
pygame.draw.rect(screen, grid[i][j], rect)
def draw_piece(piece):
for i, row in enumerate(piece.matrix):
for j, cell in enumerate(row):
if cell:
rect = pygame.Rect((piece.x + j) * BLOCK_SIZE, (piece.y + i) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
pygame.draw.rect(screen, piece.color, rect)
def main():
clock = pygame.time.Clock()
fall_time = 0
fall_speed = 500 # 毫秒
current_piece = Piece(random.choice(list(SHAPES.keys())))
running = True
while running:
dt = clock.tick(60)
fall_time += dt
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_piece.move(-1, 0)
elif event.key == pygame.K_RIGHT:
current_piece.move(1, 0)
elif event.key == pygame.K_DOWN:
if not current_piece.move(0, 1):
pass
elif event.key == pygame.K_UP:
current_piece.rotate()
if fall_time > fall_speed:
fall_time = 0
if not current_piece.move(0, 1):
add_piece_to_grid(current_piece)
clear_lines()
current_piece = Piece(random.choice(list(SHAPES.keys())))
if not valid_space(current_piece):
running = False
screen.fill(BLACK)
draw_grid()
draw_piece(current_piece)
pygame.display.update()
pygame.quit()
if __name__ == "__main__":
main()
四、实验中遇到的问题和解决过程
-问题1:出现了方块位置偏移、超出屏幕或与网格不对齐的问题。
-问题1解决方案:经过检查发现是由于未统一像素坐标与网格坐标,grid[i][j] 对应屏幕坐标为 (jBLOCK_SIZE, iBLOCK_SIZE),移动时直接操作像素坐标而非网格索引,self.x += 1 应为网格列数变化,而非像素值。
-问题2:出现了方块穿过了边界和已固定方块的问题。
-问题2解决方案:通过学习资料和网络咨询,发现是因为仅校验方块当前位置,没有涵盖所有单元格,I 型横向时漏检右侧单元格,旋转后未重新计算所有单元格坐标,导致重叠。
这是我的错误代码(仅检查了单元格碰撞):
点击查看代码
def valid_space(piece):
x, y = piece.x, piece.y
return 0 <= x < 10 and 0 <= y < 20 and not grid[y][x]
-问题3:消行后上方方块下落速度异常。
-问题3解决方案:经过反复检查,发现误将非满行剔除any(cell is None) 写成 all(cell is not None)。
五、感悟与思考
1.实验总结
我一直都很想自己设计游戏,在上了一学期的python课后,我尝试通过Python语言结合Pygame库,实现了经典的俄罗斯方块游戏。在完成这次实验的过程中,我遇到不少难题。像Pygame库的图形绘制、方块旋转的坐标变换,还有碰撞检测的算法,都是之前没接触过的内容。我通过查阅Python和Pygame的官方文档,参考GitHub上的开源代码案例,边学边做。遇到问题就去搜索解决方案,经过多次尝试和调整,终于把游戏的初始化、方块逻辑、碰撞检测这些核心模块搭建起来,完成了整个游戏的基本框架。
2.课程总结
经过这段时间的Python课程学习,我在知识储备和能力提升方面都有了很大的收获,对编程也有了更深刻的理解。我掌握了Python编程的基础内容与核心原理。现在,我能够通过编写代码实现许多有趣的小项目,像经典的石头剪刀布游戏,还有猜数字游戏等等,在实践中加深了对编程逻辑的认识。通过学习文件操作相关知识,我可以用Python编辑日记,将所学运用到实际文本处理中,真正体会到编程在生活中的实用性。
函数定义和循环语句的学习,让我有了编写简单应用程序的能力。我不仅学会了如何封装重复使用的代码块,还能通过循环高效处理重复性任务,大大提升了编程效率。在学习过程中,我积累了许多常用函数的使用方法,这些知识让我在编写代码时更加游刃有余。此外,服务端和客户端程序开发的学习虽然几经波折,但也让我对网络编程有了深入了解,收获满满。
逻辑思维能力的培养是这门课程带给我的重要收获。面对问题时,我学会了将实际需求转化为清晰的编程思路,再逐步编写代码实现功能,这个过程让我的思维更加严谨和有条理。在开发服务端和客户端程序时,频繁出现的报错曾让我一度陷入困境,但我没有退缩。通过主动向老师请教,我不仅成功解决了问题,还掌握了实用的调试技巧。这让我明白,在编程的道路上,遇到困难时坚持和积极寻求解决方案是攻克难题的关键。同时,双人合作也锻炼了我的沟通协作能力,与同学交流想法、共同完善代码,让我感受到团队的力量。
3.课程反馈与建议
王老师的教学风格就是我认为的最理想的风格,课上用简单易懂的例子来解释抽象的概念,让我们这些文科生也能轻松理解。课上编写的程序也非常有意思,比如剪刀石头布等等,让我在学习的同时也能收获快乐与成就感,这极大地激发了我对Python学习的热情。课前的抽问环节也有助于我复习上一周的知识,毕竟时隔一周很多细节都记不清了。希望老师能继续保持这样棒的教学方式,让课堂充满活力。如果非要提建议的话,为了让我们能更好地巩固知识,感觉可以在后续课程中增加更多互动环节,比如小组讨论、编程挑战等,让我们在实践中灵活运用所学内容,加深对知识的理解和掌握。
这次Python课程的学习经历让我受益匪浅,感谢王老师的悉心指导。未来,我会继续保持对编程的热爱,不断探索Python世界的奥秘!
浙公网安备 33010602011771号