python小项目之拼图游戏
日期: 2020-07-06 分类: 个人收藏 585次阅读
文章目录
前言
pygame模块是跨平台 Python 模块,专为电子游戏设计。包含图像、声音。创建在 SDL 基础上,允许实时电子游戏研发而无需被低级语言,如 C 语言或是更低级的汇编语言束缚。基于这样一个设想,所有需要的游戏功能和理念都完全简化位游戏逻辑本身,所有的资源结构都可以由高级语言提供。使用pygame模块进行游戏的开发将大大减少开发者的工作量,提升开发效率。pygame模块中提供了许多的API接口供给开发者使用,pygame中包含的模块如表1所所示。本文将在pygame模块为基础下,开发一个拼图小游戏,将图片进行随机分割,并将分割后的图片绘制在pygame创建出来的窗口当中,游戏的难度设置为可选,根据选择的难度,程序将对图片进行不同的划分和显示。
| 模块名 | 功能 | 
| pygame.cdrom | 访问光驱 | 
| pygame.cursors | 加载光标 | 
| pygame.display | 访问设备显示 | 
| pygame.draw | 绘制形状、线和点 | 
| pygame.event | 管理事件 | 
| pygame.font | 使用字体 | 
| pygame.image | 加载和存储图片 | 
| pygame.joystick | 使用手柄或类似的东西 | 
| pygame.key | 读取键盘按键 | 
| pygame.mixer | 声音 | 
| pygame.mouse | 鼠标 | 
| pygame.movie | 播放视频 | 
| pygame.music | 播放音频 | 
| pygame.overlay | 访问高级视频叠加 | 
| pygame.rect | 管理矩形区域 | 
| pygame.sndarray | 操作声音数据 | 
| pygame.sprite | 操作移动图像 | 
| pygame.surface | 管理图像和屏幕 | 
| pygame.surfarray | 管理点阵图像数据 | 
| pygame.time | 管理时间和帧信息 | 
| pygame.transform | 缩放和移动图像 | 
1. 系统结构
2.1 拼图的实现
    一个拼图游戏的实现,可以按照如下几个步骤进行,流程图如图2.1所示。
 (1)图片切割
 (2)切割后的图片进行打乱
 (3)控制图片块移动方式
 (4)控制图片块移动方向
 (5)判断拼图是否完成
 
2.2 使用到的pygame模块介绍
(1) pygame.font模块
功能:加载和显示字体
 使用:pygame.font.Font() 创建 Font 对象,再调用Font类中方法生成某种字体的文字
| 方法 | 功能 | 
| pygame.font.Font.render() | 在一个新 Surface 对象上绘制文本 | 
| pygame.font.Font.size() | 确定多大的空间用于表示文本 | 
| pygame.font.Font.set_underline() | 控制文本是否用下划线渲染 | 
| pygame.font.Font.get_underline() | 检查文本是否绘制下划线 | 
| pygame.font.Font.set_bold() | 启动粗体字渲染 | 
| pygame.font.Font.get_bold() | 检查文本是否使用粗体渲染 | 
| pygame.font.Font.set_italic() | 启动斜体字渲染 | 
| pygame.font.Font.metrics() | 获取字符串参数每个字符的参数 | 
| pygame.font.Font.get_italic() | 检查文本是否使用斜体渲染 | 
| pygame.font.Font.get_linesize() | 获取字体文本的行高 | 
| pygame.font.Font.get_height() | 获取字体的高度 | 
| pygame.font.Font.get_ascent() | 获取字体顶端到基准线的距离 | 
| pygame.font.Font.get_descent() | 获取字体底端到基准线的距离 | 
(2)pygame.time模块
功能:管理时间帧信息
 使用:使用pygame.time.Clock()方法创建一个Clock对象来跟踪时间,调用类内部的方法来达到游戏显示动画帧的效果
 Clock类内部包含的方法如表 2.2-2所示
