童年的记忆——iOS 拼图游戏

前言

最近写了一个 iOS小游戏,纯属一时兴起。动机:那天看到妹妹在朋友圈发了一组图片,正好是九宫格的形状,突然间就觉得这些图片不就像是一个拼图游戏吗?如果可以直接移动玩拼图,那也挺酷哇。撸起袖子就是干!做出来的效果就是这样的:

demo.gif

基本思路

首先我选取了一张大的原始图片,这张图片用来裁成一定数量的小方块(不用数学语言严谨描述了,影响阅读性),最好是选取的图片可以让每个小方块图片都有一定的辨识度。原图片右下角的一个小方块丢弃作为可移动的空白空间。每一个小方块都给她编上一个独一无二的号码。这个编号可以用来校验拼图是否完成。

方块布局是使用UICollectionView来搭建的,难点在于拼图的移动,实际上我是把图块的移动处理成了图块位置的交换,只要点击你想要移动的图块,这个图块就会瞬移到空白位置,这样来说在游戏体验上移动更灵敏,效率更高!

开玩时,将图块顺序打乱。

核心算法

判断当前点击的图块是否可移动

-(void)calculateIndexOfMoveable {

    //记录空白块的索引,紧靠空白块的方块才可以移动,实际上就是与空白块交换位置。初始化时的空白块统一在右下角。
    //计算当前可移动的方块
    // 白色块所在行row = indexOfWhite / totalCols
    // 白色块所在列col = indexOfWhite % totalCols
    left = indexOfWhite - 1
    right = indexOfWhite + 1;
    up = indexOfWhite - totalCols;
    down = indexOfWhite + totalCols;

    //    但是要排除一些四周情况下的索引
    if ([self indexOfCol: left] > [self indexOfCol: indexOfWhite]) {
        //left 排除
        left = -1;
    }
    if ([self indexOfCol: right] < [self indexOfCol: indexOfWhite]) {
        //right 排除
        right = -1;
    }
    if (up < 0) {
        //up 排除
        up = -1;
    }
    if (down > totalCols*totalRows-1) {
        //down 排除
        down = -1;
    }
}
-(NSInteger)indexOfRow:(NSInteger)index {
    return index / totalCols;
}

-(NSInteger)indexOfCol:(NSInteger)index {
     return index % totalCols;
}

上面的 calculateIndexOfMoveable方法可以优化成如下四个方法:

-(NSInteger)calculateIndexOfMoveable_left {
    left = indexOfWhite - 1;
    return [self indexOfCol: left] > [self indexOfCol: indexOfWhite] ? -1 : left;
}

-(NSInteger)calculateIndexOfMoveable_right {
    right = indexOfWhite + 1;
    return [self indexOfCol: right] < [self indexOfCol: indexOfWhite] ? -1 : right;
}

-(NSInteger)calculateIndexOfMoveable_up {
    
    return (indexOfWhite - totalCols) < 0 ? -1 : indexOfWhite - totalCols;
}

-(NSInteger)calculateIndexOfMoveable_down {
    
    return (indexOfWhite + totalCols) > (totalCols*totalRows-1) ? -1 : indexOfWhite + totalCols;
}

我这里定义了两个数组,一个是图片小方块的数组,一个是图片块对应的编号数组。这两个数组必须保持同步更新。也可以把图片小方块与其对应的编号作为一个模型类的属性。也可以建立一个字典,将编号与图片映射。
初始化图片块数组:

-(NSMutableArray *)dataSource {
    if (!_dataSource) {
        _dataSource = [NSMutableArray array];
        
        CGFloat x,y,w,h;
         w = (self.oringinalImg.image.size.width/totalCols)/[UIScreen mainScreen].scale;
         h = (self.oringinalImg.image.size.height/totalRows)/[UIScreen mainScreen].scale;
        
        for (int i=0; i<totalRows; i++) {
            for (int j=0; j<totalCols; j++) {
                x = j*w;
                y = i*h;
               
                CGRect rect = CGRectMake(x,y,w,h);
                if ((i==totalRows-1) && (j== totalCols-1)) {
                    [_dataSource addObject: [[UIImage alloc] init] ];
                } else {
                    
                    [_dataSource addObject: [self ct_imageFromImage:self.oringinalImg.image inRect: rect]];
                }
            }
       }
     }
     return _dataSource;
}

