iOS 滑块拼图游戏(Puzzle8)

代码地址如下:
http://www.demodashi.com/demo/11505.html

一、准备工作

先了解一个定义和定理

定义:在一个1,2,...,n的排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。——这是北大《高等代数》上的定义。

定理:交换一个排列中的两个数,则排列的奇偶性发生改变。

二、项目结构

iOS 滑块拼图游戏(Puzzle8)

三、实现过程

以3*3拼图为例进行分析

1、随机打乱拼图

1)初始化从0-8的数组initializeNums

NSMutableArray *initializeNums = [NSMutableArray array];//初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[initializeNums addObject:@(i)];
}

2)从initializeNums随机抽取数字add到数组randomNums,得到随机数组

NSMutableArray *randomNums = [NSMutableArray array];//随机数组
for (int i = 0; i < _puzzleCount; i++) {
int randomNum = arc4random() % initializeNums.count;
[randomNums addObject:initializeNums[randomNum]];
[initializeNums removeObjectAtIndex:randomNum];
}

3)判断拼图是否可还原

图1,通过移动要还原到的拼图状态

图2,随机打乱的拼图状态

图3,将图2中的空白块移动到拼图右下角的拼图状态,用来计算打乱的拼图是否可以还原

④ 空白块处相当于数字8

⑤ 我们的目的是把打乱拼图如图2通过移动(空白块与相邻数字块位置交换)还原到图1状态

⑥ 不是每个随机打乱的拼图都能还原到图1状态(根据定义定理有50%概率随机打乱的拼图不能还原)

⑦ 根据定义定理图1的逆序数为0,为偶排列。所以只有图3也为偶排列,图2才有可能还原到图1状态

图1

iOS 滑块拼图游戏(Puzzle8)

图2

iOS 滑块拼图游戏(Puzzle8)

如何计算图3的逆序数

① 先计算图2的逆序数

② 再计算图2图3变换步数

③ 将两者相加即得图3逆序数

图3

iOS 滑块拼图游戏(Puzzle8)

判断图2是否可还原代码:

//判断是否可还原拼图
inverCount = 0;
int curNum = 0;
int nextNum = 0;
for (int i = 0; i < _puzzleCount; i++) {
curNum = [randomNums[i] intValue];
if (curNum == _puzzleCount - 1) {
inverCount += _difficulty - 1 - (i / _difficulty);
inverCount += _difficulty - 1 - (i % _difficulty);
}
for (int j = i + 1; j < _puzzleCount; j++) {
nextNum = [randomNums[j] intValue];
if (curNum > nextNum) {
inverCount++;
}
} }
if (!(inverCount % 2)) {//对2求余,余0,逆序数为偶数,即偶排列;否则,为奇排列
return randomNums;
}

获得随机可还原的数组randomNums

- (NSMutableArray *)getNewAvailableRandomNums {

    //随机数字
int inverCount = 0;
while (1) {
NSMutableArray *initializeNums = [NSMutableArray array];//初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[initializeNums addObject:@(i)];
} NSMutableArray *randomNums = [NSMutableArray array];//随机数组
for (int i = 0; i < _puzzleCount; i++) { int randomNum = arc4random() % initializeNums.count; [randomNums addObject:initializeNums[randomNum]]; [initializeNums removeObjectAtIndex:randomNum]; }
//判断是否可还原拼图
inverCount = 0;
int curNum = 0;
int nextNum = 0;
for (int i = 0; i < _puzzleCount; i++) {
curNum = [randomNums[i] intValue];
if (curNum == _puzzleCount - 1) {
inverCount += _difficulty - 1 - (i / _difficulty);
inverCount += _difficulty - 1 - (i % _difficulty);
}
for (int j = i + 1; j < _puzzleCount; j++) {
nextNum = [randomNums[j] intValue];
if (curNum > nextNum) {
inverCount++;
}
}
}
if (!(inverCount % 2)) {//对2求余,余0,逆序数为偶数,即偶排列;否则,为奇排列
return randomNums;
}
}
}
2、初始化拼图UI (九宫格)

代码:

- (void)customUI {
CGFloat puzzleBgViewX = 0;
CGFloat puzzleBgViewY = 64 + 20;
CGFloat puzzleBgViewW = [UIScreen mainScreen].bounds.size.width;
CGFloat puzzleBgViewH = puzzleBgViewW; _puzzleBgView = [[UIView alloc] initWithFrame:CGRectMake(puzzleBgViewX, puzzleBgViewY, puzzleBgViewW, puzzleBgViewH)];
_puzzleBgView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:_puzzleBgView]; CGFloat puzzleBtnX = 0;
CGFloat puzzleBtnY = 0;
CGFloat puzzleBtnW = puzzleBgViewW / _difficulty - kPuzzleBtnGap * 2;
CGFloat puzzleBtnH = puzzleBtnW; for (int i = 0; i < _puzzleCount; i++) {
puzzleBtnX = i % _difficulty * (puzzleBtnW + kPuzzleBtnGap * 2) + kPuzzleBtnGap;
puzzleBtnY = i / _difficulty * (puzzleBtnH + kPuzzleBtnGap * 2) + kPuzzleBtnGap;
UIButton *puzzleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
puzzleBtn.frame = CGRectMake(puzzleBtnX, puzzleBtnY, puzzleBtnW, puzzleBtnH);
puzzleBtn.tag = i;
puzzleBtn.clipsToBounds = YES;
[_puzzleBgView addSubview:puzzleBtn]; int puzzleValue = [self.randomNums[i] intValue];
if (puzzleValue == _puzzleCount - 1) {
puzzleBtn.backgroundColor = [UIColor clearColor];
_maxPuzzleBtn = puzzleBtn;
} else {
[puzzleBtn setTitle:[NSString stringWithFormat:@"%d", puzzleValue + 1] forState:UIControlStateNormal];
puzzleBtn.backgroundColor = [UIColor colorWithRed:0x4A / 255.0 green:0xC2 / 255.0 blue:0xFB / 255.0 alpha:1];
[puzzleBtn addTarget:self action:@selector(puzzleBtnAction:) forControlEvents:UIControlEventTouchUpInside];
}
}
}
3、滑块移动逻辑

