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


    前言

    最近写了一个 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,并提出优化意见,谢谢!

    说明:本文首发于本人简书 https://www.jianshu.com/p/8f1436f688d3

  • 相关阅读:
    带有“全选”的combotree
    combotree(组合树)的使用
    根据权限显示accordion
    accordion(折叠面板)的使用
    js中substr、substring、indexOf、lastIndexOf的用法
    EasyUI中使用自定义的icon图标
    EasyUI tree的三种选中状态
    C# List集合去重使用lambda表达式
    C# DataTable去重,根据列名去重保留其他列
    win10下部署.Net Web项目到IIS10
  • 原文地址:https://www.cnblogs.com/imsz/p/8143322.html
Copyright © 2020-2023  润新知