iOS 扫雷游戏
1、项目结构图

Viewcontroller:扫雷逻辑代码
LevelModel:扫雷难度选择代码
2、定义
相信很多人小时候都玩过Windows XP系统的扫雷游戏。记得刚开始玩时不知道游戏规则,以为全靠运气,点到白色区域就是没有地雷,高兴地又蒙对了。后来发现了其中的奥秘,原来白色块代表周围都没有地雷,数字块代表其周围有几块地雷。根据这个规则我们开始扫雷游戏的实现:
以下以10×10地图为例进行分析
1)用数组mineMapArray(0 - 9)存储每个单元的状态,初始化全为0
0,表示单元周围没有地雷
1 - 8,表示单元周围有1 - 8个地雷
9,表示该单元是地雷
单元的周围是指,当前单元的左上方、上方、右上方、右方、右下方、下方、左下方、左方的单元。一个单元周围最多有8个单元。
2)用数组minesArray(0 - 99)存储所有地雷的位置
3)用数组turnoverArray(0 - 99)存储点击空白单元时可翻转所有单元的位置
3、随机地雷的位置
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。

地雷的位置是
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)];
}
4、初始化地图
代码:
/**
* 初始化地图
*/
- (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]
地雷位置效果图:

5、扫雷逻辑
- 点击数字单元,数字单元翻过来
- 点击地雷单元,所有单元翻过来,游戏结束
- 点击空白单元,找出其周围“可翻转的单元”,并翻转过来
“可翻转的单元”是指,如下图,当点击绿色区域内任意空白单元时绿色区域全部翻转过来

下面是当点击黄点位置空白单元时,找到其周围可翻转的单元的思路和算法

PS:偷偷地告诉你,例子包里有遍历流程。
思路:
① 如果当前单元是空白单元,先把这个单元存到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];
}
6、实现效果

7、颇多不足,望各位不吝赐教
- 有些细节没有说明白,欢迎留言讨论。
- 用递归遍历寻找空白单元周围可翻转单元时,时间复杂度太大。
最后,推荐一个画图标神器 Sketch,还有一个图标素材搜索网站easyicon。我一般都是在easyicon上找素材,能用的直接用,需要改动的再在Sketch上修改。
另外,Mac自带的软件Keynote也很好用。本文中的图10×10,分析图和递归遍历过程图都是用Keynote制作的。
什么!玩得不过瘾?[来这儿!]
[来这儿!]:https://mienfield.comiOS 扫雷游戏
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
iOS 扫雷游戏的更多相关文章
- 洛谷 P2670 扫雷游戏==Codevs 5129 扫雷游戏
题目描述 扫雷游戏是一款十分经典的单机小游戏.在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格).玩家翻开一个非地雷格时,该格将会出现一个数字——提示周围格子中有 ...
- 通过Unity3D发布IOS版游戏
https://developer.apple.com/ 打开上面的苹果开发者网站,选择上面的"Member Center"登录进入.前提是,你注册了开发者账号,并且付了年费. 选 ...
- 原生javascript扫雷游戏
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Java练习(模拟扫雷游戏)
要为扫雷游戏布置地雷,扫雷游戏的扫雷面板可以用二维int数组表示.如某位置为地雷,则该位置用数字-1表示, 如该位置不是地雷,则暂时用数字0表示. 编写程序完成在该二维数组中随机布雷的操作,程序读入3 ...
- JAVA_扫雷游戏(布置地雷)
1.要为扫雷游戏布置地雷,扫雷游戏的扫雷面板可以用二维int数组表示.如某位置为地雷,则该位置用数字-1表示, 如该位置不是地雷,则暂时用数字0表示. 编写程序完成在该二维数组中随机布雷的操作,程序读 ...
- [LeetCode] Minesweeper 扫雷游戏
Let's play the minesweeper game (Wikipedia, online game)! You are given a 2D char matrix representin ...
- C语言二维数组实现扫雷游戏
#include<stdio.h> //使用二维数组实现 扫雷 int main() { char ui[8][8]={ '+','+','+','+','+','+','+','+', ...
- 【Android】自己动手做个扫雷游戏
1. 游戏规则 扫雷是玩法极其简单的小游戏,点击玩家认为不存在雷的区域,标记出全部地雷所在的区域,即可获得胜利.当点击不包含雷的块的时候,可能它底下存在一个数,也可能是一个空白块.当点击中有数字的块时 ...
- C#编写扫雷游戏
翻看了下以前大学学习的一些小项目,突然发现有个项目比较有意思,觉得有必要把它分享出来.当然现在看来,里面有很多的不足之处,但因博主现在已经工作,没有时间再去优化.这个项目就是利用C#编写一个Windo ...
随机推荐
- 【动态规划】mr368-教主种树
[题目大意] 教主有着一个环形的花园,他想在花园周围均匀地种上n棵树,但是教主花园的土壤很特别,每个位置适合种的树都不一样,一些树可能会因为不适合这个位置的土壤而损失观赏价值. 教主最喜欢3种树,这3 ...
- 【Floyd】POJ1125-Stockbroker Grapevine
水题,裸的Floyd.最后要求遍历一遍图的最短路径,只需要枚举将当前每一个点作为起始点.如果它不能到达其中的某一点,则该点不可能作为起始点:否则,由该点开始遍历全图的最短路径是到所有点距离中的最大值. ...
- [NOI2017]整数
[NOI2017]整数 题目大意: \(n(n\le10^6)\)次操作维护一个长度为\(30n\)的二进制整数\(x\),支持以下两种操作: 将这个整数加上\(a\cdot2^b(|a|\le10^ ...
- 【MySQL笔记】用户管理
1.账户管理 1.1登录和退出MySQL服务器 MySQL –hhostname|hostIP –P port –u username –p[password] databaseName –e &qu ...
- [转]Syntax error on token "Invalid Character", delete this token 的解决
原文 http://blog.csdn.net/actsai/article/details/24256987 主题 Eclipse Unicode Java eclipse 中遇到了Syntax ...
- [转]Java中子类调用父类构造方法的问题分析
在Java中,子类的构造过程中,必须调用其父类的构造函数,是因为有继承关系存在时,子类要把父类的内容继承下来,通过什么手段做到的? 答案如下: 当你new一个子类对象的时候,必须首先要new一个 ...
- 【Rocket MQ】RocketMQ4.2.0 和 spring boot的结合使用,实现分布式事务
RocketMQ4.2.0 和 spring boot的结合使用,实现分布式事务 参考地址:https://www.jianshu.com/p/f57de40621a0
- 【mysql】新方法修改数据库密码以及解决--ERROR 1045 (28000)的问题
之前 有写过一篇修改mysql数据库的密码的一篇随笔, 地址是:http://www.cnblogs.com/sxdcgaq8080/p/5667124.html 但是此次采用原本的老方法,出现了问题 ...
- iOS -- DES算法
算法步骤: DES算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位(实际用到了56位,第8.16.24.32.40.48.56.64位是校验位, 使得每个密钥都有奇数个1),其 ...
- flask使用ajax上传图片或者文件
function upload_cover(){ var cover = new FormData(); var fileObj = document.getElementById('cover'). ...