1. 游戏规则

扫雷是玩法极其简单的小游戏,点击玩家认为不存在雷的区域,标记出全部地雷所在的区域,即可获得胜利。当点击不包含雷的块的时候,可能它底下存在一个数,也可能是一个空白块。当点击中有数字的块时,游戏会展现当前点击块所包含的数字。当点击空白块时,地图会展开,形成一个大小和形状不规则的图形,该图形的边界是数字块,也可以想成展开的是一个被数字包围着的不规则图形。

1.1 数字生成规则

扫雷游戏中是通过数字来判断雷的位置的,那么,数字的生成规则是什么呢?假设游戏中只有一个雷,那么,他的将被1这个数字包围着,如图:

1 1 1
1 1
1 1 1

如果遇到边界就忽略

1
1 1

可见,游戏是先生成雷然后再根据雷的位置生成数字的,我们再看下面的图:

1 1 1
1 2
1 2
. 1 1

在上图中,块中有两个数字为2的块,它是数字叠加的结果,围绕着雷的区域重合了,重合的区域块的数字相加,该块的数字就会变成相加后的数字。

1.2 本博文的例子扫雷的规则

玩家需要把所有的空白块点开,留下玩家认为有雷的块,当所剩余的块数和雷的数量相等时,玩家胜利。如果在此之前,点到有雷的方块,玩家失败。

2. 游戏的算法和数据结构

2.1 空白块展开算法

空白块的展开几乎是扫雷游戏的核心了。上面说到,扫雷游戏时,点中空白块,游戏的地图块就会展开,我们可以观察到:空白块是一层一层展开的,所以,地图展开算法我们就用广度优先搜索。也许有人会问:可以用深度优先搜索算法吗?答案是可以的,但是如果在这里用的话,效率会比广度优先搜索算法效率低。

2.2 扫雷的数据结构

(1)方向数组

  1. int[][] dir={
  2.       {-1,1},//左上角
  3.       {0,1},//正上
  4.       {1,1},//右上角
  5.       {-1,0},//正左
  6.       {1,0},//正右
  7.       {-1,-1},//左下角
  8.       {0,-1},//正下
  9.       {1,-1}//右下角
  10. };

方向数组在展开空白块的时候回用到,因为广度优先遍历就是在地图中朝各个方向走。

(2)Tile类

该类表示游戏中的“块”,我们给它声明三个成员。

  1. short    value
  2. boolean flag;
  3. boolean open;

value存储该块的值。-1表示雷块;0表示空白块;>0代表数字块。

flag存储该雷是否被玩家标记(在本例子中无作用,保留,方便扩展)。

open存储该块是否被用户点开过。

(3)Tile数组

Tile数组代表块的集合,及游戏的地图,存储着游戏的主要数据。

(4)Point类

Point类代表“位置”,声明Point类方便我们在地图中生成随机位置的雷。Point类还要重写hashCode和equals方法,为了比较位置与位置是否相同。

(5)Mine类

对上面的数据结构的封装。

Mine构造函数:对游戏地图的参数设置,比如绘制的位置,绘制的大小,块的大小,生成的雷数等。

init()方法:清空并初始化游戏地图。

create(Point p)方法:在地图中随机生成雷的位置,并产生数字。参数p是不产生雷的位置,p点可以传入用户第一次点击时的位置。生成随机位置的雷比较快速的办法是:先把地图中除p位置外所有的位置加入到链表中,然后生成0到链表大小-1之间的随机数,根据生成的随机数在链表中取元素,取完元素就把该位置从链表中移除,并把Tile数组中该位置的Tile的value设为-1。重复执行以上操作,直到生成的雷个数满足要求。产生数字的办法:遍历Tile数组,遇到雷就将他身边的八个的位置的value值加1,如果八个位置中有雷,或者该位置不存在,不执行任何操作。

open(Point p,boolean isFirst)方法:p代表点开某个位置的块,即Tile数组的索引。isFirst传入是否是第一次点击屏幕。该方法要对是不是第一次点击而作不同的操作,当玩家第一次点击块时,调用create函数生成地图。否则就进行展开地图等操作。

