玩扫雷,还不如写扫雷
当你会写扫雷了
扫雷成功,不是秒秒钟的事
目录
1.用户交互页面
2.扫雷原理及代码实现
1. 用户交互页面
输入1,就表示玩游戏
输入0,就表示不玩游戏
void menu() { printf("**************************\n"); printf("******1.play 0.exit******\n"); printf("**************************\n"); } 主函数 int main() { int n = 0; do { menu(); printf("请输入n:"); scanf("%d", &n); switch (n) { case 1:Minesweeper(); break; case 0:printf("退出\n"); break; default:printf("请重新输入\n"); break; } } while (n); return 0; }
首先程序开始运行就调用 menu 函数,打印开始交互页面,方便用户的选择。
当用户输入 1 就代表玩游戏,那就调用 Minesweeper(扫雷)函数
当用户输入 0 就代表不玩游戏,就退出程序
若用户输入其他值,就提示重新输入
运用 do...while 循环的好处:先执行后判断,上来直接进去,让用户选择是否玩游戏。如果玩游戏玩完了一局,还想继续玩,那就再次输入1,继续玩。如果不想玩了就输入0退出游戏。
2、扫雷
#define ROW 9 #define COL 9 #define ROWS 11 #define COLS 11 #define NUMBER 10 void Minesweeper() { srand((unsigned int)time(NULL)); //布雷区 char arr[ROWS][COLS] = { 0 }; //排雷区 char brr[ROWS][COLS] = { 0 }; //初始化雷区棋盘 Board(arr, ROWS, COLS, '0'); //初始化排雷区棋盘 Board(brr, ROWS, COLS, '*'); //布置雷 Set_mine(arr, ROW, COL); Print(brr, ROW, COL); //开始排雷 Find_Mine(arr, brr, ROW, COL); }
2.1建立棋盘
建立两个棋盘,大小为 11*11 ,其中一个棋盘为放雷区 arr[11][11], 另一个棋盘为排雷区 brr[11][11]
虽然两个棋盘的大小都为11*11,但是在排雷的时候只显示排雷区 9*9 的大小的 棋盘。
为什么只显示排雷区 9*9 的区域了?
只显示排雷区是因为不让用户看到雷区,如果显示雷区那用户都知道雷在哪个地方了
只显示 9*9 的区域因为避免在后面排雷统计周围雷的时候越界访问
注意:雷区最外层(白色区域)不能放雷 ,且也不能排雷,因为我只显示了排雷区中间的9*9棋盘
2.2初始化棋盘
//初始化扫雷棋盘 void Board(char arr[ROWS][COLS], int rows, int cols, char n) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { arr[i][j]= n; } } }
用 arr[ROWS][COLS] 来接收需要初始化的数组
用 rows 来接收行
用 cols 来接收列
用 n 来接收需要初始化成的什么样元素(例如n=‘*’,就把数组中所有的区域都初始化为‘*’)
首先将雷区都初始化为字符‘0’
在将排雷区都初始化为字符‘*’
2.3布置雷
//布置雷 void Set_mine(char arr[ROWS][COLS], int row, int col) { int a = 0; while (a < NUMBER) { int i = rand() % row + 1; int j = rand() % col + 1; if (arr[i][j] != '1') { arr[i][j] = '1'; a++; } } }
将雷区初始化后,就需要开始布置雷了
用 arr[ROWS][COLS] 来接收需要布雷的数组
用 row 来接收行
用 col 来接收列
注:传进来的行和列为分别 9,因为雷只能布置在 9*9 的区域 ,为了避免后面统计周围雷的个数越界
布置 NUMBER 个雷,就让 a=0,循环 a < NUMBER 次,每次布置成功一次雷就 a++,rand() %row
表示获取随机值的范围是 [0,row),rand() % row + 1 表示获取的随机值的范围是 [1,row]
行:1~row
列:1~col
为什么随机值最小为 1?
因为雷区的最外层如果布雷了,在统计周围雷的个数的时候就容易造成越界访问
在使用 rand() 函数之前需要用到 srand((unsigned int)time(NULL)),为了防止随机值每次重复,就用时间戳来初始化
2.4 打印扫雷的棋盘
//打印扫雷棋盘 void Print(char arr[ROWS][COLS], int row, int col) { printf("***********打印棋盘*************\n"); for (int i = 0; i <= row ; i++) { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i); for (int j = 1; j <= col; j++) { printf("%c ", arr[i][j]); } printf("\n"); } }
为了美化和提示的效果上来直接打印:**********打印棋盘**********
为了方便用户输入需要下的位置:打印行号和列标
列标直接用一个循环打印,为什么从0开始 因为第一列打印的是行号
行号每次在打印扫雷区一行棋盘之前,就打印一个行号
4.5 开始排雷
//开始排雷 void Find_Mine(char arr[ROWS][COLS], char brr[ROWS][COLS], int row, int col) { while (1) { int a = 0; int b = 0; printf("请输入你要排雷的下标:"); scanf("%d%d", &a, &b); if (a > 0 && a < 10 && b > 0 && b < 10) { if (arr[a][b] == '1') { printf("不好意思,你踩雷了\n"); Print(arr, ROW, COL); break; } else { int count = Mine_Num(arr, a, b); if (count == 0) { brr[a][b] = ' '; Spread(arr, brr, a, b); Print(brr, ROW, COL); } else { brr[a][b] = count + '0'; Print(brr, ROW, COL); } } } else { printf("请重新输入:\n"); } if (success(brr, ROW, COL)) { printf("恭喜你排雷成功\n"); Print(arr, ROW, COL); break; } } }
排雷整体思路:
排雷不可能只排一次,要排多次,并且要把所有不是雷的位置排查出来,最后雷区只剩下雷的位置没有排才算成功。所以输入排雷的下标也就需要输入多次,我们就用循环。
输入排雷下标之后我们需要判断输入的下标是否在打印的排雷区范围之内(9*9)
如果不是在范围之内就需要重新输入,如果在范围之类就需要判断这个位置是不是雷,如果是雷就游戏失败。如果不是雷就把周围雷的个数赋值到对应的排雷区位置上打印,如果周围没有雷就显示空格并且把它周围不是雷的区域展开打印。然后判断剩下的没有排的排雷区的位置个数是否与雷的个数相同如果是就证明排雷成功,否则就继续排雷。
统计周围雷的个数:
//周围雷的个数 int Mine_Num(char arr[ROWS][COLS], int a, int b) { return arr[a - 1][b - 1] + arr[a - 1][b] + arr[a - 1][b + 1] + arr[a][b - 1] + arr[a][b + 1] + arr[a + 1][b - 1] + arr[a + 1][b] + arr[a + 1][b + 1] - 8 * '0'; }
把雷区 arr[a][b] ,周围的坐标里面对应的值加起来在减去 8*‘0’,为什么要减去 8*‘0’,因为雷区里面不是雷放的是字符 ‘0’,雷放的是字符 ‘1’,所以把它周围的字符加起来然后减去 8*‘0’,就变成了数字,(周围有8个坐标就是8*‘0’)。
如果返回的是 0,就表示周围没有雷,就去依次判断周围八个坐标的周围有没有雷,一直递归直到找到雷为止,并且不能越界
如果返回的不是 0,返回的是其他数字假设用字母 a 来表示那个数字,就表示周围有a个雷,就不展开周围的坐标,并把排的那个下标的位置赋值为 a+‘0’(就表示把那个数字又转换成了字符),并打印在那个位置。
展开周围没有雷的位置:
//展开周围没有的雷 void Spread(char arr[ROWS][COLS], char brr[ROWS][COLS], int x, int y) { if (x >= 1 && x <= ROW && y >= 1 && y <= COL) { for (int around_x = -1; around_x <= 1; around_x++) { for (int around_y = -1; around_y <= 1; around_y++) { if (arr[x + around_x][y + around_y] == '0') { int count = Mine_Num(arr, x + around_x, y + around_y); if (count == 0) { if (brr[x + around_x][y + around_y] == '*') { brr[x + around_x][y + around_y] = ' '; Spread(arr, brr, x + around_x, y + around_y); } } else { brr[x + around_x][y + around_y] = count + '0'; } } } } }
因为我们展开周围的坐标,要用的递归,所以可能造成越界访问,我们进入这个函数的第一步就是判断坐标是否在区域内,如果周围坐标的周围也为空就把 ‘ ’ 赋值给对应的扫雷区位置
为什么要把棋盘设置为11*11?
如果我们排查的坐标为(1,1),我们统计周围的坐标时就不会越界访问,因为我们雷区的棋盘是 11*11,我们布置的雷在 9*9 棋盘内。
判断成功:
//判断成功 int success(char brr[ROWS][COLS], int row, int col) { int count = 0; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { if (brr[i][j] == '*') { count++; } } } if (count == NUMBER) { return 1; } return 0; }
把扫雷区显示在屏幕上的位置遍历一遍,如果 ‘*’ 等于雷的个数,就说明扫雷成功。
注:每排查一个坐标它的值都会由 ‘*’ 改变为它周围雷的个数。