| 方法 | 功能 | 
| pygame.time.Clock.tick() | 更新时钟 | 
| pygame.time.Clock.tick_busy_loop() | 更新时钟 | 
| pygame.time.Clock.get_time() | 在上一个tick中使用的时间 | 
| pygame.time.Clock.get_rawtime() | 在上一个tick中使用的实际时间 | 
| pygame.time.Clock.get_fps() | 计算时钟帧率 | 
(3) pygame.image模块和 pygame.Surface模块
功能:加载图片文件,设置图片文件中的内容
 使用:调用pygame.image.load()方法,加载一张图片,并返回一个Surface 对象,进而可以进行画线、设置像素、对特定的区域进行捕获。
| 方法 | 功能 | 
| pygame.Surface.blit() | 将一个图像(Surface 对象)绘制到另一个图像上方 | 
| pygame.Surface.convert() | 修改图像(Surface 对象)的像素格式 | 
| pygame.Surface.fill() | 使用纯色填充 Surface 对象 | 
| pygame.Surface.get_size() | 获取 Surface 对象的尺寸 | 
| pygame.Surface.get_width() | 获取 Surface 对象的宽度 | 
| pygame.Surface.get_height() | 获取 Surface 对象的高度 | 
| pygame.Surface.get_rect() | 获取 Surface 对象的矩形区域 | 
(4) pygame.transform模块
功能:缩放和移动图像
 使用:调用pygame.transform.scale()方法Surface对象带有图片数据进行缩放
(5)pygame.display模块
功能:控制窗口和屏幕显示
 使用:调用pygame.display.set_mode方法,初始化一个准备显示的窗口,使用只需将将对应的文件数据填入其中便可以完成显示的工作
| 方法 | 功能 | 
| pygame.display.set_mode() | 初始化一个准备显示的窗口或屏幕 | 
| pygame.display.get_surface() | 获取当前显示的 Surface 对象 | 
| pygame.display.update() | 更新部分软件界面显示 | 
| pygame.display.set_caption() | 设置显示窗体的标题 | 
(6)pygame.event模块
功能: 处理事件与事件队列
 使用:调用pygame.event.get()方法从消息队列中获取一个事件,再去判断该事件的type属性 进一步去处理
3. 实现代码
3.1 拼图代码实现
(1)图片的分割与存储
     对图片进行分割和打乱,采用列表存储图片分割后分割块的索引,用一维的列表表示二维拼图图块,根据列表中的索引值进行转换即可得到对应的图片分割块的位置,当整个列表有序时表示拼图完成,游戏结束。一维与二维转化如图3.1-1所示,对应关系为:
 行号=(数值索引)/size 列号=(数值索引)%size //size为拼图的大小

图片分割具体实现代码,先对存储的列表进行初始化,将右下角的分割块设置为空白块,再使用random模块对列表进行随机打乱。
1.	''''' 
2.	@函数名:CreateBoard 
3.	@函数作用:获得一个打乱的列表 并初始化空白图块的位置 
4.	@参数: num_rows:行数 num_cols:列数 num_cells:整张图的大小 
5.	@返回值:返回拼图列表和空白图块的位置 
6.	'''  
7.	def CreateBoard(num_rows, num_cols, num_cells):  
8.	    board = []  
9.	    for i in range(num_cells): board.append(i)  
10.	  
11.	    '''''去掉右下角的一块 作为拼图游戏的空白块'''  
12.	    blank_cell_idx = num_cells - 1  
13.	    board[blank_cell_idx] = -1  
14.	  
15.	    ''''' 
16.	        从配置文件cfg.py中获取打乱次数NUMRANDOM 
17.	        使用random模块 获取0-3之间的随机数 
18.	        根据随机数值的不同调整board列表中的位置 
19.	        0: left, 1: right, 2: up, 3: down 
20.	    '''  
21.	    for i in range(cfg.NUMRANDOM):  
22.	        direction = random.randint(0, 3)  
23.	        if direction == 0: blank_cell_idx = moveL(board, blank_cell_idx, num_cols)  
24.	        elif direction == 1: blank_cell_idx = moveR(board, blank_cell_idx, num_cols)  
25.	        elif direction == 2: blank_cell_idx = moveU(board, blank_cell_idx, num_rows, num_cols)  
26.	        elif direction == 3: blank_cell_idx = moveD(board, blank_cell_idx, num_cols)  
27.	    return board, blank_cell_idx  
判断整个游戏是否结束,遍历整个列表,判断该列表是否变得有序
1.	'''
2.	@函数名:isGameOver 
3.	@函数作用:判断游戏是否结束 
4.	@参数: board:拼图列表  size:大小(行数、列数) 
5.	@返回值:如果列表有序返回true 否则返回false 
6.	'''  
7.	def isGameOver(board, size):  
8.	    assert isinstance(size, int)  
9.	    num_cells = size * size  
10.	  
11.	    #判断列表是否变为一个有序的状态  
12.	    for i in range(num_cells-1):  
13.	        if board[i] != i: return False  
14.	    return True  
 