(6)MainView类

视图类,负责绘图和操作Mine对象。

3.代码示例

Mine.java

  1. public class Mine {
  2. public int x;//地图的在屏幕上的坐标点
  3. public int y;//地图的在屏幕上的坐标点
  4. public int mapCol;//矩阵宽
  5. public int mapRow;//矩阵高
  6. private int mineNum ;
  7. public static short EMPTY=0;//空
  8. public static short MINE=-1;//雷
  9. public Tile[][] tile;//地图矩阵
  10. public int tileWidth;//块宽
  11. private Paint textPaint;
  12. private Paint bmpPaint;
  13. private Paint tilePaint;
  14. private Paint rectPaint;
  15. private Paint minePaint;
  16. private Random rd=new Random();
  17. public int mapWidth;//绘图区宽
  18. public int mapHeight;//绘图区高
  19. public boolean isDrawAllMine=false;//标记是否画雷
  20. private int[][] dir={
  21. {-1,1},//左上角
  22. {0,1},//正上
  23. {1,1},//右上角
  24. {-1,0},//正左
  25. {1,0},//正右
  26. {-1,-1},//左下角
  27. {0,-1},//正下
  28. {1,-1}//右下角
  29. };//表示八个方向
  30. public class Tile{
  31. short value;
  32. boolean flag;
  33. boolean open;
  34. public Tile()
  35. {
  36. this.value=0;
  37. this.flag=false;
  38. this.open=false;
  39. }
  40. }
  41. public static class Point{
  42. private int x;
  43. private int y;
  44. public Point(int x,int y)
  45. {
  46. this.x=x;
  47. this.y=y;
  48. }
  49. @Override
  50. public int hashCode() {
  51. // TODO Auto-generated method stub
  52. return 2*x+y;
  53. }
  54. @Override
  55. public boolean equals(Object obj) {
  56. // TODO Auto-generated method stub
  57. return this.hashCode()==((Point)(obj)).hashCode();
  58. }
  59. }//表示每个雷块
  60. public Mine(int x, int y, int mapCol, int mapRow, int mineNum, int tileWidth)
  61. {
  62. this.x=x;
  63. this.y=y;
  64. this.mapCol = mapCol;
  65. this.mapRow = mapRow;
  66. this.mineNum=mineNum;
  67. this.tileWidth=tileWidth;
  68. mapWidth=mapCol*tileWidth;
  69. mapHeight=mapRow*tileWidth;
  70. textPaint=new Paint();
  71. textPaint.setAntiAlias(true);
  72. textPaint.setTextSize(MainActivity.W/10);
  73. textPaint.setColor(Color.RED);
  74. bmpPaint=new Paint();
  75. bmpPaint.setAntiAlias(true);
  76. bmpPaint.setColor(Color.DKGRAY);
  77. tilePaint =new Paint();
  78. tilePaint.setAntiAlias(true);
  79. tilePaint.setColor(0xff1faeff);
  80. minePaint =new Paint();
  81. minePaint.setAntiAlias(true);
  82. minePaint.setColor(0xffff981d);
  83. rectPaint =new Paint();
  84. rectPaint.setAntiAlias(true);
  85. rectPaint.setColor(0xff000000);
  86. rectPaint.setStyle(Paint.Style.STROKE);
  87. tile=new Tile[mapRow][mapCol];
  88. }
  89. /**
  90. * 初始化地图
  91. */
  92. public void init()
  93. {
  94. for (int i = 0; i< mapRow; i++)
  95. {
  96. for (int j = 0; j< mapCol; j++)
  97. {
  98. tile[i][j]=new Tile();
  99. tile[i][j].value=EMPTY;
  100. tile[i][j].flag=false;
  101. tile[i][j].open=false;
  102. isDrawAllMine=false;
  103. }
  104. }
  105. }
  106. /**
  107. * 生成雷
  108. * @param exception 排除的位置,该位置不生成雷
  109. */
  110. public void create(Point exception)
  111. {
  112. List<Point> allPoint=new LinkedList<Point>();
  113. //把所有位置加入链表
  114. for (int i = 0; i< mapRow; i++)//y
  115. {
  116. for (int j = 0; j < mapCol; j++)//x
  117. {
  118. Point point=new Point(j,i);
  119. if(!point.equals(exception))
  120. {
  121. allPoint.add(point);
  122. }
  123. }
  124. }
  125. List<Point> minePoint=new LinkedList<Point>();
  126. //随机产生雷
  127. for (int i=0; i< mineNum; i++)
  128. {
  129. int idx=rd.nextInt(allPoint.size());
  130. minePoint.add(allPoint.get(idx));
  131. allPoint.remove(idx);//取了之后,从所有集合中移除
  132. }
  133. //在矩阵中标记雷的位置
  134. for(Iterator<Point> it=minePoint.iterator();it.hasNext();)
  135. {
  136. Point p=it.next();
  137. tile[p.y][p.x].value=MINE;
  138. }
  139. //给地图添加数字
  140. for (int i = 0; i< mapRow; i++)//y
  141. {
  142. for (int j = 0; j< mapCol; j++)//x
  143. {
  144. short t=tile[i][j].value;
  145. if(t==MINE)
  146. {
  147. for (int k=0;k<8;k++)
  148. {
  149. int offsetX=j+dir[k][0],offsetY=i+dir[k][1];
  150. if(offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow ) {
  151. if (tile[offsetY][offsetX].value != -1)
  152. tile[offsetY][offsetX].value += 1;
  153. }
  154. }
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * 打开某个位置
  161. * @param op
  162. * @param isFirst 标记是否是第一次打开
  163. */
  164. public void open(Point op,boolean isFirst)
  165. {
  166. if(isFirst)
  167. {
  168. create(op);
  169. }
  170. tile[op.y][op.x].open=true;
  171. if( tile[op.y][op.x].value==-1)
  172. return;
  173. else if( tile[op.y][op.x].value>0)//点中数字块
  174. {
  175. return;
  176. }
  177. //广度优先遍历用队列
  178. Queue<Point> qu=new LinkedList<Point>();
  179. //加入第一个点
  180. qu.offer(new Point(op.x,op.y));
  181. //朝8个方向遍历
  182. for (int i=0;i<8;i++)
  183. {
  184. int offsetX=op.x+dir[i][0],offsetY=op.y+dir[i][1];
  185. //判断越界和是否已访问
  186. boolean isCan=offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow;
  187. if(isCan)
  188. {
  189. if(tile[offsetY][offsetX].value==0 &&!tile[offsetY][offsetX].open) {
  190. qu.offer(new Point(offsetX, offsetY));
  191. }
  192. else if(tile[offsetY][offsetX].value>0)
  193. {
  194. tile[offsetY][offsetX].open=true;
  195. }
  196. }
  197. }
  198. while(qu.size()!=0)
  199. {
  200. Point p=qu.poll();
  201. tile[p.y][p.x].open=true;
  202. for (int i=0;i<8;i++)
  203. {
  204. int offsetX=p.x+dir[i][0],offsetY=p.y+dir[i][1];
  205. //判断越界和是否已访问
  206. boolean isCan=offsetX>=0&&offsetX< mapCol &&offsetY>=0&&offsetY< mapRow;
  207. if(isCan)
  208. {
  209. if( tile[offsetY][offsetX].value==0&&!tile[offsetY][offsetX].open) {
  210. qu.offer(new Point(offsetX, offsetY));
  211. }
  212. else if(tile[offsetY][offsetX].value>0)
  213. {
  214. tile[offsetY][offsetX].open=true;
  215. }
  216. }
  217. }
  218. }
  219. }
  220. /**
  221. * 绘制地图
  222. * @param canvas
  223. */
  224. public void draw(Canvas canvas)
  225. {
  226. for (int i = 0; i< mapRow; i++)
  227. {
  228. for (int j = 0; j< mapCol; j++)
  229. {
  230. Tile t=tile[i][j];
  231. if(t.open){
  232. if(t.value>0)
  233. {
  234. canvas.drawText(t.value+"",x+j*tileWidth,y+i*tileWidth+tileWidth,textPaint);
  235. }
  236. }else
  237. {
  238. //标记,备用
  239. if(t.flag)
  240. {
  241. }else
  242. {
  243. //画矩形方块
  244. RectF reactF=new RectF(x+j*tileWidth,y+i*tileWidth,x+j*tileWidth+tileWidth,y+i*tileWidth+tileWidth);
  245. canvas.drawRoundRect(reactF,0,0, tilePaint);
  246. }
  247. }
  248. //是否画出所有雷
  249. if( isDrawAllMine&&tile[i][j].value==-1) {
  250. canvas.drawCircle((x + j * tileWidth) + tileWidth / 2, (y + i * tileWidth) + tileWidth / 2, tileWidth / 2, bmpPaint);
  251. }
  252. }
  253. }
  254. //画边框
  255. canvas.drawRect(x,y,x+mapWidth,y+mapHeight, rectPaint);
  256. //画横线
  257. for (int i = 0; i< mapRow; i++) {
  258. canvas.drawLine(x,y+i*tileWidth,x+mapWidth,y+i*tileWidth, rectPaint);
  259. }
  260. //画竖线
  261. for (int i = 0;i < mapCol; i++) {
  262. canvas.drawLine(x+i*tileWidth,y,x+i*tileWidth,y+mapHeight, rectPaint);
  263. }
  264. }
  265. }

MainView.java

  1. public class MainView extends View {
  2. private Mine mine;
  3. private boolean isFirst=true;//标记是否是本局第一次点击屏幕
  4. private Context context;
  5. private final int mineNum=10;//产生的雷的个数
  6. private final int ROW=15;//要生成的矩阵高
  7. private final int COL=8;//要生成的矩阵宽
  8. private int TILE_WIDTH=50;//块大小
  9. private boolean isFalse=false;
  10. public MainView(Context context)
  11. {
  12. super(context);
  13. this.context=context;
  14. TILE_WIDTH=MainActivity.W/10;
  15. mine=new Mine((MainActivity.W-COL*TILE_WIDTH)/2,(MainActivity.H-ROW*TILE_WIDTH)/2,COL,ROW,mineNum,TILE_WIDTH);
  16. try {
  17. mine.init();
  18. }catch (Exception e){
  19. e.printStackTrace();
  20. }
  21. }
  22. /**
  23. * 游戏逻辑
  24. */
  25. public void logic()
  26. {
  27. int count=0;
  28. for (int i=0;i<mine.mapRow;i++)
  29. {
  30. for (int j=0;j<mine.mapCol;j++)
  31. {
  32. if(!mine.tile[i][j].open)
  33. {
  34. count++;
  35. }
  36. }
  37. }
  38. //逻辑判断是否胜利
  39. if(count==mineNum)
  40. {
  41. new AlertDialog.Builder(context)
  42. .setMessage("恭喜你,你找出了所有雷")
  43. .setCancelable(false)
  44. .setPositiveButton("继续", new DialogInterface.OnClickListener() {
  45. @Override
  46. public void onClick(DialogInterface dialog, int which) {
  47. mine.init();
  48. invalidate();
  49. isFirst=true;
  50. }
  51. })
  52. .setNegativeButton("退出", new DialogInterface.OnClickListener() {
  53. @Override
  54. public void onClick(DialogInterface dialog, int which) {
  55. System.exit(0);
  56. }
  57. })
  58. .create()
  59. .show();
  60. }
  61. }
  62. /**
  63. * 刷新View
  64. * @param canvas
  65. */
  66. @Override
  67. protected void onDraw(Canvas canvas) {
  68. mine.draw(canvas);
  69. }
  70. /**
  71. * 点击屏幕事件
  72. * @param event
  73. * @return
  74. */
  75. @Override
  76. public boolean onTouchEvent(MotionEvent event) {
  77. if(event.getAction()==MotionEvent.ACTION_DOWN)
  78. {
  79. int x=(int)event.getX();
  80. int y=(int)event.getY();
  81. //判断是否点在范围内
  82. if(x>=mine.x&&y>=mine.y&&x<=(mine.mapWidth+mine.x)&&y<=(mine.y+mine.mapHeight))
  83. {
  84. int idxX=(x-mine.x)/mine.tileWidth;
  85. int idxY=(y-mine.y)/mine.tileWidth;
  86. mine.open(new Mine.Point(idxX,idxY),isFirst);
  87. isFirst=false;
  88. if(mine.tile[idxY][idxX].value==-1)
  89. {
  90. mine.isDrawAllMine=true;
  91. new AlertDialog.Builder(context)
  92. .setCancelable(false)
  93. .setMessage("很遗憾,你踩到雷了!")
  94. .setPositiveButton("继续", new DialogInterface.OnClickListener() {
  95. @Override
  96. public void onClick(DialogInterface dialog, int which) {
  97. mine.init();
  98. isFalse=true;
  99. isFirst=true;
  100. invalidate();
  101. }
  102. })
  103. .setNegativeButton("退出", new DialogInterface.OnClickListener() {
  104. @Override
  105. public void onClick(DialogInterface dialog, int which) {
  106. System.exit(0);
  107. }
  108. })
  109. .create()
  110. .show();
  111. }
  112. if(isFalse)
  113. {
  114. isFalse=false;
  115. invalidate();
  116. return true;
  117. }
  118. logic();
  119. invalidate();
  120. }
  121. }
  122. return true;
  123. }
  124. }

MainActivity.java

  1. public class MainActivity extends Activity {
  2. public static int W;
  3. public static int H;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. DisplayMetrics dm = new DisplayMetrics();
  8. getWindowManager().getDefaultDisplay().getMetrics(dm);
  9. W = dm.widthPixels;//宽度
  10. H = dm.heightPixels ;//高度
  11. setContentView(new MainView(this));
  12. new AlertDialog.Builder(this)
  13. .setCancelable(false)
  14. .setTitle("游戏规则")
  15. .setMessage("把你认为不是雷的位置全部点开,只留着有雷的位置,每局游戏有10个雷。")
  16. .setPositiveButton("我知道了",null)
  17. .create()
  18. .show();
  19. }
  20. }

完整代码:https://github.com/luoyesiqiu/Mine

【Android】自己动手做个扫雷游戏的更多相关文章

  1. [Android]自己动手做个拼图游戏

    目标 在做这个游戏之前,我们先定一些小目标列出来,一个一个的解决,这样,一个小游戏就不知不觉的完成啦.我们的目标如下: 游戏全屏,将图片拉伸成屏幕大小,并将其切成若干块. 将拼图块随机打乱,并保证其能 ...

  2. wpf版扫雷游戏

    近来觉得wpf做出来的界面很拉风,自己也很喜欢搞些小游戏,感觉这做出来的会很炫,很装逼,(满足自己的一点小小的虚荣心)于是就去自学,发现感觉很不错,可是属性N多,太多了,而且质料也少,很多不会用,只会 ...

  3. [LeetCode] Minesweeper 扫雷游戏

    Let's play the minesweeper game (Wikipedia, online game)! You are given a 2D char matrix representin ...

  4. (转载)WinformGDI+入门级实例——扫雷游戏(附源码)

    本文将作为一个入门级的.结合源码的文章,旨在为刚刚接触GDI+编程或对相关知识感兴趣的读者做一个入门讲解.游戏尚且未完善,但基本功能都有,完整源码在文章结尾的附件中. 整体思路: 扫雷的游戏界面让我从 ...

  5. C# -- HttpWebRequest 和 HttpWebResponse 的使用 C#编写扫雷游戏 使用IIS调试ASP.NET网站程序 WCF入门教程 ASP.Net Core开发(踩坑)指南 ASP.Net Core Razor+AdminLTE 小试牛刀 webservice创建、部署和调用 .net接收post请求并把数据转为字典格式

    C# -- HttpWebRequest 和 HttpWebResponse 的使用 C# -- HttpWebRequest 和 HttpWebResponse 的使用 结合使用HttpWebReq ...

  6. 我用Bash编写了一个扫雷游戏

    我在编程教学方面不是专家,但当我想更好掌握某一样东西时,会试着找出让自己乐在其中的方法.比方说,当我想在 shell 编程方面更进一步时,我决定用 Bash 编写一个扫雷游戏来加以练习. 我在编程教学 ...

  7. 使用vue写扫雷游戏

    上班闲来没事做,,心血来潮.想用刚学的vue,写一个扫雷游戏..好了,直入正题. 第一步,先制作一个10x10的格子图..这个divcss就不说了..大家都会. 第二步,制造一个数组,用来生成随机雷区 ...

  8. 「雕爷学编程」Arduino动手做(38)——joystick双轴摇杆模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

  9. 「雕爷学编程」Arduino动手做(33)——ESP-01S无线WIFI模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...

随机推荐

  1. spring cloud 入门系列五:使用Feign 实现声明式服务调用

    一.Spring Cloud Feign概念引入通过前面的随笔,我们了解如何通过Spring Cloud ribbon进行负责均衡,如何通过Spring Cloud Hystrix进行服务断路保护,两 ...

  2. 深入理解SpringAOP之代理对象

    本篇文章主要带大家简单分析一下AOP的代理对象,至于AOP是什么,如何配置等基础性知识,不在这里讨论.阅读前请先参考:代理模式,在这之前我们需要了解springframework的三个核心接口与get ...

  3. Python3实现ICMP远控后门(上)_补充篇

    ICMP后门(上)补充篇 前言 在上一篇文章Python3实现ICMP远控后门(上)中,我简要讲解了ICMP协议,以及实现了一个简单的ping功能,在文章发表之后,后台很多朋友留言,说对校验和的计算不 ...

  4. Python黑客泰斗利用aircrack-ng破解 wifi 密码,超详细教程!

    开始前,先连上无线网卡,因为虚拟机中的kali系统不用调用笔记本自带的无线网卡,所以需要一个外接无线网卡,然后接入kali系统. 输入 ifconfig -a 查看网卡,多了个 wlan0,说明网卡已 ...

  5. HTML DOM innerHTML 属性及实现图片连续播放

    定义和用法 innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML. 语法 tablerowObject.innerHTML=HTML 实例 下面的例子返回了表格行的 inner H ...

  6. File,FileInfo,FileStream,StreamReader的区别与用法

    概括的说,File,FileInfo,FileStream是用于文件 I/O 的类,StreamReader是用于从流读取和写入流的类,使用之前都需using System.IO. 先定义一个TXT文 ...

  7. python_pycharm下拉前置标示

    在pycharm中是自动补全的变量的类别p:parameter 参数m:method方法c:class 类v:variable 变量f:function 函数 从定义的角度上看,我们知道函数(func ...

  8. MySQL中查询时"Lost connection to MySQL server during query"报错的解决方案

    一.问题描述: mysql数据库查询时,遇到下面的报错信息: 二.原因分析: dw_user 表数据量比较大,直接查询速度慢,容易"卡死",导致数据库自动连接超时.... 三.解决 ...

  9. capwap学习笔记——初识capwap(四)(转)

    2.5.7 CAPWAP传输机制 WTP和AC之间使用标准的UDP客户端/服务器模式来建立通讯. CAPWAP协议支持UDP和UDP-Lite [RFC3828]. ¢ 在IPv4上,CAPWAP控制 ...

  10. java位 、字节 、字符的梳理

    1字节(byte)=8位(bit) char=2字节(这是因为char是Java中的保留字,Java用的是Unicode,所以char在Java中是16位即2个字节的.) 附: String str= ...