拼图游戏1. 文档介绍本文档主要介绍了一个简单的拼图游戏的开发过程2. 系统概述就是一个简单的拼图游戏,将一幅图片随机打散,然后拼出原图,很简单,也没有加入时间限制,只是一个拼图游戏的核心。
3. 设计约束本软件是用Qt开发出来的,Qt一个c++的gui类库,可以跨多种操作系统平台。
除了Qt库,还用到的就是标准的c程序库,各个系统中的开发环境也都有。
基于这两点,所以我开发出来的拼图游戏可以跨平台移植而不用修改代码,直接在目标平台上再重新编译一下就行了。
另外,我开发的只是一个拼图游戏的核心。
有很强的可扩展性,可以根据不同的需要而加入代码进行定制。
拼图中用到的图像分割和随机打散算法都是比较高效的。
鼠标点击图像块进行图像的交换,非常易用,对用户来说,非常清晰。
4. 开发与运行环境●操作系统:Windows7 32位旗舰版Service Pack 1●处理器:Pentium(R)*************************.8GHz●安装内存(RAM): 2.00GB●编译器:minGW移植到windows下的gcc编译器●调试器:minGW移植到windows下的gdb调试器●Make:minGW中的mingw32-make●Qt C++ GUI库●软件开发成功后拿到windowx xp和其他windows 7下运行毫无问题。
5. 软件的总体结构图图1 程序总流程图5.1 随机打散图片图2 随机打散图片5.2 拼图流程图3 拼图流程6. 功能模块设计对于一个拼图游戏的核心来说,主要的模块就是图片的随机打散模块和拼图模块。
我将屏幕窗口分成9块,如下图所示:图4 屏幕窗口6.1 图像随机分割我们将图像也分成和屏幕窗口一样的9块,如下图所示:图5 图像分割在Qt中,视口是物理坐标系下指定的任意矩形,窗口指的是同一矩形,只不过是逻辑坐标系下的。
我们先将视口设置成屏幕上的一块,然后窗口设置成一块大小和一块图像的大小一样的矩形,我们将一块图像绘制到窗口中,经过窗口-视口变换后就绘制到了视口所对应的屏幕上。
为了实现图像的随机打散,使用一个全局的映射数组map[3][3],map数组中存放的数字只能是0~8之间的数(包括0和8)。
map[0][0]对应屏幕的0块,map[0][1]对应屏幕的1块,依次类推,map[3][3]对应屏幕的8块。
如果map[0][0]里面存放的数是8,那么我们就将图像的第8块绘制到屏幕的第0块。
如果map数组中的数字是随机打乱的,那么我们通过这个映射数组将图像绘制到屏幕上后,屏幕上将会是打乱的图像。
为了方便代码的重用,我们将map数组的随机打乱编写到一个函数random()中,这样游戏再玩一次的时候就可以直接再调用一次这个函数重新打散一次映射数组就可以了。
map数组在初始化的时候,我们给map[0][0]~map[3][3]依次放进去0~8,然后再调用一次random()函数后将图像绘制到屏幕上去生成最初的打乱的图像。
random()函数的算法是这样的,首先产生两个随机数,然后再将这两个随机数对9求余的到两个9以内(即0~8且包括0和8)的随机数randData1和randData2,然后对map[randData1/3][randData1%3]和map[randData2/3][randData2%3]进行交换。
通过循环将这样的交换多进行几轮,然后就得到了一个随机打算的映射数组map。
代码:6.2 拼图我这个拼图游戏主要是用鼠标单击横纵相邻的两个块实现交换最后使打散的图像还原来完成拼图。
首先获得鼠标点击的位置,判断鼠标点击的位置是否在拼图区,如果不在拼图区,将描述选中块的一个数据结构selected置成没有选中任何块,然后刷新就完了;如果在拼图区,那么接下来就进行下面的程序。
计算鼠标选中那一块,通过selected数据结构判断之前是否已经有选中块了,如果之前没有选中块,那么置selected数据结构中选中块为现在选中的块,然后刷新;如果之前有选中的块,那么就判断之前选中的块和现在选中的块是否是横纵相邻的,如果不是横纵相邻的,那么就重置selected数据结构中选中块为现在选中的块,然后刷新;如果之前选中的块和现在选中的块是横纵相邻的,那么就交换两个选中的块,然后刷新。
我将判断游戏是否成功也放在了这个函数中,但是在模块的划分中,这是单独的一块,只是实现时为了实现方便将两个模块结合到了单独的一个函数中。
6.3 模块命名规则整个游戏我用一个类PintuWindow进行描述。
这个类继承自QMainWindow,其头文件为pintuwindow.h,实现文件为pintuwindow.cpp。
在PintuWindow类中,自定义了一个信号void success(),然后两个私有槽void random()和void succeed()。
在Pintu类的构造函数中,我将success()信号连接到槽succeed()以便在发射success()信号的时候可以自动执行secceed()槽。
那么在每次两个图块交换后我就判断游戏是否成功,如果成功,就会发射success()信号。
random()实现图块的随机打算,secceed()实现游戏成功的处理。
重新实现了两个虚函数void paintEvent(QPaintEvent *event)和void mousePressEvent(QMouseEvent *event)以实现绘图和鼠标操作。
然后是一个描述映射的数组int map[3][3],一个描述选中的情况的结构体struct pair selected和一个指向成功时弹出的那个对话框的指针SuccessDialog *successDialog等私有成员。
SuccessDialog类是在游戏成功时显示的一个对话框,继承自QDialog,其头文件是sucessdialog.h,实现文件时successdialog.cpp。
在这个类中自定义了两个信号void replay()和void exit()。
重新实现了void paintEvent(QPaintEvent *event)用以在对话框中插入一张图片。
然后就是一个QLabel *succ指向显示祝贺语的QLable,一个指向QPushButton重玩按钮的QPushButton *buttonReplay和一个指向QPushButton推出按钮的QPushButton *buttonExit。
当用户单击重玩按钮时就会先发射replay()信号,然后执行关闭这个对话框的槽。
Replay 信号在void PintuWindow::succeed()函数中是连接到了类PintuWindow的random()槽。
所以当我们单击rePlay按钮的时候会关闭对话框,重新随机打散图像,重新进行拼图游戏。
当用户单击exit按钮时就是发射exit信号,然后执行关闭这个对话框的槽。
exit()信号在void PintuWindow::succeed()函数中是连接到了类PintuWindow的close()槽,类PintuWindow的close()槽会关闭整个拼图游戏程序的窗口,然后结束拼图游戏。
7. 用户界面设计概述界面非常简单,一个窗口分成了两个区域,一个拼图区,一个原图区。
原图区显示原图,用以作拼图时的参照,拼图区实现拼图。
界面快照如下:图6 界面快照拼图成功时,会弹出一个对话框提示,成功时界面快照如下:图7 成功界面快照8. 综合考虑8.1 稳定性现在做出来的只是一个拼图游戏的核心部分,这些代码经过许多测试和修改稳定性也比较强了。
开始时鼠标单击会选中拼图区之外的图块,经过修改后鼠标不会选中拼图区之外的任何部分,map映射数组和selected结构在使用前都需要经过测试保证其正确,然后才使用的。
保证map数组不会出现越界错误,map数组和selected结构不会存入不正确的数据。
这些都是经过测试通过了的。
鼠标单击图块边缘时选中的图块也是经过精确的计算的到,不会出现单击图块边缘时明明是单击的这个图块,却由于计算偏差选中了另一个图块。
这些精确的计算也都是通过了软件的精确性测试通过了的。
8.2 可扩展性这个软件相当于一个拼图游戏的内核,可以围绕着这个内核进行许多的扩展,做成符合不同要求的拼图游戏。
例如,在这个游戏中加进一个定时器,将拼图的时间设置为一个与重玩次数相关的算法生成的时间或者设置成有用户自定义的形式。
当定时器的时间用完的时候如果用户还没有完成拼图,那么就游戏失败。
这个扩展时很容易实现的,而且游戏的图片随机打散算法和拼图算法以及拼图区和原图区的绘图等核心部分也不会改变。
还例如,还可以扩展成让用户自己选择一副自己喜欢的图片进行拼图。
那么这个时候需要一个动态加载用户图片的模块。
另外,就是需要将QImage对象(QImage对象是表示拼图游戏使用的图片的一个类)从paintEvent(QPaintEvent *event)函数的局部数据区移动到PintuWindow的全局数据区成为类PintuWindow的一个私有成员。
这个动态加载图片的模块就用以更新QImage对象中使用的图片。
这种情况下,游戏的图片随机打散算法和拼图算法都不会改变。
由于用户选择的图片的像素不同,拼图区和原图区的绘图只需要稍稍修改一个计算就可以了。
8.3 复用和移植本游戏使用了Qt这个C++的gui类库,Qt这个类库是一个跨多种操作系统平台的类库,所以基于Qt的应用程序有很好的平台可移植性。
另外就是使用了标准C函数库,除此之外,再没有用到别的库了,所以我的这个拼图游戏可以完全在Qt所支持的操作系统平台之间进行移植而不用修改源代码。
只需要拿到Qt所支持的平台上重新编译一次就可以了。
我的这个拼图游戏的图片随机打散算法和拼图算法的代码都是可以在别的拼图游戏中重用的。
界面的设计完全和功能代码的设计分开,界面的设计完全放倒了paintEvent(QPaintEvent *evnet)函数中,这样整个程序就比较清晰明了。
9 其他此程序的源代码见此文档父目录下的pintu目录。
参考文献:Qt官方文档《C++ GUI Qt4编程》(第2版)[加拿大]Jasmin Blanchette [英]Mark Summerfield。