iOS 滑块拼图游戏(Puzzle8)

效果图&DEMO

效果图

一、准备工作

先了解一个定义和定理

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

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

二、实现过程

以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
图2

如何计算图3的逆序数

① 先计算图2的逆序数
② 再计算图2图3变换步数
③ 将两者相加即得图3逆序数

图3

判断图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

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查看

四、参考

1、不可还原拼图
2、回忆经典,讲述滑块游戏背后的数学故事
3、吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏(15 puzzle)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容