iOS 自制小游戏之扫雷

时间过得真快,倏忽之间,毕业已近一年。

毕业答辩时做的项目有个游戏模块,扫雷游戏。当时也是想挑战一下自己,于是就着手构思怎么实现这个游戏。前前后后大概用了一周时间完成了扫雷的基本功能,如点击扫雷,长按标注地雷,难度选择等。但没有实现点击空白单元周围也翻过来的功能。后来在一次面试中,和一个技术总监谈到游戏的不足时,他提示到可以用递归遍历的方法找到点击的空白单元周围所有可以翻转单元的位置,才恍然大悟。

闲话少说,且看我的思路和实现过程


还是先上 Demo

#以下以10×10地图为例进行分析

定义

1)用数组mineMapArray(0 - 9)存储每个单元的状态,初始化全为0

0,表示单元周围没有地雷
1 - 8,表示单元周围有1 - 8个地雷
9,表示该单元是地雷

单元的周围是指,当前单元的左上方、上方、右上方、右方、右下方、下方、左下方、左方的单元。一个单元周围最多有8个单元。

2)用数组minesArray(0 - 99)存储所有地雷的位置
3)用数组turnoverArray(0 - 99)存储点击空白单元时可翻转所有单元的位置

1、随机地雷的位置

1)先创建临时地图位置数组tmpMapArray(0 - 99),方便下一步随机_mineNums个地雷位置用

 //1.创建临时地图位置数组,用于随机出地雷位置
    NSMutableArray *tmpMapArray = [NSMutableArray array];//临时地图位置数组
    for (int i = 0; i < _row * _column; i++) {
        [tmpMapArray addObject:@(i)];
    }

2)随机产生_mineNums个地雷并记录地雷位置到地图相应mineMapArray

delIndex,临时地图数组tmpMapArray的删除的位置
addIndex,地雷地图数组minesArray上添加地雷的位置

//2.更新地图地雷位置和记录地雷位置
    int delIndex;//随机地雷的位置
    int addIndex;//地雷添加到地图的位置
    for (int i = 0; i < _mineNums; i++) {
        delIndex = arc4random() % tmpMapArray.count;
        addIndex = [tmpMapArray[delIndex] intValue];
        [self.mineMapArray replaceObjectAtIndex:addIndex withObject:@(9)];//更地图上地雷位置
        [self.minesArray addObject:tmpMapArray[delIndex]];//添加地雷位置到存储所有地雷位置的数组
        [tmpMapArray removeObjectAtIndex:delIndex];//删除临时随机的地雷位置
    }

3)计算每个不是地雷的单元的周围地雷数量

一般情况下地雷的数量比较少,所以,首先我们可以遍历找到地雷单元,然后再遍历地雷周围的单元,再在mineMapArray数组相应位置上加1。

10×10

地雷的位置是location_row是行数, _column是列数,即一行单元的个数
所以,
左上 = location - _column - 1
上 = location - _column
右上 = location - _column + 1
右 = location + 1
右下 = location + _column + 1
下 = location + _column
左下 = location + _column - 1
左 = location - 1

注意:在遍历周围单元是要注意是否在边界位置
location / _column != 0 判断当前单元是否在第一行
location % _column != 0 判断当前单元是否在第一列
location / _column != _row - 1 判断当前单元是否在最后一行
location % _column != _column - 1 判断当前单元是否在最后一列

左上单元,需要判断当前单元是否在第一行&&是否在第一列
上单元,需要判断当前单元是否在第一行
右上单元,需要判断当前单元是否在第一行&&是否在最后一列
右单元,需要判断当前单元是否在最后一列
右下单元,需要判断当前单元是否在最后一行&&最后一列
下单元,需要判断当前单元是否在最后一行
左下单元,需要判断当前单元是否在最后一行&&第一列
左单元,需要判断当前单元是否在第一列

 //3.标记地雷周围数字
    for (NSNumber *obj in self.minesArray) {//找到地雷周围位置,标记数值加1
        NSInteger location = [obj integerValue];
        NSInteger aroundLocation;//遍历地雷周围8个位置
        
        
        
        //location / _column != 0 判断是否在第一行
        //location % _column != 0 判断是否在第一列
        //location / _column != _row - 1 判断是否在最后一行
        //location % _column != _column - 1 判断是否在最后一列
        //
        
        aroundLocation = location - _column;//上
        if (location / _column != 0) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location - _column + 1;//右上
        if (location / _column && location % _column != _column - 1) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location + 1;//右
        if (location % _column != _column - 1) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location + _column + 1;//右下
        if (location % _column != _column - 1 && location / _column != _row - 1) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location + _column;//下
        if (location / _column != _row - 1) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location + _column - 1;//左下
        if (location / _column != _row - 1 && location % _column != 0) {
            [self locationPlus:aroundLocation];
        }
        
        aroundLocation = location - 1;//左
        if (location % _column != 0) {
            [self locationPlus:aroundLocation];
        }

        aroundLocation = location - _column - 1;//左上
        if (location / _column != 0 && location % _column != 0) {
            [self locationPlus:aroundLocation];
        }
        
    }
