Young87

当前位置:首页 >个人收藏

python小项目之拼图游戏

前言

    pygame模块是跨平台 Python 模块,专为电子游戏设计。包含图像、声音。创建在 SDL 基础上,允许实时电子游戏研发而无需被低级语言,如 C 语言或是更低级的汇编语言束缚。基于这样一个设想,所有需要的游戏功能和理念都完全简化位游戏逻辑本身,所有的资源结构都可以由高级语言提供。使用pygame模块进行游戏的开发将大大减少开发者的工作量,提升开发效率。pygame模块中提供了许多的API接口供给开发者使用,pygame中包含的模块如表1所所示。本文将在pygame模块为基础下,开发一个拼图小游戏,将图片进行随机分割,并将分割后的图片绘制在pygame创建出来的窗口当中,游戏的难度设置为可选,根据选择的难度,程序将对图片进行不同的划分和显示。

表1 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.1 拼图实现流程图
2.2 使用到的pygame模块介绍
(1) pygame.font模块

功能:加载和显示字体
使用:pygame.font.Font() 创建 Font 对象,再调用Font类中方法生成某种字体的文字

表2.2-1 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所示

表2.2-2 Clock类内部方法
方法功能
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 对象,进而可以进行画线、设置像素、对特定的区域进行捕获。

表2.2-3 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方法,初始化一个准备显示的窗口,使用只需将将对应的文件数据填入其中便可以完成显示的工作

表2.2-5 display模块常用方法
方法功能
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为拼图的大小

在这里插入图片描述

图3.1-1 一维数组与二维数组之间的对应关系

    图片分割具体实现代码,先对存储的列表进行初始化,将右下角的分割块设置为空白块,再使用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)  
  1. 实验效果
    开始界面
    在这里插入图片描述
    选择3 * 3模式,按下Q键
    在这里插入图片描述
    移动拼图
    在这里插入图片描述
    选择4 * 4模式
    在这里插入图片描述
    选择5 * 5模式
    在这里插入图片描述
    随机图片更换
    在这里插入图片描述
    结束界面
    在这里插入图片描述
5. 总结和展望

    本次项目的设计比较简单,学习了pygame提供的开发接口,pygame提供的接口非常方便,缩短了一个游戏开发的周期,拼图游戏还能够进行优化和添加一些新的功能,后续可以对其进行改进。总的来讲,这是对python基本语法的一次总结和对新模块的学习,比较基础,没有特别困难的内容。自己对python这门语言中很多精髓的地方还没有去了解到,后面会继续学习,加强自身的实力。

除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog

上一篇: 如何在科研论文中画出漂亮的插图?

下一篇: 操作系统设备管理_操作系统中的设备和安全管理

精华推荐