(2)拼图分割块的移动
     图片分割块的移动实质上是与空白图块进行交换位置,根据空白块的位置进行上下左右的移动,移动的时候要注意几个边界条件,当空白分割块在第一行、最后一行、第一列和最后一列的位置上时,分别对应无法向上、下、左和右移动。在进行位置交换时要先判断此次位置的移动是否合法。具体实现代码如下
1.	''''' 
2.	@函数名:moveR 
3.	@函数作用:将空白图块和左边的图块进行位置交换 
4.	@参数: board:拼图列表  blank_cell_idx:空白图标位置 num_cols:拼图列表行数 
5.	@返回值:返回空白图块当前的位置 
6.	'''  
7.	def moveR(board, blank_cell_idx, num_cols):  
8.	    #判断空白图块的位置是否在第一列上,如果在第一列上则不进行交换  
9.	    if blank_cell_idx % num_cols == 0: return blank_cell_idx  
10.	    #进行数值替换  
11.	    board[blank_cell_idx-1], board[blank_cell_idx] = board[blank_cell_idx], board[blank_cell_idx-1]  
12.	    return blank_cell_idx - 1  
13.	  
14.	''''' 
15.	@函数名:moveL 
16.	@函数作用:将空白图块和右边图块进行位置交换 
17.	@参数: board:拼图列表  blank_cell_idx:空白图标位置 num_cols:拼图列表行数 
18.	@返回值:返回空白图块当前的位置 
19.	'''  
20.	def moveL(board, blank_cell_idx, num_cols):  
21.	    #判断空白图块是否在最后一列上,如果在最后一列则不进行交换  
22.	    if (blank_cell_idx+1) % num_cols == 0: return blank_cell_idx  
23.	    #进行数值上的替换  
24.	    board[blank_cell_idx+1], board[blank_cell_idx] = board[blank_cell_idx], board[blank_cell_idx+1]  
25.	    return blank_cell_idx + 1  
26.	  
27.	''''' 
28.	@函数名:moveD 
29.	@函数作用:将空白图块和上边图块进行位置交换 
30.	@参数: board:拼图列表  blank_cell_idx:空白图标位置 num_cols:拼图列表列数 
31.	@返回值:返回空白图块当前的位置 
32.	'''  
33.	def moveD(board, blank_cell_idx, num_cols):  
34.	    #判断空白图块的位置是否在第一行上,如果在第一行上则不进行替换  
35.	    if blank_cell_idx < num_cols: return blank_cell_idx  
36.	    #进行数值上的替换  
37.	    board[blank_cell_idx-num_cols], board[blank_cell_idx] = board[blank_cell_idx], board[blank_cell_idx-num_cols]  
38.	    return blank_cell_idx - num_cols  
39.	  
40.	''''' 
41.	@函数名:moveU 
42.	@函数作用:将空白图块和下边图块进行位置交换 
43.	@参数: board:拼图列表  blank_cell_idx:空白图标位置 num_cols:拼图列表列数 
44.	@返回值:返回空白图块当前的位置 
45.	'''  
46.	def moveU(board, blank_cell_idx, num_rows, num_cols):  
47.	    #判断当前空白图块是否在最后一行上 如果在则不进行替换  
48.	    if blank_cell_idx >= (num_rows-1) * num_cols: return blank_cell_idx  
49.	    #进行数值替换  
50.	    board[blank_cell_idx+num_cols], board[blank_cell_idx] = board[blank_cell_idx], board[blank_cell_idx+num_cols]  
51.	    return blank_cell_idx + num_cols  
 
