iOS 滑塊拼圖游戲(Puzzle8)

效果圖&DEMO

效果圖

一舌涨、準備工作

先了解一個定義和定理

定義:在一個1,2,...,n的排列中计呈,如果一對數的前后位置與大小順序相反周霉,即前面的數大于后面的數姚炕,那么它們就稱為一個逆序族壳。一個排列中逆序的總數就稱為這個排列的逆序數这橙。逆序數為偶數的排列稱為偶排列陡厘;逆序數為奇數的排列稱為奇排列。如2431中铅协,21,43摊沉,41狐史,31是逆序,逆序數是4说墨,為偶排列骏全。——這是北大《高等代數》上的定義尼斧。

定理:交換一個排列中的兩個數姜贡,則排列的奇偶性發(fā)生改變。

二棺棵、實現過程

以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烛恤,是隨機打亂的拼圖通過移動(空白塊與相鄰數字塊位置交換)要還原到的拼圖狀態(tài)
圖2母怜,是隨機打亂的拼圖狀態(tài)
圖3,是將圖2中的空白塊通過若干次移動(空白塊與相鄰數字塊位置交換)后空白塊到拼圖右下角的拼圖狀態(tài)缚柏,用來計算判斷打亂的拼圖是否可以還原
④ 空白塊處相當于數字8
⑤ 我們的目的是把打亂拼圖如圖2通過移動(空白塊與相鄰數字塊位置交換)還原到圖1狀態(tài)
⑥ 不是每個隨機打亂的拼圖都能還原到圖1狀態(tài)(根據定義定理有50%概率隨機打亂的拼圖不能還原)
⑦ 根據定義定理 苹熏,圖1的逆序數為0,為偶排列币喧。所以只有圖3也為偶排列轨域,圖2才有可能還原到圖1狀態(tài)

圖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自点、滑塊移動邏輯

點擊空白塊周圍數字塊,數字塊移到空白塊區(qū)域(其實就是空白塊和數字塊交換)

圖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;
}

三塘淑、其他細節(jié)功能

1、難度選擇 3*3(低)蚂斤, 4*4(中)存捺, 5*5(高)
2、自定義圖片拼圖(相機和相冊)
3曙蒸、圖片拼圖提示
4捌治、步數統(tǒng)計
5、最佳記錄
6纽窟、移動提示音設置

具體請下載demo查看

四肖油、參考

1、不可還原拼圖
2师倔、回憶經典构韵,講述滑塊游戲背后的數學故事
3、吳昊品游戲核心算法 Round 17 —— 吳昊教你玩拼圖游戲(15 puzzle)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末趋艘,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子凶朗,更是在濱河造成了極大的恐慌瓷胧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棚愤,死亡現場離奇詭異搓萧,居然都是意外死亡,警方通過查閱死者的電腦和手機宛畦,發(fā)現死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門瘸洛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人次和,你說我怎么就攤上這事反肋。” “怎么了踏施?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵石蔗,是天一觀的道長罕邀。 經常有香客問我,道長养距,這世上最難降的妖魔是什么诉探? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮棍厌,結果婚禮上肾胯,老公的妹妹穿的比我還像新娘。我一直安慰自己耘纱,他們只是感情好阳液,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揣炕,像睡著了一般帘皿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上畸陡,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天鹰溜,我揣著相機與錄音,去河邊找鬼丁恭。 笑死曹动,一個胖子當著我的面吹牛,可吹牛的內容都是我干的牲览。 我是一名探鬼主播墓陈,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼第献!你這毒婦竟也來了贡必?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤庸毫,失蹤者是張志新(化名)和其女友劉穎仔拟,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體飒赃,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡利花,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了载佳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炒事。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蔫慧,靈堂內的尸體忽然破棺而出挠乳,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布欲侮,位于F島的核電站崭闲,受9級特大地震影響,放射性物質發(fā)生泄漏威蕉。R本人自食惡果不足惜刁俭,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望韧涨。 院中可真熱鬧牍戚,春花似錦、人聲如沸虑粥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娩贷。三九已至第晰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間彬祖,已是汗流浹背茁瘦。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留储笑,地道東北人甜熔。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像突倍,于是被迫代替她去往敵國和親腔稀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容