6.5 *扫雷矩阵
扫雷是一个大家都很熟悉的小游戏,游戏规则简单。
点中雷当然就失败了,如果没有点中雷,那么将显示一个数字,这个数字代表周围8个格子有多少颗雷。
如果确定了是雷,可以右键标记是雷,直到所有的雷都被找到。
这一节,我们并不是要做一个扫雷游戏,也不涉及玩法,而是要掌握扫雷背后的数据生成:
1.雷是怎么分布的?
2.究竟那些数字是怎么生成的?
完成下面的练习,做到最后这个谜底会解开。
这一节,我们将采用逐次迭代的的思维方式:每次改进都更接近目标
练习题
-
阅读下面的算法1.生成一个二维矩阵 [阅读]在第一节的介绍中,有这个代码,它可以打印一个10*10的*号矩阵,现在修改这个代码,初始化了一个10*10的矩阵,所有值都设置为0,并且打印。由于整数数组初值就是0,所以数组创建后直接打印就可以了。
这个就是初始化的地雷矩阵,一个雷都没有。public static void main(String[]args){ int mines[][]=new int[10][10]; for(int i=0;i < 10;i++){ for(int j=0;j < 10;j++) System.out.print(mines[i][j]+" "); System.out.println();}}2.唯一标识数字 [阅读]上一节我们得到了一个10*10的矩阵,现在,我们要添加雷了。这个雷首先需要的是随机性,如果某个位置是雷,那么就用9代替0,也就是说我们需要得到类似这样的一个矩阵:
9000
0090
0900
9000
这个4*4的地雷阵有4个雷。
这怎么做到呢。
首先矩阵任意位置需要一个唯一的数字作为它们的标识。它的坐标其实就是矩阵里唯一的,例如(x,y)标识第x行第y列,但是怎么转化成唯一的数字呢?我们来看看第一节的代码,打印出来的数字,就是唯一的数字,通用计算方法就是:
x*列数量+ypublic static void main(String[]s){ for(int i=0;i < 10;i++){ for(int j=0;j < 10;j++){ System.out.print(i*10+j+" ");} System.out.println();}}3.生成地雷算法 [阅读]现在我们用Math的随机函数Math.random()。具体思路如下:
1.在矩阵最大数字范围内(0-99),随机生成一个数字
2.将这个数字反向计算出x和y坐标
3.用这个x和y将第一个矩阵的0替换成9,这就表示放了一个雷
4.如果还需要放雷,回到第1步,否则就放完雷,结束
接下来,我们就来逐个解决这四个问题。4.生成随机数 [阅读]Math.random()可以生成一个0到1的小数,随机,而且很多个小数,类似于这样0.635373627262
怎么把这个随机小数缩小到0-99的范围呢?(为啥是0-99?思考下。)
Math.random()*99的话,这个随机数字就是0-99的一个小数!非常简单吧,然后切掉小数部分,我们可以强制转换成整数,也就是:
(int)(Math.random()*99)
注意,Math.random()*99一定要用括号,否则int强制类型转换的是Math.random(),这样大概率都是0了,0再乘以99还是0public static void main(String[]args){ int mines[][]=new int[10][10]; int r=(int)(Math.random()*99); for(int i=0;i < 10;i++){ for(int j=0;j < 10;j++) System.out.print(mines[i][j]+" "); System.out.println();}}5.根据随机数计算矩阵位置 (10分) [编程填空]这个随机数对应着矩阵的某个位置,我们把它算出来。由于矩阵是10*10,理解起来还是比较容易的,例如14,那么就是第二行,第五列,不理解的话,运行一下第二题的代码。
其实就是mines[1][4]这个位置!
我们要做的,就是取个位,取十位。
补全下面int x和int y的代码public static void main(String[]args){ int mines[][]=new int[10][10]; int r=(int)(Math.random()*99); //x是r的十位 int x= ; //y是r的个位 int y= ; for(int i=0;i < 10;i++){ for(int j=0;j < 10;j++) System.out.print(mines[i][j]+" "); System.out.println(); }} -
完成算法1.计算提示数字(本题没有自动批改) [编程(无自动批改)]现在矩阵剩下最后一步,也是比较麻烦的一步,计算雷周围的数字,这个数字该怎么算呢?
有多种算法。
第一种,以地雷为主。遍历mines,假如某个元素是9,也就是它是雷,那么它周围8个元素全部加1。整个数组遍历一遍后,所有的数字都算出来了。
这种算法,数字是有可能在每次遍历的时候发生改变的,只要发现这个位置处于雷的周围,那么就要加1.
第二种算法,遍历数组,检查每个元素周围有多少个雷,发现一个雷就加1。
这两种算法没有太大区别。但都涉及到一个问题:二维数组的周围八个元素怎么表示?
只写下标的话,就是这样:i-1,j-1 i-1,j i-1,j+1 i,j-1 i,j i,j+1 i+1,j-1 i+1,j i+1,j+1
如果每个元素都有八个邻居那就好办了,关键边界上的元素没有八个元素,如果直接去访问的话,会有数组下标溢出异常的!也就是java.lang.ArrayIndexOutOfBoundsException
解决办法也有两个
一. 设置判断,每个元素都要判断是否到达四个方向的末尾!
方法一:设置多个if语句判断,这个方法比较直接也容易理解。
方法二:先找到邻居的下标,先不管是不是下标越界,计算它的唯一数字标识,也就是x*列数量+y,算出来的数字如果超过0-99的范围,就不用再访问了,找下一个邻居。
二.直接写,然后让异常出现,然后用try……catch块处理异常。
最后得到的数组类似这样的:
0 0 0 1 9 1 0 0 0 0
0 0 0 1 1 1 1 1 1 0
0 0 0 0 1 1 2 9 1 0
0 0 0 0 1 9 2 1 1 0
0 0 0 0 1 1 1 0 0 0
1 1 0 0 0 0 0 1 2 2
9 1 0 0 0 0 0 1 9 9
0 1 1 1 0 0 0 0 0 0
1 2 9 2 1 0 0 0 0 0
1 9 3 9 1 0 0 0 0 02.抽象 (10分) [编程(无自动批改)]上面的代码是10*10矩阵,并且有9个雷。如果要自由生成矩阵,那么肯定要进行抽象的。
1.10*10要变成a*b,其中a和b都是键盘输入的
2.地雷数量不固定为9,用一个整型变量number代替,也是键盘输入
3.唯一标识数字的计算需要改成x*b+y,其中x和y是数组下标
4.随机数计算坐标,不能再直接取个位和十位了,而是
x=r/b(随机数除以列后的商,去掉小数部分,其实就是处于哪一行)
y=r%b(随机数除以列后的余数,就是处于哪一列)
只是原来a和b都是10,刚好就是取个位和十位。3.埋地雷 (10分) [编程填空]埋地雷就是把对应的位置的值设置为9,这里我们打算放置9个雷,需要一个循环来完成,循环的里面是以下几句
1.生成随机数r
2根据r计算x
3.根据r计算y
4.设置mins [x][y]的值为9
5.放雷数加一,如果没有放完,回到1public static void main(String[]args){ int mines[][]=new int[10][10];for(int i=0;i<9;i++){ int r=(int)(Math.random()*99); int x=r/10; int y=r%10; ;} for(int i=0;i < 10;i++){ for(int j=0;j < 10;j++) System.out.print(mines[i][j]+" "); System.out.println(); }}4.随机数重复的时候 (10分) [编程]如果一切顺利,上一题运行后,你可以得到这样的矩阵,其中9就是雷的位置了:
0 0 0 0 9 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 9 0 0
0 0 0 0 0 9 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 9 9
0 0 0 0 0 0 0 0 0 0
0 0 9 0 0 0 0 0 0 0
0 9 0 9 0 0 0 0 0 0
但是,运行后会发现,会有一定的几率达不到9个雷!这是怎么回事呢?
这是因为随机数r重复生成,例如生成了两个34,那么[3][4]这个位置两次赋值9。最简单的解决办法就是添加一个条件,如果mins [x][y]的值已经是9,那么修改循环变量(这个循环变量现在的意义是埋雷数目),不增加埋雷次数。
修改上一题的代码,修补这个bug