- (void)locationPlus:(NSInteger)location {
    NSInteger cellMineNums = [[self.mineMapArray objectAtIndex:location] integerValue];
    if (cellMineNums != 9) {
        cellMineNums++;
    }
    [self.mineMapArray replaceObjectAtIndex:location withObject:@(cellMineNums)];
}

2、初始化地图

代码:

/**
 *  初始化地图
 */
- (void)setupMapView {
    
    for (int i = 0; i < _row * _column; i++) {
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.tag = kTag + i;
        //设置frame
        CGRect screenBounds = [UIScreen mainScreen].bounds;
        CGFloat buttonW = (screenBounds.size.width - kBorderX * 2 - (_column - 1) * kGap) / _column;
        CGFloat buttonH = buttonW;
        CGFloat buttonX = (i % _column) * (buttonW + kGap) + kBorderX;
        CGFloat buttonY = (i / _column) * (buttonH + kGap) + kBorderX;
        button.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH);
        button.backgroundColor = [UIColor grayColor];
        [button setBackgroundImage:[UIImage imageNamed:[NSString stringWithFormat:@"selected_%@", self.mineMapArray[i]]] forState:UIControlStateSelected];
        [button setBackgroundImage:[UIImage imageNamed:@"selected_bg"] forState:UIControlStateNormal];
        [button addTarget:self action:@selector(cellButtonSelect:) forControlEvents:UIControlEventTouchUpInside];
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(markMine:)];
        [button addGestureRecognizer:longPress];
        [self.bgView addSubview:button];
    }
}

随机地雷位置数组:

[0, 0, 0, 1, 9, 1, 0, 0, 1, 1,
 0, 0, 0, 1, 1, 1, 1, 1, 2, 9,
 0, 0, 0, 0, 0, 0, 1, 9, 4, 3,
 0, 0, 0, 1, 2, 3, 3, 3, 9, 9,
 0, 1, 1, 2, 9, 9, 9, 2, 2, 2,
 0, 2, 9, 4, 4, 4, 3, 1, 0, 0,
 0, 2, 9, 9, 2, 9, 2, 1, 1, 0,
 0, 1, 2, 2, 2, 1, 2, 9, 1, 0,
 1, 1, 1, 1, 1, 0, 1, 1, 1, 0,
 9, 1, 1, 9, 1, 0, 0, 0, 0, 0]

地雷位置效果图:


效果图

3、扫雷逻辑

  • 点击数字单元,数字单元翻过来
  • 点击地雷单元,所有单元翻过来,游戏结束
  • 点击空白单元,找出其周围“可翻转的单元”,并翻转过来

“可翻转的单元”是指,如下图,当点击绿色区域内任意空白单元时绿色区域全部翻转过来

分析图
下面是当点击黄点位置空白单元时,找到其周围可翻转的单元的思路和算法
递归遍历过程图

思路:

① 如果当前单元是空白单元,先把这个单元存到turnoverArray;
② 再依次判断这个单元的上、右上、右、右下、下、左下、左、左上单元
③ 如果判断的单元是空白单元,则把判断的单元作为当前空白单元回到①;如果判断的单元是数字单元,则回到②依次进行判断;

核心算法:

- (void)findAllTurnover:(NSInteger)location {
    
   if (![self.turnoverArray containsObject:@(location)]) {//如果turnoverArray不包含这个单元,存进去
        [self.turnoverArray addObject:@(location)];
    }
    if ([self.mineMapArray[location] integerValue] != 0) {//如果当前单元不是空白单元则,回到上一层继续寻找下一个位置
        return;
    }
    
    NSInteger aroundLocation;
    aroundLocation = location - _column - 1;//左上
    if (location / _column != 0 && location % _column != 0) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location - _column;//上
    if (location / _column != 0) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location - _column + 1;//右上
    if (location / _column && location % _column != _column - 1) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location + 1;//右
    if (location % _column != _column - 1) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location + _column + 1;//右下
    if (location % _column != _column - 1 && location / _column != _column - 1) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location + _column;//下
    if (location / _column != _column - 1) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location + _column - 1;//左下
    if (location / _column != _column - 1 && location % _column != 0) {
        [self addTurnover:aroundLocation];
    }
    
    aroundLocation = location - 1;//左
    if (location % _column != 0) {
        [self addTurnover:aroundLocation];
    }
    
}
- (void)addTurnover:(NSInteger)location {
    
    if ([self.turnoverArray containsObject:@(location)]) {//如果已经包含这个单元return
        return;
    }
    [self.turnoverArray addObject:@(location)];
    [self findAllTurnover:location];
}

4、颇多不足,望各位不吝赐教

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

推荐阅读更多精彩内容