写了个拼图游戏,探讨一下相关的AI算法。拼图游戏的复原问题也叫做N数码问题。
-
拼图游戏
-
N数码问题
-
广度优先搜索
-
双向广度优先搜索
-
A*搜索
实现一个拼图游戏,使它具备以下功能:
1、自由选取喜欢的图片来游戏
2、自由选定空格位置
3、空格邻近的方块可移动,其它方块不允许移动
4、能识别图片是否复原完成,游戏胜利时给出反馈
5、一键洗牌,打乱图片方块
6、支持重新开始游戏
7、难度分级:高、中、低
8、具备人工智能,自动完成拼图复原
9、实现几种人工智能算法:广度优先搜索、双向广度优先搜索、A*搜索
10、保存游戏进度
11、读取游戏进度
Puzzle Game.png
先看看完成后的效果。点自动按钮后,游戏将会把当前的拼图一步一步移动直到复原图片。
自动复原.gif
图片的选取可通过拍照、从相册选,或者使用内置默认图片。
由于游戏是在正方形区域内进行的,所以若想有最好的游戏效果,我们需要一张裁剪成正方形的图片。
截取正方形区域.png
选好图片后,需要把图片切割成n x n块。这里每一个方块PuzzlePiece都是一个UIButton。
由于图片是会被打散打乱的,所以每个方块应该记住它自己在原图上的初始位置,这里给方块添加一个属性ID,用于保存。
@interface PuzzlePiece : UIButton /// 本方块在原图上的位置,从0开始编号
@property (nonatomic, assign) NSInteger ID; /// 创建实例+ (instancetype)pieceWithID:(NSInteger)ID image:(UIImage *)image; @end
切割后的图片块组成了一个n x n矩阵,亦即n阶方阵。而想要改变游戏难度,我们只需要改变方阵的阶数即可。
设计三档难度,从低到高分别对应3 x 3、4 x 4、5 x 5的方阵。
难度选择.gif
假如我们把游戏中某个时刻的方块排列顺序称为一个状态,那么当阶数为n时,游戏的总状态数就是n²的阶乘。
在不同难度下进行游戏将会有非常大的差异,无论是手动游戏还是AI进行游戏。
在低难度下,拼图共有(3*3)! = 362880个状态,并不多,即便是最慢的广搜算法也可以在短时间内搜出复原路径。
3阶方阵的搜索空间.png
在中难度下,拼图变成了4阶方阵,拼图状态数飙升到(4*4)! = 20922789888000,二十万亿。广搜算法已基本不能搜出结果,直到爆内存。
广搜算法占用的巨量内存.gif
在高难度下,拼图变成了5阶方阵,状态数是个天文数字(5*5)! = 1.551121004333098e25,10的25次方。此时无论是广搜亦或是双向广搜都已无能为力,而A*尚可一战。
高难度下的5阶方阵.png
在选取完图片后,拼图是完整无缺的,此时让第一个被触击的方块成为空格。
从第二次触击开始,将会对所触击的方块进行移动,但只允许空格附近的方块发生移动。
每一次移动方块,实质上是让方块的位置与空格的位置进行交换。在这里思维需要转个小弯,空格并不空,它也是一个对象,只不过表示出来是一块空白而已。那么我们移动了方块,是否可以反过来想,其实是移动了空格?答案是肯定的,并且思维这样转过来后,更方便代码实现。
方块移动.gif
这里为了让打乱顺序后的拼图有解,采用随机移动一定步数的方法来实现洗牌。