本章的主题为介绍如何用Python实现一个拼图游戏,这里将会使用pygame库去做更多游戏相关的事情。通过本章的学习,我们将会了解到如何去实现一个交互的游戏,以及如何通过鼠标键盘进行互动。在此基础上我们可以去实现一些我们比较常见的游戏,比如五子棋,找茬之类的小游戏。
16.1 要解决什么问题我们要解决的问题就是如何实现一个非常常见的交互式游戏,拼图游戏。那么要实现该游戏,我们可能会遇到的难点在哪呢?下面是几个我们想到的问题。
- 游戏界面需要能够正确的响应鼠标;
- 分别按下键盘的上下左右按键时,能够移动空白块;
- 必须确保游戏界面被打乱的图片块是有解的,也就是一定可以被还原的;
- 游戏后台需要判断图片是否被还原了,一旦还原,程序需立即将缺失的图片块恢复;
- 怎么退出?比如我们设计为,如果按下了ESP键,则程序退出;
带着这些问题,我已经开始思考如何去实现它了。
16.2 实现思路这里我们实现的思路大致如下。
- 需要一个函数将原来的完成的图片进行打乱,且该过程必须可逆;
- 需要定义四个函数,分别负责将空白块进行上下左右的移动,或者说交互;
- 需要实现一个函数来判断是否拼图成功了;
- 需要初始化游戏界面,包括加载图片,设置标题和界面大小等等;
- 根据操作更新游戏界面;
本节将会涉及sys,random和pygame模块,这里将重点介绍pygame模块。很多用Python语言实现的游戏都是使用pygame做的界面设计,因此我们可以基于该游戏对其进行一定的学习了解。进而能够编写出我们自己的游戏,然后分享给我们的朋友亲人。
16.3.1 pygame模块pygame模块,是一个可以跨平台的模块,其设计目的就是为电子游戏而设计,能够支持图片和声音。它的实现使用我们开发程序时无需关注底层实现,不必被底层语言而束缚,同时也能够很快的开发出我们想要的游戏。既然是为游戏而设计的,那么用它来实现我们想要的游戏效果应该是绰绰有余。
pygame模块的安装命令如下:
PS F:\05_Github\> pip install pygame
Collecting pygame
Downloading https://files.pythonhosted.org/packages/11/4f/2bbfdb52c76d7d5b2a04c90e24bbb4b6970af66f0ebfe50bb32b67b51b39/pygame-1.9.6-cp36-cp36m-win32.whl (4.0MB)
|████████████████████████████████| 4.0MB 24kB/s
Installing collected packages: pygame
Successfully installed pygame-1.9.6
WARNING: You are using pip version 19.2.2, however version 19.2.3 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
PS F:\05_Github\ >
pygame模块有很多子模块,这里只对几个比较常用的模块做个简单的介绍如表16-1所示,具体需要使用某个模块的时我们可以进行深入的学习。
16.3.2 sys模块sys模块是一个内建模块,不需要单独安装。
sys模块提供了对Python解释器使用的一些变量的访问,并可以进行一些修改,例如对环境变量PATH的读取和修改,并提供了某些和解释器进行交互的函数以使我们的程序能够和解释器进行交互。
例如,sys.argv会将命令行参数以list的形式传递给Python脚本,sys.argv[0]是脚本的名字,sys.argv[1]是第一个参数,以此类推。
sys.exit()表示退出程序,也可以带参数表示退出码,如果有其他程序调用该程序,即可以通过返回的数字来确定被调用程序的退出原因。
sys.implementation查看当前正在运行的Python解释器的版本信息。
>>> sys.implementation
Namespace(cache_tag='cpython-36', hexversion=50726384, name='cpython', version=sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0))
>>>
sys.stdin,sys.stddout,sys.stderr解释器用于标准输入,标准输出和错误。
16.3.3 random模块random模块实现了一个伪随机数生成器。也可以用于随机选取给定范围内的数值。
random.randint(a, b) 返回一个a和b之间的随机数。
random.choice(seq) 从一个非空的seq中随机的返回一个元素。
>>> random.random()
0.30662085583434484
>>> random.uniform(2.5, 10.0)
9.727588546182595
>>> random.randrange(10)
4
>>> random.choice(['win', 'lose', 'draw'])
'win'
>>>
更多关于random模块的详细信息可以参考下面的链接。
16.4 代码实现到现在我们已经设计到的模块进行了简单介绍,下面我们将编写伪码及Python代码实现我们的拼图游戏。
16.4.1 编写伪码我们利用伪码来简单表述我们的实现思路,及具体代码的大概流程。
#定义空白块移动函数,分别支持向上向下向左向右
#定义判断拼图是否完成函数
#定义游戏初始化函数
#游戏主循环
获取键盘事件,然后判断是否为上下左右键,或者字母'w','a','d','s'分别表示上下左右
获取鼠标按下事件,如果按下鼠标的坐标在空白块的范围内
根据上面的事件类型执行相应的操作
更新游戏界面
先看看程序运行的效果,如图16-1所示。用鼠标点击图中空白块相邻的块即可移动。
图16-1 运行效果图
Python实现代码如下。
#encoding=utf-16
import pygame, sys, random
from pygame.locals import *
# 一些常量
WINDOWWIDTH = 500
WINDOWHEIGHT = 500
BACKGROUNDCOLOR = (255, 255, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
FPS = 40
VHNUMS = 3
CELLNUMS = VHNUMS * VHNUMS
MAXRANDTIME = 100
# 退出
def terminate():
pygame.quit()
sys.exit()
# 随机生成游戏盘面
def newGameBoard():
board = []
for i in range(CELLNUMS):
board.append(i)
blackCell = CELLNUMS - 1
board[blackCell] = -1
for i in range(MAXRANDTIME):
direction = random.randint(0, 3)
if (direction == 0):
blackCell = moveLeft(board, blackCell)
elif (direction == 1):
blackCell = moveRight(board, blackCell)
elif (direction == 2):
blackCell = moveUp(board, blackCell)
elif (direction == 3):
blackCell = moveDown(board, blackCell)
return board, blackCell
# 若空白图像块不在最左边,则将空白块左边的块移动到空白块位置
def moveRight(board, blackCell):
if blackCell % VHNUMS == 0:
return blackCell
board[blackCell - 1], board[blackCell] = board[blackCell], board[blackCell - 1]
return blackCell - 1
# 若空白图像块不在最右边,则将空白块右边的块移动到空白块位置
def moveLeft(board, blackCell):
if blackCell % VHNUMS == VHNUMS - 1:
return blackCell
board[blackCell 1], board[blackCell] = board[blackCell], board[blackCell 1]
return blackCell 1
# 若空白图像块不在最上边,则将空白块上边的块移动到空白块位置
def moveDown(board, blackCell):
if blackCell < VHNUMS:
return blackCell
board[blackCell - VHNUMS], board[blackCell] = board[blackCell], board[blackCell - VHNUMS]
return blackCell - VHNUMS
# 若空白图像块不在最下边,则将空白块下边的块移动到空白块位置
def moveUp(board, blackCell):
if blackCell >= CELLNUMS - VHNUMS:
return blackCell
board[blackCell VHNUMS], board[blackCell] = board[blackCell], board[blackCell VHNUMS]
return blackCell VHNUMS
# 是否完成
def isFinished(board, blackCell):
for i in range(CELLNUMS - 1):
if board[i] != i:
return False
return True
# 初始化
pygame.init()
mainClock = pygame.time.Clock()
# 加载图片
gameImage = pygame.image.load('pic.jpg')
gameRect = gameImage.get_rect()
# 设置窗口
windowSurface = pygame.display.set_mode((gameRect.width, gameRect.height))
pygame.display.set_caption('拼图')
cellWidth = int(gameRect.width / VHNUMS)
cellHeight = int(gameRect.height / VHNUMS)
finish = False
gameBoard, blackCell = newGameBoard()
# 游戏主循环
while True:
for event in pygame.event.get():
if event.type == QUIT:
terminate()
if finish:
continue
if event.type == KEYDOWN:
if event.key == K_LEFT or event.key == ord('a'):
blackCell = moveLeft(gameBoard, blackCell)
if event.key == K_RIGHT or event.key == ord('d'):
blackCell = moveRight(gameBoard, blackCell)
if event.key == K_UP or event.key == ord('w'):
blackCell = moveUp(gameBoard, blackCell)
if event.key == K_DOWN or event.key == ord('s'):
blackCell = moveDown(gameBoard, blackCell)
if event.type == MOUSEBUTTONDOWN and event.button == 1:
x, y = pygame.mouse.get_pos()
col = int(x / cellWidth)
row = int(y / cellHeight)
index = col row * VHNUMS
if (index == blackCell - 1 or index == blackCell 1 or index == blackCell - VHNUMS or index == blackCell VHNUMS):
gameBoard[blackCell], gameBoard[index] = gameBoard[index], gameBoard[blackCell]
blackCell = index
if (isFinished(gameBoard, blackCell)):
gameBoard[blackCell] = CELLNUMS - 1
finish = True
windowSurface.fill(BACKGROUNDCOLOR)
for i in range(CELLNUMS):
rowDst = int(i / VHNUMS)
colDst = int(i % VHNUMS)
rectDst = pygame.Rect(colDst * cellWidth, rowDst * cellHeight, cellWidth, cellHeight)
if gameBoard[i] == -1:
continue
rowArea = int(gameBoard[i] / VHNUMS)
colArea = int(gameBoard[i] % VHNUMS)
rectArea = pygame.Rect(colArea * cellWidth, rowArea * cellHeight, cellWidth, cellHeight)
windowSurface.blit(gameImage, rectDst, rectArea)
for i in range(VHNUMS 1):
pygame.draw.line(windowSurface, BLACK, (i * cellWidth, 0), (i * cellWidth, gameRect.height))
for i in range(VHNUMS 1):
pygame.draw.line(windowSurface, BLACK, (0, i * cellHeight), (gameRect.width, i * cellHeight))
pygame.display.update()
mainClock.tick(FPS)
我们的程序编写好了,也运行良好,能解决我们的问题。现在如果想把它分享给我们亲爱的其他同事,我们该怎么做呢?最好是将它打包为exe文件,否则使用的人也需要安装Python,且必须安装所有的依赖库,似乎太麻烦了,没等听您解释完就不想用了。因此,我们需要将Python代码进行打包,生成exe进行方便使用。
这里我们同时也提供了打包为EXE的代码。由于我们使用pyinstaller进行打包,所以需要提前安装。当然安装也很简单,使用pip命令就可以安装。
pip install pyinstaller
安装成功后我们就可以通过下面的代码进行打包了。
接下来,将Python代码转换为exe程序,我们使用的是pyinstaller,具体的转换代码我实现为python代码。只需要执行下面的Python代码,就会调用pyinstaller生成一个单独的exe文件。这种形式的优点是只有一个exe文件,其他所有的依赖的Python环境文件都被打包在该exe文件中。而缺点就是由此导致该文件比较大,而且每次执行都相当于有一个解压到临时目录的过程,所以执行比较慢。
import os, shutil
from subprocess import Popen
if os.path.exists('dist'):
shutil.rmtree("dist")
if os.path.exists('build'):
shutil.rmtree("build")
if os.path.exists('__pycache__'):
shutil.rmtree("__pycache__")
handle = Popen("pyinstaller -F -w --add-data res;res -i res/bit.ico puzzle.py")
handle.wait()
shutil.copyfile("dist/puzzle.exe", "./puzzle.exe")
shutil.rmtree("dist")
shutil.rmtree("build")
shutil.rmtree("__pycache__")
个人推荐将其打包为目录,而不是单个exe文件。这样的好处就是不需要每次执行都进行解压到临时目录,而是直接在同目录下调用执行,因此比较快。下面就是将Python文件打包为目录的代码,跟上面有区别,其中一个打包参数为-D。下面的脚本生成的目录dist中,会有相应的exe文件,可以直接双击运行。
import os, shutil
from subprocess import Popen
if os.path.exists('dist'):
shutil.rmtree("dist")
if os.path.exists('build'):
shutil.rmtree("build")
if os.path.exists('__pycache__'):
shutil.rmtree("__pycache__")
handle = Popen("pyinstaller -D -w --add-data res;res -i res/bit.ico puzzle.py")
handle.wait()
而为了更方便,我们这里会介绍如何将该目录打包为安装文件,这样在给别人用的时候,只需要给一个安装文件。用户拿到该安装文件后,安装上之后就可以使用了,且使用感受更好。我们这里介绍一个将目录打包为安装文件的工具程序。该工具程序名为Inno Setup,是一个免费的工具软件,网站主页如下图16-2所示。可以点击Download Inno Setup链接进行下载,详细信息可以登陆网站()进行了解。
图16-2 Inno Setup主页
这里,我们可以按照Inno Setup的引导来创建普通的程序安装文件,创建完成后会生成内容如下所示的iss文件。当然,如果我们提前有该文件的话,可以直接用Inno Setup程序直接打开该文件,执行编译来生成安装文件。
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "puzzle"
#define MyAppVersion "2.2"
#define MyAppPublisher "ggang.liu, Inc."
#define MyAppExeName "puzzle.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{F3D8DE4F-EA0D-47E7-AFBC-7FE7A8EAD8D3}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\{#MyAppName}
DisableProgramGroupPage=yes
; The [Icons] "quicklaunchicon" entry uses {userappdata} but its [Tasks] entry has a proper IsAdminInstallMode Check.
UsedUserAreasWarning=no
InfoAfterFile=C:\puzzle\release\README.md
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=C:\puzzle\release
OutputBaseFilename=puzzle-setup
SetupIconFile=C:\puzzle\res\bt.ico
Password=dot
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
[Files]
Source: "C:\puzzle\dist\puzzle\puzzle.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\puzzle\dist\puzzle\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
这里会打包生成的安装文件是一个exe文件,我们双击执行安装时会出现类似图16-3所示的安装界面,可以看出是一个很常见的程序安装界面,跟其他程序安装没有什么区别,看起来更专业。
图16-3 服务程序安装界面
到这里,程序的打包安装就介绍完毕,具体选择哪种方式进行打包,可以根据具体情况而定。试着为自己的程序进行打包并安装,让我们的小工具的安装使用足够专业和易用。
16.5 本章小结本章我们学习了如何通过pygame模块实现拼图程序,由此也给我们打开了一扇利用Python编写游戏的窗户。可以利用这些知识去实现其他类似的小游戏,也可以通过查阅相关的资料学习更多这方面的知识。
欢迎关注,转发,收藏,感谢
Python实用案例编程入门:第一章 Python概述及为什么学Python
Python实用案例编程入门:第十五章 用Python实现Windows上的服务
Python实用案例编程入门:第二章 字符串
Python实用案例编程入门:第四章 字典和文件
Python实用案例编程入门:第五章 函数和类
Python实用案例编程入门:第三章 列表和元组
Python实用案例编程入门:第七章 调式手段
Python实用案例编程入门:第六章 控制流语句
Python实用案例编程入门:第十章 用Python处理音频文件
Python实用案例编程入门:第九章 爬虫下载VOA每日广播英语MP3
Python实用案例编程入门:第十二章 测试线预订程序
Python实用案例编程入门:第十一章 做一个年会抽奖程序
Python实用案例编程入门:第十三章 自动收发电子邮件,远程控制
Python实用案例编程入门:第八章 如何自动连接WIFI
Python实用案例编程入门:第十四章 通过声音控制您的计算机
,