3.2 拼图交互和界面的实现
(1)游戏开始界面
     使用pygame的font模块引入对应的字体文件,再通过render方法绘制出提示语句,并调用get_rect方法获取文本对象的属性值,设置其属性,最后将其绘制在对应的窗口上,并调用pygame的event模块,等待用户输入 选择对应的难度。具体实现代码如下所示:
1.	''''' 
2.	@函数名:ShowStartInterface 
3.	@函数作用:显示游戏开始的界面 
4.	@参数: screen:pygame窗口对象 wight:宽度 height:高度 
5.	@返回值:返回游戏难度数值 3/4/5 
6.	'''  
7.	def ShowStartInterface(screen, width, height):  
8.	    # 从配置文件中获取背景颜色并将窗口填满  
9.	    screen.fill(cfg.BACKGROUNDCOLOR)  
10.	    # 从配置文件中获取字体的路径 并设置tfront和cfront字体的大小  
11.	    tfont = pygame.font.Font(cfg.FONTPATH, width//4)  
12.	    cfont = pygame.font.Font(cfg.FONTPATH, width//20)  
13.	  
14.	    # 配置提示语句,使用render方法绘制出一个文本  
15.	    title = tfont.render('PUZZLE', True, cfg.GRAY)  
16.	    content1 = cfont.render('按Q或W或E键开始游戏,退出游戏请按ESC', True, cfg.BLACK)  
17.	    content2 = cfont.render('Q为3*3模式, W为4*4模式, E为5*5模式', True, cfg.BLACK)  
18.	  
19.	    #返回对应文本对象的的属性对象 设置各个文本的位置  
20.	    trect = title.get_rect()  
21.	    trect.midtop = (width/2, height/10)  
22.	    crect1 = content1.get_rect()  
23.	    crect1.midtop = (width/2, height/2.2)  
24.	    crect2 = content2.get_rect()  
25.	    crect2.midtop = (width/2, height/1.8)  
26.	  
27.	    #重新绘制窗口中的内容  
28.	    screen.blit(title, trect)  
29.	    screen.blit(content1, crect1)  
30.	    screen.blit(content2, crect2)  
31.	  
32.	    while True:  
33.	        #从队列中获取事件  
34.	        for event in pygame.event.get():  
35.	            #退出/按下ESC 表示退出游戏  
36.	            if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):  
37.	                #卸载所有导入的 pygame 模块  
38.	                pygame.quit()  
39.	                sys.exit()  
40.	            elif event.type == pygame.KEYDOWN:  
41.	                #判断是否为q/w/e中的其中一个按键  
42.	                if event.key == ord('q'): return 3  
43.	                elif event.key == ord('w'): return 4  
44.	                elif event.key == ord('e'): return 5  
45.	        #更新窗口上的显示内容  
46.	        pygame.display.update()  
 
(2)游戏结束界面显示
     与开始界面的显示差不多,通过绘制文本对象,将其显示在游戏的窗口上,等待用户输入并结束程序,实现代码如下:
1.	''''' 
2.	@函数名:ShowEndInterface 
3.	@函数作用:显示游戏结束的界面 
4.	@参数: screen:pygame窗口对象 wight:宽度 height:高度 
5.	@返回值:none 
6.	'''  
7.	def ShowEndInterface(screen, width, height):  
8.	    #从配置文件中获取背景颜色并将窗口填满  
9.	    screen.fill(cfg.BACKGROUNDCOLOR)  
10.	    #从配置文件中获取字体的路径 并设置字体的大小为windth的十五分之一  
11.	    font = pygame.font.Font(cfg.FONTPATH, width//15)  
12.	    #配置提示语句,使用render方法绘制出一个文本 (233, 150, 122)表示文本的颜色  
13.	    title = font.render('恭喜! 你成功完成了拼图!', True, (233, 150, 122))  
14.	    #返回title的属性对象  
15.	    rect = title.get_rect()  
16.	    #设置midtop数值,即为文本的位置  
17.	    rect.midtop = (width/2, height/2.5)  
18.	    #重新绘制窗口内容  
19.	    screen.blit(title, rect)  
20.	    #更新显示窗口中的内容  
21.	    pygame.display.update()  
22.	    while True:  
23.	        #结束后,进行事件的轮询捕捉 等待用户操作 然后退出  
24.	        for event in pygame.event.get():  
25.	            #退出/空格键按下  
26.	            if (event.type == pygame.QUIT) or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):  
27.	                pygame.quit()  
28.	                sys.exit()  
29.	        pygame.display.update()  
 
(3)游戏运行界面
     调用pygame.image模块加载图片,并通过transfrom模块对其进行缩放。
1.	#根据配置文件cfg.py中的图片路径PICTURE_ROOT_DIR,对位图进行加载  
2.	game_img_used = pygame.image.load(GetImagePath(cfg.PICTURE_ROOT_DIR))  
3.	#根据cfg中设置屏幕大小 对加载位图进行缩放  
4.	game_img_used = pygame.transform.scale(game_img_used, cfg.SCREENSIZE)  
5.	#获取加载窗口的属性  
6.	game_img_used_rect = game_img_used.get_rect()  
调用pygame.display模块创建一个窗口,设置其标题
1.	# 根据cfg中的屏幕大小数据SCREENSIZE设置窗口的大小  
2.	screen = pygame.display.set_mode(cfg.SCREENSIZE)  
3.	#使用display模块中的set_caption方法设置窗口的标题  
4.	pygame.display.set_caption('PUZZLE_BY_SJ')  
调用开始界面显示方法,根据用户的输入,设置拼图的行列,根据窗口的大小,计算每一个图片分割块的长度和宽度(行列的值默认相等)
1.	#显示游戏开始界面,等待用户输入  
2.	    size = ShowStartInterface(screen, game_img_used_rect.width, game_img_used_rect.height)  
3.	    #判断获取到的返回值是否为int类型,否则退出程序  
4.	    assert isinstance(size, int)  
5.	    #设置游戏拼图的行列数值  
6.	    num_rows, num_cols = size, size  
7.	    #设置拼图块数目  
8.	    num_cells = size * size  
9.	    #根据窗口大小,计算每一个块的大小  
10.	    cell_width = game_img_used_rect.width // num_cols  
11.	    cell_height = game_img_used_rect.height // num_rows  
内容显示,根据拼图列表中的值计算出对应的行列再转化为窗口上具体的区域,调用pygame.Rect方法设置显示的区域,再重新对窗口进行绘制,游戏中有一个主循环,每次循环要对拼图列表中的每一个值进行判断,并重新显示,遍历完拼图列表之后再调用pygame.draw模块绘画直线,将图片分割出层次感,实现代码如下:
1.	screen.fill(cfg.WHITE)  
2.	#对窗口进行图片填充  
3.	for i in range(num_cells):  
4.	    #表示空白图块 不进行图片填充  
5.	    if game_board[i] == -1:  
6.	        continue  
7.	  
8.	    #计算当前图块在窗口上的位置 行号和列号,拼图列表是一个一维列表要对其转换为二维列表的表示方式  
9.	    x_pos = i // num_cols  
10.	    y_pos = i % num_cols  
11.	  
12.	    #使用pygame.Rect设置显示的区域  
13.	    rect = pygame.Rect(y_pos*cell_width, x_pos*cell_height, cell_width, cell_height)  
14.	    img_area = pygame.Rect((game_board[i]%num_cols)*cell_width, (game_board[i]//num_cols)*cell_height, cell_width, cell_height)  
15.	    #对窗口进行绘制  
16.	    screen.blit(game_img_used, rect, img_area)  
17.	   #在窗口中绘制直线 行上的直线 分割图片  
18.	for i in range(num_cols+1):  
19.	    pygame.draw.line(screen, cfg.BLACK, (i*cell_width, 0), (i*cell_width, game_img_used_rect.height))  
20.	# 在窗口中绘制直线 列上的直线 分割图片  
21.	for i in range(num_rows+1):  
22.	    pygame.draw.line(screen, cfg.BLACK, (0, i*cell_height), (game_img_used_rect.width, i*cell_height))  
23.	#更新窗口显示的内容  
24.	pygame.display.update()  
 
游戏动画的运行调用pygame的Clock模块,控制动画显示的帧率,类似于延时,使每次循环在相同的间隔内执行
1.	#创建时钟对象 (可以控制游戏循环频率)  
2.	clock = pygame.time.Clock()  
3.	#设置游戏动画的帧率 让程序知道多长时间绘制一次 以毫秒为单位  
4.	clock.tick(cfg.FPS)  
 
(2)游戏的交互
     交互的实现是调用pygame的event模块,从消息队列中取出一个事件,并对其进行判断,分为鼠标和键盘事件。键盘事件根据按键的输入分类为上下左右,根据不同的输入执行相对应移动函数,实现代码如下:
1.	#键盘操作  
2.	elif event.type == pygame.KEYDOWN:  
3.	    #左移  
4.	    if event.key == pygame.K_LEFT or event.key == ord('a'):  
5.	        blank_cell_idx = moveL(game_board, blank_cell_idx, num_cols)  
6.	    #右移  
7.	    elif event.key == pygame.K_RIGHT or event.key == ord('d'):  
8.	        blank_cell_idx = moveR(game_board, blank_cell_idx, num_cols)  
9.	    #上移  
10.	    elif event.key == pygame.K_UP or event.key == ord('w'):  
11.	        blank_cell_idx = moveU(game_board, blank_cell_idx, num_rows, num_cols)  
12.	    #下移  
13.	    elif event.key == pygame.K_DOWN or event.key == ord('s'):  
14.	        blank_cell_idx = moveD(game_board, blank_cell_idx, num_cols)  
 
鼠标的操作,先获取鼠标的焦点位置,计算该位置在整个窗口的哪一块位置,如果其上下左右存在空白图块,则进行移动,实现代码如下:
1.	#鼠标操作  
2.	            elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:  
3.	                #获取鼠标的焦点位置  
4.	                x, y = pygame.mouse.get_pos()  
5.	                #计算当前处在哪一个拼图块的位置  
6.	                x_pos = x // cell_width  
7.	                y_pos = y // cell_height  
8.	  
9.	                #计算该块再拼图列表中的index  
10.	                idx = x_pos + y_pos * num_cols  
11.	                #在空白图块右边  
12.	                if idx == blank_cell_idx-1:  
13.	                    blank_cell_idx = moveR(game_board, blank_cell_idx, num_cols)  
14.	                #在空白图块的左边  
15.	                elif idx == blank_cell_idx+1:  
16.	                    blank_cell_idx = moveL(game_board, blank_cell_idx, num_cols)  
17.	                #在空白图块的上方  
18.	                elif idx == blank_cell_idx+num_cols:  
19.	                    blank_cell_idx = moveU(game_board, blank_cell_idx, num_rows, num_cols)  
20.	                #在空白图块的下方  
21.	                elif idx == blank_cell_idx-num_cols:  
22.	                    blank_cell_idx = moveD(game_board, blank_cell_idx, num_cols)  
 
- 实验效果
开始界面

选择3 * 3模式,按下Q键

移动拼图

选择4 * 4模式

选择5 * 5模式

随机图片更换

结束界面

 
5. 总结和展望
本次项目的设计比较简单,学习了pygame提供的开发接口,pygame提供的接口非常方便,缩短了一个游戏开发的周期,拼图游戏还能够进行优化和添加一些新的功能,后续可以对其进行改进。总的来讲,这是对python基本语法的一次总结和对新模块的学习,比较基础,没有特别困难的内容。自己对python这门语言中很多精髓的地方还没有去了解到,后面会继续学习,加强自身的实力。
 除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
上一篇: 如何在科研论文中画出漂亮的插图?
精华推荐