点击空白块周围数字块,数字块移到空白块区域(其实就是空白块和数字块交换)

图4

iOS 滑块拼图游戏(Puzzle8)

index:数字块对应位置如图4

_difficulty : 拼图列数或行数

③ 点击数字块依次判断其 是否有空白块

④ 找到空白块,将点击数字块与空白块位置交换,实现数字块移动效果

以数字块3(index = 4)为例分析

upIndex = index - _difficulty 判断是否在九宫格里&&其位置对应的值是否是8,即空白块。

upIndex >= 0 && [self.randomNums[upIndex] intValue] == _puzzleCount - 1

downIndex = index + _difficulty 判断是否在九宫格里&&其位置对应的值是否是8,即空白块。

if (downIndex <= _puzzleCount - 1 && [self.randomNums[downIndex] intValue] == _puzzleCount - 1

leftIndex = index - 1 判断是否在九宫格里&&其位置对应的值是否是8,即空白块

index % _difficulty > 0 && [self.randomNums[leftIndex] intValue] == _puzzleCount - 1

rightIndex = index + 1 判断是否在九宫格里&&其位置对应的值是否是8,即空白块

index % _difficulty < _difficulty - 1 && [self.randomNums[rightIndex] intValue] == _puzzleCount - 1

代码:

- (void)puzzleBtnAction:(UIButton *)puzzleBtn {
NSInteger index = puzzleBtn.tag; //上
NSInteger upIndex = index - _difficulty;
if (upIndex >= 0 && [self.randomNums[upIndex] intValue] == _puzzleCount - 1) { CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = upIndex;
self.randomNums[upIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}]; [self isWin]; return; }
//下
NSInteger downIndex = index + _difficulty;
if (downIndex <= _puzzleCount - 1 && [self.randomNums[downIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = downIndex;
self.randomNums[downIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}]; [self isWin];
return;
}
//左
NSInteger leftIndex = index - 1;
if (index % _difficulty > 0 && [self.randomNums[leftIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = leftIndex;
self.randomNums[leftIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}]; [self isWin];
return;
}
//右
NSInteger rightIndex = index + 1;
if (index % _difficulty < _difficulty - 1 && [self.randomNums[rightIndex] intValue] == _puzzleCount - 1) {
CGPoint maxPuzzleBtnCenter = _maxPuzzleBtn.center;
CGPoint puzzleBtnCenter = puzzleBtn.center;
_maxPuzzleBtn.tag = index;
puzzleBtn.tag = rightIndex;
self.randomNums[rightIndex] = @([self.randomNums[index] intValue]);
self.randomNums[index] = @(_puzzleCount - 1);
[UIView animateWithDuration:0.35 animations:^{
puzzleBtn.center = maxPuzzleBtnCenter;
_maxPuzzleBtn.center = puzzleBtnCenter;
}]; [self isWin];
return;
} }
*4、另一种打乱拼图的方法

思路:将图1经过有限次数随机移动达到打乱拼图的目的,这样打乱的拼图肯定是可还原的。

代码:

- (NSMutableArray *)getNewAvailableRandomNums2 {

   NSMutableArray *randomNums = [NSMutableArray array];//随机数组 - 初始化0-n数字
for (int i = 0; i < _puzzleCount; i++) {
[randomNums addObject:@(i)];
} int randCount = _puzzleCount * _puzzleCount;
int randDirection = 0; //0 上 1 下 2 左 3 右
BOOL aliableDirection = NO;
int blankIndex = 8;
int index = 0;
while (randCount--) { aliableDirection = NO;
randDirection = arc4random() % 4;
while (1) {
switch (randDirection) {
case 0: if (blankIndex / _difficulty > 0) {
index = blankIndex - _difficulty;
aliableDirection = YES;
}
break;
case 1: if (blankIndex / _difficulty < _difficulty - 1) {
index = blankIndex + _difficulty;
aliableDirection = YES;
}
break;
case 2: if (blankIndex % _difficulty > 0) {
index = blankIndex - 1;
aliableDirection = YES;
}
break;
case 3: if (blankIndex % _difficulty < _difficulty - 1) {
index = blankIndex + 1;
aliableDirection = YES;
}
break;
default:
break;
}
if (aliableDirection == YES) {
break;
}
randDirection = (randDirection + 1) % 4;
} randomNums[blankIndex] = @([randomNums[index] intValue]);
randomNums[index] = @(8);
blankIndex = index; }
return randomNums;
}

四、其他细节功能

1、难度选择 3*3(低), 4*4(中), 5*5(高)

2、自定义图片拼图(相机和相册)

3、图片拼图提示

4、步数统计

5、最佳记录

6、移动提示音设置

具体请下载demo查看

五、实现效果

效果图

iOS 滑块拼图游戏(Puzzle8)

六、参考

1、不可还原拼图

2、回忆经典,讲述滑块游戏背后的数学故事

3、吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏(15 puzzle)

iOS 滑块拼图游戏(Puzzle8)

代码地址如下:
http://www.demodashi.com/demo/11505.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

上一篇:Django popup示例


下一篇:HDU 1934 树状数组 也可以用线段树