扫雷游戏分析和设计
一. 扫雷游戏的可实现功能
- 使⽤控制台实现经典的扫雷游戏
- 游戏可以通过菜单实现继续玩或者退出游戏
- 扫雷的棋盘是9*9的格⼦
- 默认随机布置10个雷
- 通过输入坐标来排查雷
- 如果位置不是雷,就显⽰周围有⼏个雷
- 如果位置是雷,就炸死游戏结束
- 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
二.根据功能设计程序实现
-
设计菜单
函数代码:
//打印菜单
void menu() {
printf("********************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("********************\n");
printf("choose-->");
}
通过输入数字来控制是否进入游戏,输入的数字有三种情况:0,1,其他
- 输入为0时,退出游戏
- 输入为1时,进行游戏,游戏结束后,重新返回菜单开始游戏
- 输入为其他时,需要重新选择是否开始游戏
我们可以设置循环,因为只有1需要退出游戏,其他都会重复进行游戏,所以我们可以将a的值作为循环的条件,来判断是否循环
主函数代码:
int a;
do {
menu();
scanf("%d", &a);//输入数字来控制
switch (a) {//判断
case 1:
printf("Welcome to mine!\n");
game(); //用函数概括游戏
break;
case 0:printf("退出游戏,游戏结束\n"); break;
default:printf("输入错误,请重新输入\n"); break;
}
} while (a);//当a的值为0时结束循环
-
初始化棋盘
设计棋盘有两步:
- 棋盘的内部(布置雷)
- 棋盘的外部(存放“*”,还有排查后的周围雷数)
很明显我们要用数组来存放棋盘信息,因为内部和外部存放的信息类型不同所以我们可以创造两个二维数组分别存放
同时,在创建数组时我们需要考虑数组大小,因为我们想要的是9*9类型的棋盘,所以我们一开始会想到直接创建的9*9的长度,但根据扫雷如果位置不是雷,就显⽰周围有⼏个雷的游戏规则,我们需要统计坐标周围8个位置雷的数目,如果数组是9*9长度时,当我们想排除的坐标在棋盘边界时,会出现越界访问的情况:
为了不将这个问题复杂化,我们干脆直接将数组扩大,这样就不会出现数组越界的情况了
这里不直接用数字,是方便在代码编译的过程中如果需要改动,只用改动定义一处的数字,不用全文进行改动
#define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 char mine[ROWS][COLS] = { 0 };//内部数组存放雷的信息 char show[ROWS][COLS] = { 0 };//外部数组存放*和雷的数量
接下来我们要分别初始化两个数组,来布置棋盘
- mine数组最初是没有雷的,我们用0来代替
- show数组是需要打印出来的数组,一开始存放的是*
所以我们可以设计一个函数来初始化两个数组,一个存放0,一个存放*
在存放数据时我们只需要存放在9*9的数组里面就行
函数代码如下:
这里用字符c来改变初始化存放的数据//棋盘初始化 void Setboard(char board[ROWS][COLS], int row, int col,char c) { for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { board[i][j] = c; } } } //主函数中进行引用 Setboard(mine, ROW, COL,'0'); Setboard(show, ROW, COL, '*');
-
布置雷
因为每次游戏的布置的雷是随机的,所以我们要用rand()库函数来保证随机性,我们要布置10个雷所以要循环10次,这里我们用count来计数,同时我们要保证布置的雷不重复,加个if语句
函数代码如下:
//布置雷
void Setmine(char mine[ROWS][COLS], int row, int col) {
srand((unsigned int)time(NULL));
int count = NUM;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0') {
mine[x][y] = '1';
count--;
}
}
}
-
打印棋盘
我们创建个函数打印棋盘,在打印棋盘时我们需要标注位置信息,方便玩家输入坐标
函数代码如下:
//打印棋盘
void Disboard(char board[ROWS][COLS], int row, int col) {
printf("****** 扫雷 *******\n");
for (int i = 0; i < 10; 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 ", board[i][j]);
}
printf("\n");
}
}
我们也可以打印mine数组来监测是否存放了10个雷
-
排查雷
当我们输入一个坐标时,有以下情况需要判断:
- 坐标是否有误(如:超出棋盘范围)
- 坐标是否是雷
- 坐标是否被排查过
第一个条件我们可以通过这个坐标是否在1~9范围内来判断
第二个条件我们可以通过这个坐标在mine数组中是否为1来判断
第三个条件我们可以通过这个坐标在show数组中是否为*来判断(因为排查过的坐标会变成数字,存放在数组show中)
运用循环嵌套一步步判断
函数代码:
void Exmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int count;
int x, y;
if (x > 0 && x <= row && y > 0 && y <= col) {
if (mine[x][y] != '1') {
if (show[x][y] == '*') {
//数周围有几个雷,放到show数组里
}
else printf("已排除,请重新输入坐标\n");
}
else printf("What's a pity! You are dead!\n");
}
else printf("输入有误,请输入棋盘范围内坐标\n");
}
-
周围有几个雷
从这张图我们可知当我们选中的坐标为(3,6)时,此位置没有雷,所以我们要数出周围雷的情况,由此图我们可以很容易知道(3,6)坐标周围的坐标
现在我们将(3,6)换成参数(x,y),其周围数怎么表示呢?
我们可以用(x,y)来表示:如(x+1,y-1)
我们发现(x,y)周围的坐标都在x+1~x-1和y+1~y-1范围内,所以我们可以用循环来记录周围雷的个数,创建一个计数器count来存放数出雷的个数,通过判断这个坐标在mine数组中是否为1来计数,为1就+1,为0就不计数,最后返回count数出的数量
函数代码:
char statistic(char mine[ROWS][COLS], int x, int y) {
int number = 0;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (mine[i][j] == '1') {
number++;
}
}
}
return number +'0';//因为show数组是char类型所以返回要加‘0’
}
因为玩家需要不断的输入坐标来排雷,所以我们可以用while循环,当玩家排完所有雷时我们需要结束游戏,跳出循环,我们可以用一个计数器(count)统计玩家排了多少次雷,因为总共有row*col,81个坐标,10个雷,所以当count大于等于71时游戏结束,玩家获胜
完善函数代码:
void Exmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int count;
int x, y;
while (count < row * col - 10) {
if (x > 0 && x <= row && y > 0 && y <= col) {
if (mine[x][y] != '1') {
if (show[x][y] == '*') {
show[x][y] = statistic(mine, x, y);//数周围有几个雷,放到show数组里
}
else printf("已排除,请重新输入坐标\n");
}
else printf("What's a pity! You are dead!\n");
}
else printf("输入有误,请输入棋盘范围内坐标\n");
}
if (count[0] >= row * col - NUM) {
Disboard(mine, ROW, COL);
printf("You win!\n");
}
}
三.增加美观性
-
库函数
我们可以通过增加以下库函数来使我们的游戏界面变得美观和用户体验感:
- Sleep(time);//包含在Windows.h的头文件中,可使界面暂停time毫秒
- system("cls");//包含在stdlib.h的头文件中,可清楚界面上的信息
-
自定义函数
创建函数,打印游戏规则,使用户更易上手:
//打印游戏说明
void rule() {
char c;
printf("欢迎来到扫雷游戏,在游戏开始前,请玩家先阅读游戏规则:\n");
Sleep(2000);
printf("本游戏仅有简单模式,总格数为9*9,共有10个雷\n");
Sleep(2000);
printf("请玩家输入要排除方格的坐标,示例如下:\n");
Sleep(1500);
//打印棋盘
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
for (int i = 1; i < 10; i++) {
printf("%d ", i);
for (int j = 1; j < 10; j++) {
printf("* ");
}
printf("\n");
}
printf("请输入坐标(x,y)排雷:2 1\n\n");
Sleep(2000);
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");
for (int i = 1; i < 10; i++) {
printf("%d ", i);
for (int j = 1; j < 10; j++) {
if (i == 2 && j == 1)printf("1 ");
else printf("* ");
}
printf("\n");
}
printf("1代表坐标(2,1)周围雷的个数为1\n");
Sleep(2000);
printf("阅读完毕,祝您游戏愉快!\n");
Sleep(2000);
printf("回车键来到菜单:");
scanf("%c",&c);//功能:使界面暂停,玩家有时间阅读规则
}
效果图:
-
代码美观
可以创造三个文件,game.h,game.c,main.c
game.h用来存放函数名,game.c用来实现函数功能,main.c用来测试运行游戏
引用game.h头文件就可以使用函数