初始化图片块对应的编号数组:

-(NSMutableArray *)startIndexs {
    if (!_startIndexs) {
        _startIndexs = [NSMutableArray array];
        for (int i = 0; i < totalCols*totalRows; i++) {
            _startIndexs[i] = @(i);
        };
    }
    return _startIndexs;
}

裁剪图片的具体方法:

/**
 *  从图片中按指定的位置大小截取图片的一部分
 *
 *  @param image UIImage image 原始的图片
 *  @param rect  CGRect rect 要截取的区域
 *
 *  @return UIImage
 */
- (UIImage *)ct_imageFromImage:(UIImage *)image inRect:(CGRect)rect {
    
    //把像素rect 转化为点rect(如无转化则按原图像素取部分图片)
    CGFloat scale = [UIScreen mainScreen].scale;
    CGFloat x= rect.origin.x*scale,y=rect.origin.y*scale,w=rect.size.width*scale,h=rect.size.height*scale;
    CGRect dianRect = CGRectMake(x, y, w, h);
    
    //截取部分图片并生成新图片
    CGImageRef sourceImageRef = [image CGImage];
    CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, dianRect);
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
    return newImage;
}

两个数组同步随机乱序的方法,完成后两个数组在相同的索引位置其对应关系仍保持不变。

- (void)randomArray {
    //两个数组同步打乱顺序,早知道这么麻烦我就用模型将索引值绑定image了。/(ㄒoㄒ)/~~
    NSMutableArray *newDatasourceArr = [NSMutableArray array];
    NSMutableArray *newStartIndexArr = [NSMutableArray array];

    int m = (int)self.dataSource.count;

    for (int i=0; i<m; i++) {
        int t = arc4random() % (self.dataSource.count);
        newDatasourceArr[i] = self.dataSource[t];
        newStartIndexArr[i] = self.startIndexs[t];
        self.dataSource[t] = [self.dataSource lastObject];
        self.startIndexs[t] = [self.startIndexs lastObject];
        [self.dataSource removeLastObject];
        [self.startIndexs removeLastObject];
    }
    self.dataSource = newDatasourceArr;
    self.startIndexs = newStartIndexArr;
}


12.17修改更新:关于打乱图序,我这种随机打乱顺序的做法欠妥,试玩几次后发现有些情况我总是还原不了,回忆上学时玩过的一款游戏没有出现过这样的情况。这时候我开始怀疑并不是所有的序列都可以进行还原。而我却忽略了,这非常不应该。

打乱后还需要验证当前状态是否有解。根据相关定理,如果打乱后的排列与原始排列的逆序数奇偶性相同,则是可还原的(证明比较简单 参考链接——不可还原的拼图)。如果拼图的版块是随机打乱的,那么只有50%概率是可以被还原的。我这里统一将空格设置在末尾最后一个,可以忽略掉,不影响逆序数。

方案二:让程序随机移动数次,这样肯定是能够还原的。这个“数次”也值得商榷,要尽可能乱,又不能太多次了。但是我这个游戏设定的打乱后空格统一在最后一格,还需要调整空格位置,同样用到刚才的逆序数相关定理,将空格与当前最后一个格子交换,现在排列奇偶性改变,还需要随机将非空格的两个格子进行交换一次。这样就可以了。

方案三:对于m*n的拼图,从拼图板块中任取三块做轮换,通过[(m*n)/3]^2次轮换,即可实现相当“乱”的打乱效果。所谓三轮换,实质就是两次交换:如123,1与2交换后,这时候状态213,再3与2交换,这时候状态312。体现在拼图上很好实验,把包含空格的2*2格子进行各种移动变换,就对应了3轮换。


还有个功能就是可以自定义几行几列,难点是需要动态更新相关数据,值得注意的是本例中cell是复用的,大小、内容需要根据需要即时调整。

最后奉献上demo https://github.com/imsz5460/-puzzlegame 欢迎大家找bug,并提出优化意见,谢谢!

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

推荐阅读更多精彩内容