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*列数量+y
    public 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还是0
    public 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 0
    2.抽象 (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.放雷数加一,如果没有放完,回到1
    public 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