前言
扫雷游戏想必大家都听说过,或者上手玩过,没有接触过的话,就让我先简单的介绍扫雷的基本玩法:
游戏目标:
尽快找到所有地雷的位置,并标记出来,同时避免点击到地雷。当所有非雷的方块都被打开时,游戏胜利;如果误点到地雷,则游戏失败。
游戏操作:
如果点击到的方块下面没有地雷,方块会显示一个数字或者空白。数字代表该方块周围的 8 个相邻方块中地雷的数量;如果显示为空白,表示周围 8 格内没有地雷;如果不幸点击到有地雷的方块,地雷就会 “爆炸”,游戏结束
介绍完毕之后,现在我们可不可以自己来编写一段程序,来实现简易的扫雷游戏呢?下面我们开始动手实践:我使用的编译器是VS 2022
1.扫雷游戏的实现和分析
1.1.使用控制台实现简易的扫雷游戏,所以我们要打印出棋盘,9*9格式或者13*13格式;
1.2.可以通过菜单实现继续游玩或者退出游戏;
1.3.游戏中的雷的布置是随机的;
1.4.排查雷
现在,先来看看我们要实现的效果:

2.棋盘的实现
本次设计一个9*9的棋盘,在扫雷的过程中,雷的布置和雷的排查,它们的信息都需要被存储,所以我们需要一定的数据结构来存储这些信息,因为是9*9的棋盘,所以首先容易想到的就是创建一个9*9的二维数组来存放信息:

扫雷游戏中肯定有雷与非雷的区别呀,那么,我们该怎么来表示雷与非雷之间的区别呢?这样,我们用字符‘1’来表示雷,字符‘0’来表示非雷。好,雷与非雷之间已经区别开来,但是,我们又遇到一个问题,我们要扫雷呀,怎么实现向扫雷游戏那样点击到的方块下面没有地雷,方块会显示一个数字或者空白呢?并且当显示你所点击的区域范围内存在一个雷时,要与雷区别开来,因为雷是用字符‘1’来表示的,存在1个雷和点击处就是雷,这两者之间无法区别,那么,怎么解决这个问题呢?我们可以创建两个数组,一个用来布置雷,一个用来显示玩家游玩时的棋盘,并且棋盘上用字符‘*’来初始化棋盘,这样问题就解决了。然而,我们怎么统计点击处附近存在多少雷呢?

如上图,当我排查内部时,很容易就统计出了周围存在几个雷,但是,当我排查棋盘的边缘时,该怎么统计呢?有的人就说,直接像内部统计雷那样统计就行了呀!但是,有没有想过你怎么知道在数组范围外,所存储的数据是什么,这可是越界访问了呀。所以,该怎么解决这个问题呢,或者说,怎么让它不越界访问呢?只要9*9范围外的一圈都是数组的就行了呗,也就是创建一个11*11的数组,打印棋盘时就打印9*9范围就可以了:

3.用代码来实现
1.创建一个菜单
菜单的功能有进入游戏和退出游戏两种:
void menu()
{
printf("*********************\n");
printf("**** 1. play *****\n");
printf("**** 0. exit *****\n");
printf("*********************\n");
}
怎么实现选择功能呢,可以使用switch语句,然而为了实现多次游玩的效果,可以再使用do while循环,并且编写一个函数game(),之后所有的函数都从它那里调用:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("数字非法\n");
}
} while (input);
return 0;
}
当输入的数字既不是1,也不是0时,给予提示。
2.初始化棋盘
由之前的分析可知要创建两个字符二维数组,且行列都为11,但是为了之后方便改写棋盘的大小,我们可以用 #define 来定义几个常量
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
为什么还要定义两个常量来表示9呢,因为之后我们打印的棋盘是9*9的格式的,之后想要加大难度,改为50*50的就更方便了。
char boom[ROWS][COLS];
char show[ROWS][COLS];
数组如图所创建。boom数组是用来存放雷的,show数组是用来给玩家展示的
下面开始初始化棋盘,雷棋盘和展示棋盘都要初始化,所以有
InitBoard(boom, ROWS, COLS);
InitBoard(show, ROWS, COLS);
将整个棋盘都要设计成自己想要的样子,所以要传ROWS,COLS作为参数:
void InitBoard(char board[ROWS][COLS], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
board[i][j] = ;
}
}
}
二维数组用双层for循环来初始化,由上文提到,雷棋盘先全用字符‘0’来初始化,之后在用字符‘1’来填充,作为雷;展示棋盘用字符‘*’来初始化。那么问题来了,光标所指的地方该写什么?是字符‘0’,还是字符‘*’,显然无论是哪个都不行,否则要创建两个InitBoard函数,一个用‘0’,一个用‘*’,如何解决这个问题呢?只要在传参时,将自己要初始化的字符作为参数传给函数就可以啦
//初始化棋盘
InitBoard(boom, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
void InitBoard(char board[ROWS][COLS], int r, int c, char character)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
board[i][j] = character;
}
}
}
3.打印棋盘
初始化棋盘完成后,来打印以下棋盘来看看初始化是否成功,使用两层for循环就可以将二维数组中的每个元素打印出来,因为我们要打印的是9*9的棋盘,所以传参时,传ROW,COL:
void PrintBoard(char board[ROWS][COLS], int r, int c)
{
int i = 0;
for (i = 1; i <= r; i++)
{
int j = 0;
for(j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
打印出的结果如下图所示:

(“展示棋盘”和“雷棋盘”,这串字读者可以不用打印出来,这里打印是为了更好的观看)
但是,大家有没有注意到这样打印不方便我们输入坐标来排雷,每次要输入坐标的时候都要自己用肉眼数,太过于麻烦,怎么使它变得容易呢?只要将坐标打印出来即可:

按照图示来编写代码,首先我们要先打印行,0~9一个for循环就可以解决,注意这里打印完毕后,需要换行,避免使棋盘错位:

接着打印列,由图可知,列的标号是先于棋盘打印的,所以代码可以如下编写:
void PrintBoard(char board[ROWS][COLS], int r, int c)
{
printf("------扫雷游戏----------\n");
int i = 0;
for (i = 0; i <= r; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= r; i++)
{
int j = 0;
printf("%d ", i);
for(j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
我们要打印9*9的棋盘,所以下标都从1开始。打印每一行后,记得换行。打印的0和*都是字符,所以使用占位符%c。
4.布置雷
雷是布置在雷棋盘中的,并且是在9*9的范围内布置,所以函数传参为:
// 布置雷
void SetBoom(boom, ROW, COL);
布置雷最重要的就是随机布置,也就是生成随机坐标,坐标随机了,按坐标所布置的雷也就随机了。那么,如何产生随机数,C语言为我们提供了两个函数:rand(),srand()可以生成随机数。但是,随机数生成的范围太大了,而我们只需要生成1~9之间的数字,要解决这个问题,只需要rand()%r + 1,rand()%c+1,二者分别生成横坐标和纵坐标的随机数,若想要了解两者函数,可以根据网址了解:rand - C++ Reference,srand - C++ Reference,了解函数后,我们知道,srand要生成随机数需要一个可以变化的种子,依赖这个种子就可以,那么什么是随时变化的呢,那就是时间。由此,我们需要借助time函数来实现我们想要的功能,想了解time函数,可以访问:time - C++ Reference。
随机数解决完毕后,现在可以定义雷的数目,我们可以像前文那样,使用#define来定义常量
#define EASY_BOOM 10
雷是由‘1’来表示的,并且布置点还没有雷;要设置完我们预定的雷的数量,我们可以使用while循环,所以我们可以如下编写程序:
void SetBoom(char boom[ROWS][COLS], int r, int c)
{
int num = EASY_BOOM;
while (num)
{
int x = rand() % r + 1;
int y = rand() % c + 1;
if (boom[x][y] == '0')
{
boom[x][y] = '1';
num--;
}
}
}
每当布置完一个雷后数量就减1,if的判断,就避免了雷布置在同一块位置。下面,我们将布置雷后的棋盘打印出来:

由两图比较可知,雷的分布是随机的,并且每一局都是如此。
5.扫雷
现在来到最后一个环节---扫雷。
(1).现在来分析扫雷,首先,我们肯定要自己输入坐标,所以要创建2个变量x,y;
其次还要判断我们输入的坐标,是否合法,也就是说是否在大于等于1,小于等于9这个范围 内;即便在这个范围内,还要判断输入的坐标在棋盘上是否是雷的坐标,也就是判断 boom[x][y]是否等于‘1’,等于就炸死了,游戏结束,并且打印出雷棋盘;
(2).倘若不等于‘1’,那就要开始统计该坐标八个方位存在几个雷,为了方便,我们可以创建函数SumBoom来统计,并且将函数的返回值赋值给show[x][y],毕竟我们扫雷的时候看到的棋盘是show数组,如此以来,我们便可知道扫雷函数的参数是哪些了;
(3).要达到反复扫雷的效果,可使用while函数,那么循环的终止条件是什么?我们可以定义一个变量sum来统计我们扫雷正确的次数,也就是在雷有10颗的情况下,sum小于71时,就一直循环,直到达到71,若达到71,可视为游戏胜利。
分析完毕,开始实现:
void ClearBoom(boom, show, ROW, COL)
ClearBoom 函数的实现:
int SumBoom(char boom[ROWS][COLS], int x, int y)
{
return boom[x - 1][y - 1] + boom[x - 1][y] + boom[x - 1][y + 1]
+ boom[x][y - 1] + boom[x][y + 1] + boom[x + 1][y - 1]
+ boom[x + 1][y] + boom[x + 1][y + 1] - 8 * '0';
}
void ClearBoom(char boom[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int sum = 0;
int x = 0;
int y = 0;
while (sum < (r * c - EASY_BOOM))
{
printf("请输入你要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
if (boom[x][y] == '1')
{
printf("很遗憾,你被炸死了,游戏结束\n");
PrintBoard(boom, r, c);
break;
}
else
{
if (show[x][y] == '*')
{
int count = SumBoom(boom, x, y);
show[x][y] = count + '0';
PrintBoard(show, r, c);
sum++;
}
else
{
printf("坐标已经被排查\n");
}
}
}
else
{
printf("坐标非法\n");
}
}
if (sum == (r * c - EASY_BOOM))
{
printf("恭喜你,游戏胜利\n");
PrintBoard(boom, r, c);
}
}
注意:在创建函数SumBoom时,返回的是整数,但是数组所存储的元素都是字符类型,要转换为对应的整型只需要在它基础上减去一个字符‘0’即可;其次,show数组所存放的数据类型都是char类型,所以返回值要加上字符‘0’,来转换成对应的字符型。
4.总结
现在将所有的代码展示出来,供大家参考:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_BOOM 10
void InitBoard(char board[ROWS][COLS], int r, int c, char character)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
board[i][j] = character;
}
}
}
void PrintBoard(char board[ROWS][COLS], int r, int c)
{
printf("------扫雷游戏----------\n");
int i = 0;
for (i = 0; i <= r; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= r; i++)
{
int j = 0;
printf("%d ", i);
for(j = 1; j <= c; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetBoom(char boom[ROWS][COLS], int r, int c)
{
int num = EASY_BOOM;
while (num)
{
int x = rand() % r + 1;
int y = rand() % c + 1;
if (boom[x][y] == '0')
{
boom[x][y] = '1';
num--;
}
}
}
int SumBoom(char boom[ROWS][COLS], int x, int y)
{
return boom[x - 1][y - 1] + boom[x - 1][y] + boom[x - 1][y + 1]
+ boom[x][y - 1] + boom[x][y + 1] + boom[x + 1][y - 1]
+ boom[x + 1][y] + boom[x + 1][y + 1] - 8 * '0';
}
void ClearBoom(char boom[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int sum = 0;
int x = 0;
int y = 0;
while (sum < (r * c - EASY_BOOM))
{
printf("请输入你要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
if (boom[x][y] == '1')
{
printf("很遗憾,你被炸死了,游戏结束\n");
PrintBoard(boom, r, c);
break;
}
else
{
if (show[x][y] == '*')
{
int count = SumBoom(boom, x, y);
show[x][y] = count + '0';
PrintBoard(show, r, c);
sum++;
}
else
{
printf("坐标已经被排查\n");
}
}
}
else
{
printf("坐标非法\n");
}
}
if (sum == (r * c - EASY_BOOM))
{
printf("恭喜你,游戏胜利\n");
PrintBoard(boom, r, c);
}
}
void game()
{
char boom[ROWS][COLS];
char show[ROWS][COLS];
//初始化棋盘
InitBoard(boom, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
PrintBoard(show, ROW, COL);
PrintBoard(boom, ROW, COL);*/
//布置雷
SetBoom(boom, ROW, COL);
//扫雷
ClearBoom(boom, show, ROW, COL);
}
void menu()
{
printf("*********************\n");
printf("**** 1. play *****\n");
printf("**** 0. exit *****\n");
printf("*********************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("数字非法\n");
}
} while (input);
return 0;
}
运行游戏:

其实,这个代码之所以叫做简易扫雷游戏,是因为它没有向网页版的一样,一点开就扫出一大片,要想达到此效果,可以使用递归来实现,篇幅很长感谢你的耐心看完。
5564

被折叠的 条评论
为什么被折叠?



