最终效果:
目录
1、总体逻辑
扫雷的地图以9x9的大小为例,需要一个二维数组来当地图,并且要在这个地图上进行埋雷、扫雷操作,还要将周围的雷数显示出来,所以仅用一个二维数组是不够的,需要用两个:
- 埋雷地图:雷层,埋在土的下面
- 扫雷地图:土层,铺在雷的上面
- 同时扫雷地图也就是展示出来的地图
在扫雷的过程中:
- 如果扫到雷:游戏结束
- 如果不是雷:显示周围八个格子的雷数
- 如果周围0个雷,那就继续判断周围的周围,直到周围出现雷
2、地图布局
以9x9的地图为例,就需要两个9x9大小的二维数组来作为地图,但是想到在扫雷的过程中会对一个坐标的周围其他八个坐标进行扫描,如果那个坐标是边界上的坐标的话,那扫面周围的坐标的话就需要进行另外的判断,如图:
假如要输入的坐标是(1,1),也就是左上角第一个格子,这时需要判断他的周围格子是否是雷,但是他的上方和左方都已经超过了边界,所以就要对他进行单独的判断来保证坐标不会出现非法的情况,但是这种边界的格子很多,每个都这样判断很麻烦,所以把地图在原来的基础上扩大一圈,只操作原来大小的地图就可以了:
//地图布局
#define ROW 9 //实际的雷区范围
#define COL 9
#define ROWS ROW+2 //实际的数组范围
#define COLS COL+2
//雷数
#define COUNT 10
char mine[ROWS][COLS]; //埋雷地图、雷层
char show[ROWS][COLS]; //扫雷地图、土层
memset(mine, ' ', sizeof(mine)); //初始化,空格表示没有雷
memset(show, '.', sizeof(show)); //初始化,. 表示没有被排查过
3、埋雷
埋雷的操作用生成随机数来实现,要注意的是判断生成的随机数的范围,不能超出了实际雷区大小的范围,还有生成的坐标位置是否已经布置过雷了,用 * 来表示雷:
// 布置雷
void setMap(char mine[ROWS][COLS]) {
int count = COUNT; //总雷数
srand((unsigned)time(NULL)); //设置随机数种子
while (count) {
int x = 1 + rand() % ROW; //范围公式:1+(0--ROW-1)
int y = 1 + rand() % COL;
if (mine[x][y] == ' ') { //没有布置雷的地方
mine[x][y] = '*';
count--;
}
}
}
4、打印地图
打印出的地图是不知道雷的位置的图,就是显示的是”土层“,具体的雷层不会显示出来,为了方便用户输入坐标,在地图的最上面和最左边打印出列号和行号:
// 打印界面
void print(char show[ROWS][COLS]) {
// 这一部分是打印行数
printf(" ");
for (int i = 1; i <= ROW; i++)
printf(" %d", i);
printf("\n ");
for (int i = 1; i <= ROW; i++)
printf("--");
printf("\n");
//这一部分才是打印列数地图
for (int i = 1; i <= ROW; i++) {
printf("%d|", i);
for (int j = 1; j <= COL; j++) {
printf("%c ", show[i][j]);
}
printf("\n");
}
}
5、扫雷
扫雷通过用户输入坐标,来判断该坐标是否是雷:
- 是雷:游戏结束、用户被炸死
- 踩到雷的情况下,将每个雷的位置显示到当前的扫雷地图上
- 非雷:得到旁边八个坐标的含雷数
- 如果为0:就继续判断周围的周围的含雷数,递归至碰到雷
- 如果非0:就显示出雷的个数
就是如下图的情况:
另外扫雷的过程中还需要判断是否已经把雷全部扫出来了,就是判断扫雷地图上的 已扫出的 非雷的 数量,如果数量等于了 总格子数 - 雷数,那么游戏就可以结束了,并且将每个雷的位置显示到当前的扫雷地图上。
5.1、将每个雷的位置显示到当前地图上
就直接遍历埋雷地图,把雷的坐标拿到扫雷地图,然后将扫雷地图该坐标的符号改为雷的符号:
// 把展示界面的雷显示出来
void show_mine(char mine[ROWS][COLS], char show[ROWS][COLS]) {
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
if (mine[i][j] == '*')
show[i][j] = mine[i][j];
}
}
}
5.2、计算出当前地图剩余的非雷数
还是遍历,每次从头遍历 扫雷地图上 实际雷区的 已被扫的区域:
// 计算全图空白数
int get_blank_num(char show[ROWS][COLS]) {
int count = 0;
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
if (show[i][j] != '.') //不等于 . 就表示扫过了
count++;
}
}
return count;
}
5.3、计算坐标周围雷的个数
因为9x9的雷区范围实际数组大小是11x11的,所以扫描哪一个坐标的周围其他八个坐标都不用担心坐标非法的问题,直接遍历周围就可以了:
// 计算周围雷的个数
int get_mine_num(char mine[ROWS][COLS],int x,int y) {
int count = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (mine[i][j] == '*')
count++;
}
}
return count;
}
从x-1 和 y-1 开始遍历到 x+1 和 y+1 是因为周围八个坐标就在这个范围内。
5.4、如果周围没有雷显示空白并且向外扩展
如果一个坐标不是雷,且周围也没有雷,那应该往周围的周围扩展,判断周围的周围是否有雷、有多少个雷,达到下图效果:
需要注意的就是,递归的结束条件:
- 如果碰到雷,就返回
- 如果周围的坐标已经被扫过了,那么不再递归进去该坐标
- 坐标也要合法
//判断周围的周围
void sweep2(char show[ROWS][COLS],char mine[ROWS][COLS], int x, int y) {
if (mine[x][y] == '*') return; // 是雷就返回
if (x > 0 && x <= ROW && y > 0 && y <= COL) { // 坐标合法
int mine_num = get_mine_num(mine, x, y); //得到周围雷数
if (mine_num == 0) { //如果周围没有雷,那就要显示出周围的空白,用递归,要判断周围的周围坐标是否合法、是否已经被扫过了
show[x][y] = ' ';
if (show[x][y - 1] != ' ' && y - 1 > 0)
sweep2(show, mine, x, y - 1);
if (show[x][y + 1] != ' ' && y + 1 <= COL)
sweep2(show, mine, x, y + 1);
if (show[x - 1][y - 1] != ' ' && x - 1 > 0 && y - 1 > 0)
sweep2(show, mine, x - 1, y - 1);
if (show[x - 1][y] != ' ' && x - 1 > 0)
sweep2(show, mine, x - 1, y);
if (show[x - 1][y + 1] != ' ' && x - 1 > 0 && y + 1 <= COL)
sweep2(show, mine, x - 1, y + 1);
if (show[x + 1][y - 1] != ' ' && x + 1 <= ROW && y - 1 > 0)
sweep2(show, mine, x + 1, y - 1);
if (show[x + 1][y] != ' ' && x + 1 <= ROW)
sweep2(show, mine, x + 1, y);
if (show[x + 1][y + 1] != ' ' && x + 1 <= ROW && y + 1 <= COL)
sweep2(show, mine, x + 1, y + 1);
}
else {
show[x][y] = mine_num + '0';
}
}
}
6、总体
整个扫雷的具体功能就是这些,有些功能并没有讲究代码的美观,交互和逻辑为了方便也就放在一起了,以下是全部代码:
主程序文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"game_head.h"
// 菜单函数
void menu() {
printf("***********************\n");
printf("****** 1、play ******\n");
printf("****** 0、exit ******\n");
printf("***********************\n");
}
int main(){
system("title 扫雷2.0");
int chose = 0;
do {
menu();
printf("请输入:>");
scanf("%d", &chose);
switch (chose) {
case 1:
//进入游戏
system("cls");
game();
break;
case 0:
printf("退出游戏!\n");
return 0;
default:
printf("输入错误!\n");
}
} while (chose);
return 0;
}
程序头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>
//地图布局
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//雷数
#define COUNT 10
//声明
void game();
程序功能文件
#include"game_head.h"
// 打印界面
void print(char show[ROWS][COLS]) {
// 这一部分是打印行数
printf(" ");
for (int i = 1; i <= ROW; i++)
printf(" %d", i);
printf("\n ");
for (int i = 1; i <= ROW; i++)
printf("--");
printf("\n");
//这一部分才是打印列数地图
for (int i = 1; i <= ROW; i++) {
printf("%d|", i);
for (int j = 1; j <= COL; j++) {
printf("%c ", show[i][j]);
}
printf("\n");
}
}
// 布置雷
void setMap(char mine[ROWS][COLS]) {
int count = COUNT; //总雷数
srand((unsigned)time(NULL));
while (count) {
int x = 1 + rand() % ROW;//1+(0--ROW-1)
int y = 1 + rand() % COL;
if (mine[x][y] == ' ') {
mine[x][y] = '*';
count--;
}
}
}
// 计算周围雷的个数
int get_mine_num(char mine[ROWS][COLS],int x,int y) {
int count = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (mine[i][j] == '*')
count++;
}
}
return count;
}
// 计算全图空白数
int get_blank_num(char show[ROWS][COLS]) {
int count = 0;
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
if (show[i][j] != '.')
count++;
}
}
return count;
}
//扫雷具体
void sweep2(char show[ROWS][COLS],char mine[ROWS][COLS], int x, int y) {
if (mine[x][y] == '*') return; // 是雷就返回
if (x > 0 && x <= ROW && y > 0 && y <= COL) { // 坐标合法
int mine_num = get_mine_num(mine, x, y); //得到周围雷数
if (mine_num == 0) { //如果周围没有雷,那就要显示出周围的空白,用递归,要判断周围的周围坐标是否合法、是否已经被扫过了
show[x][y] = ' ';
if (show[x][y - 1] != ' ' && y - 1 > 0)
sweep2(show, mine, x, y - 1);
if (show[x][y + 1] != ' ' && y + 1 <= COL)
sweep2(show, mine, x, y + 1);
if (show[x - 1][y - 1] != ' ' && x - 1 > 0 && y - 1 > 0)
sweep2(show, mine, x - 1, y - 1);
if (show[x - 1][y] != ' ' && x - 1 > 0)
sweep2(show, mine, x - 1, y);
if (show[x - 1][y + 1] != ' ' && x - 1 > 0 && y + 1 <= COL)
sweep2(show, mine, x - 1, y + 1);
if (show[x + 1][y - 1] != ' ' && x + 1 <= ROW && y - 1 > 0)
sweep2(show, mine, x + 1, y - 1);
if (show[x + 1][y] != ' ' && x + 1 <= ROW)
sweep2(show, mine, x + 1, y);
if (show[x + 1][y + 1] != ' ' && x + 1 <= ROW && y + 1 <= COL)
sweep2(show, mine, x + 1, y + 1);
}
else {
show[x][y] = mine_num + '0';
}
}
}
// 把展示界面的雷显示出来
void show_mine(char mine[ROWS][COLS], char show[ROWS][COLS]) {
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
if (mine[i][j] == '*')
show[i][j] = mine[i][j];
}
}
}
// 扫雷
void sweep(char show[ROWS][COLS],char mine[ROWS][COLS]) {
int x = 0;
int y = 0;
int blank_num = 0;
while (blank_num != ROW * COL - COUNT) {
print(show);
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x > 0 && x <=ROW && y > 0 && y <= COL) {
//判断是不是雷
if (mine[x][y] == '*') {
system("cls");
show_mine(mine, show);
printf(" 你输入的坐标:(%d,%d)\n", x, y);
print(show);
printf("很遗憾,你挂了!\n");
return;
}
else {
//判断周围的雷
sweep2(show, mine, x, y);
system("cls");
printf(" 你输入的坐标:(%d,%d)\n", x, y);
}
blank_num = get_blank_num(show);
}
else {
system("cls");
printf(" (%d,%d)坐标非法!\n",x,y);
}
}
if (blank_num == ROW * COL - COUNT) { // 扫完所有的雷了
show_mine(mine, show);
system("cls");
print(show);
printf("恭喜你,挑战成功!\n");
}
}
// 进入游戏
void game() {
//定义两个数组,一个是展示的界面,一个是存雷的界面
char mine[ROWS][COLS]; // 存雷
char show[ROWS][COLS]; //展示
memset(mine, ' ', sizeof(mine)); //初始化,空格表示没有雷
memset(show, '.', sizeof(show)); //初始化,. 表示没有被排查过
//布置雷
setMap(mine);
//扫雷
sweep(show, mine);
}