文章目录
1.扫雷游戏的分析和设计
1.1扫雷游戏的功能说明和展示
- 使用控制台实现经典的扫雷游戏
- 游戏可以通过菜单实现继续玩或者退出游戏
- 扫雷的棋盘是9*9的格子
- 默认随机布置10个雷
- 可以排查雷
-
如果位置不是雷,就显示周围有几个雷
-
如果位置是雷,就炸死游戏结束
-
把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
游戏界面如下:
1.2游戏的分析和设计
在这里,我们将设计游戏的代码分别用三个文件来进行管理,以便理清逻辑且不会显得很杂乱,帮助我们更好的编写代码。
- 1)test.c 源文件-------用来专门测试游戏逻辑
- 2)game.c源文件------用来编写实现游戏中函数中的代码
- 3)game.h头文件------用来对上面两个源文件所设计的数据类型和游戏函数进行声明
如图添加好这三个文件,接下来我们就开始对游戏进行分析。
1.2.1数组的选择
扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。
因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们首先想到的就是创建⼀个9 * 9的数组来存放信息,也就是一个9行9列的二维数组来存储信息。
如图所示:
如上图,我们要将设置的信息存储进这个二维数组里面,那如果其中某个位置布置雷,我们就存放1,没有布置雷就存放0.
假设我们正在进行游戏,我们排查了(2,4)这个坐标,我们需要知道周围的⼀圈8个黄色位置,雷的个数有多少,统计的雷的个数为2.
假设我们排查(8,6)这个坐标时,我们访问周围的一圈8个蓝色位置,统计周围雷的个数时,最下面的三个坐标所对应的数组就会越界,从而导致程序不能运行。
为了防止越界,我们在设计的时候,给数组扩大一圈,当然,雷还是布置在中间的9 * 9的坐标上,周围一圈不去布置雷就行,这样就解决了越界的问题。所以我们将存放数据的数组创建成11 * 11的二维数组是比较合适。
1.2.2不同数组的作用
我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某一个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。
所以雷和非雷的信息不要使用数字,使用某些字符,这样就避免雷的信息和雷的个数信息就可能或产生混淆和打印上的困难,但是这样做棋盘上有雷和非雷的信息,还有排查出的雷的个数信息,就比较混杂,不够方便。
这里我们采用另外⼀种方案, 我们专门给一个棋盘(对应一个数组mine)存放布置好的雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排查出的雷的信息。这样就互不干扰了。
把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时为了保持神秘, show数组开始时初始化为字符 ‘*’ ,为了保持两个数组的类型⼀致,可以使用同⼀套函数处理,mine数组最开始也初始化为字符’0’,布置雷改成’1’。 如下图:
对应的数组应该是:
1.char mine[11][11] = {0};//⽤来存放布置好的雷的信息
2.char show[11][11] = {0};//⽤来存放排查出的雷的个数信息
此处的数组中的[11][11],可以用[ROW][COL]来代替,我们在头文件 game.h 中将ROW、COL定义为数字9的符号,ROWS、COLS定义为数字11的符号,这样下来,当我们需要更改数字的时候,就可以直接更改这个定义后的数字,不用去代码中一个个的改,更加方便。
头文件 game.h 中:
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
2.扫雷游戏代码的实现
2.1 打印可以进行选择的游戏菜单
首先,我们在头文件中输入需要包含的库文件
game.h
#include<stdio.h>
然后在game.c和test.c文件中输入我们自定义的头文件
#include"game.h"//自定义的头文件名需要用""来表明
输入库文件后,我们就可以在test.c中来梳理扫雷游戏运行的逻辑顺序。
我们先设计菜单来进行游戏的开始和结束,并设定一个游戏函数game(),当选择开始游戏时,game()会运行。
当然,在进行游戏时,我们不会只玩一次,而是希望它可以重复地玩,因此我们就需要在代码中加入do-while循环,用1来表示开始游戏,用0来表示结束游戏,当输入1时,游戏正常运行,可以一直游玩,当输入0后,do-while循环接收到0后就会终止循环,游戏结束。
因为在do-while循环中,非0为真,表示循环可以一直进行,0为假,表示循环直接终止。
test.c
#include "game.h"
void menu() //打印菜单
{
printf("****************************\n");
printf("******* 1.开始游戏 ******\n");
printf("******* 0.结束游戏 ******\n");
printf("****************************\n");
}
int main()
{
int input = 0;
do
{
menu();//菜单函数
printf("请输入你的选择:>\n");
scanf("%d", &input);//选择1或者0来开始或结束游戏
switch (input)//根据菜单中的选择数字来判断后面要运行的程序
{
case 1:
game();//进行扫雷游戏的函数
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("数字选择错误,请重新选择");
break;
}
} while (input);//当input为0时,循环终止
return 0;
}
当我们运行代码后,就会看到如下界面
我们设置好菜单后,接下来就是编译扫雷游戏的代码。
2.2编写游戏代码
2.2.1 创建数组
在game()函数中创建两个字符数组,一个mine[][]数组,用来存放布置好的雷,另一个show[][]数组,用来给玩家排查雷。
mine数组存放雷,我们不需要给玩家看到,show数组展示排查出的雷的信息,需要展示给玩家看,因为后续我们在打印的 时候,就只用打印一个show数组就可以了
test.c
char mine[ROWS][COLS];//存放布置好的雷
char show[ROWS][COLS];//存放排查出的雷的信息
2.2.2 初始化数组
当我们创建好数组后,需要对数组里的元素进行初始化,按照我们之前的分析,mine数组是用来存放雷的,我们先将mine数组全部初始化为 ‘0’ ,而show数组是用来向玩家展示的,我们就将show数组全部初始化为 '*‘ ,表示带 * 的位置的雷是未知的。
由于两个数组中存放的字符不是一样的,因此为了简便,就可以将’0’ 、'*'分别传参。
test.c
Initialboard(mine,ROWS,COLS,'0');
Initialboard(show,ROWS,COLS,'*');
我们先写出初始化数组的函数,我们在到头文件中对初始化数组的函数进行声明
game.h
void Initialboard(char board[ROWS][COLS],int rows,int cols,char set);
其中的board[][]数组就是形参,mine、show为实参,声明后就可以直接传参,不用再对两个函数分别初始化。
我们在game.c中编写游戏的运行代码
game.c
void Initialboard(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;//将数组中每个元素设置为传过来的参数'*'或者'0'
}
}
2.2.3 打印数组
对mine数组、show数组初始化后,我们就可以开始编写打印数组的代码了。
我们先在test.c的game()函数中,先写入我们要打印数组的函数。
在上面的初始化数组中,我们初始化的数组为11 * 11的,而我们打印出来的数组是给玩家看的,就只用打印9 * 9的就可以了
test.c
Displayboard(mine, ROW, COL);//不用打印
Displayboard(show, ROW, COL);
game.h中声明
game.h
void Displayboard(char board[ROWS][COLS], int row,int col);
game.c
void Displayboard(char board[ROWS][COLS], int row, int col)
{
printf("-------扫雷游戏--------\n");
for (int i = 0; i <= row; i++)//打印列的坐标0~9
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)//打印行的坐标0~9
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//打印放置'0'或者'*'的数组
}
printf("\n");
}
printf("-------扫雷游戏-------\n");
}
运行结果如下:
为了区分坐标,我们在行和列前都加了数字,在输入坐标的时候就可以很直观地看到我们要选择的位置的坐标。
完成了打印数组的代码后,接下来我们就要开始在数组中布置雷。
2.2.4 布置雷
因为是9*9的数组,于是我们就设计一个函数setmine()用来编写我们布置雷的代码。
游戏中需要设置十个雷,再利用随机数生成这十个雷的坐标x、y,随机数的生成需要用到srand()函数和rand()函数,它们的头文件是<stdlib.h>、<time.h>;然后把放有雷的坐标设置为’1’ (也就是生成的随机数坐标),没有雷的坐标还是为’0’。
(1)test.c文件的game()函数中写入setmine()函数,并且在我们的主函数中加入srand()函数
test.c
setmine(mine, ROW, COL);
再到game.h中进行对setmine函数进行声明,此处我们也可以将雷的个数用符号来定义,更便于我们修改数字。
game.h
#define COUNT_MINE 10//设置10个雷
void setmine(char board[ROWS][COLS],int row,int col);
最后到game.c文件中编写布置雷的代码
game.c
void setmine(char board[ROWS][COLS], int row, int col)
{
int cnt = COUNT_MINE;//雷的个数
while (cnt)
{
int x = rand() % row + 1;//设置0~9的随机数
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';//将随机生成的坐标设置为带有'1'的雷
cnt--;//每设置一个雷,雷的个数都会依次递减
}
}
}
当我们布置好雷后,还要在布置有雷的数组中去排查雷。
2.2.5 排查雷
在我们根据随机数生成的坐标位置设置好雷后,当我们选择show数组中的任意位置,要先判断该坐标是否在数组9*9范围内并且该坐标是否已经排查过了;
除此之外,它还要统计周围的八个位置中雷的个数,并将雷的个数展示在我们选择的坐标上。此处我们就用statistic()函数来统计mine数组中所选择的坐标周围雷的个数。
因为我们定义的是字符数组,存放雷的字符为 ‘1’ , ‘1’ 的ASCII码为49,0~9的ASCII码为48 ~58,不能准确计算出该坐标周围的雷的个数;
所以我们要计算雷的个数就相当于要将这八个位置的字符相加,但字符’1’对应49,于是我们想到 ‘1’ - ‘0’ =49-48=1,那么八个字符相加再减去八个字符0等于是不是八个位置雷的个数呢?答案是肯定的,我们就可以用这种方法来统计雷的个数。
(1)在test.c文件中写入Findmine()函数来编写排查雷的代码。
test.c
Findmine(mine, show, ROW, COL);
(2)到game.h头文件中声明该函数
game.h
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col);
(3)geme.c文件中编写代码
game.c
//统计选择的坐标周围的八个坐标雷的个数
int stasticmine(char mine[ROWS][COLS], int x,int y)//该函数需要返回,选用了int类型
{
return(mine[x][y - 1] + mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1]-8*'0');
}
//排查雷的函数代码
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;//判断排查的次数,因为71个地方没有雷,当超过71时,游戏就取得胜利
while(win< ROW * COL - COUNT_MINE)//win<9*9-10=71
{
printf("请输入你要排查雷的坐标:>\n");
scanf("%d %d", &x, &y);
if (x <= row && x >= 1 && y <= col && y >= 1)//判断所选坐标是否在9*9数组的范围内
{
if (show[x][y]!= '*')//判断输入的坐标是否已经排查过了,防止反复排查
{
printf("该坐标已经排查过了,请重新输入坐标\n");
}
else if (mine[x][y] == '1')//判断所输入的坐标是否是雷
{
printf("boom!!!排查失败!你被炸死了!!\n");
printf("\n");
printf("\n");
Displayboard(mine, ROW, COL);
printf("\n");
printf("\n");
break;
}
else
{
int cnt=stasticmine(mine,x,y);//统计坐标周围的雷的个数
show[x][y] = cnt + '0';
Displayboard(show, ROW, COL);//将排查了的数组展示出来,继续挑选坐标排查
win++;
}
}
else
{
printf("坐标输入不合法,请重新输入\n");
}
}
if (win == ROW * COL - COUNT_MINE)
{
printf("恭喜你,排雷成功,你获得称号:排雷小霸王\n");
}
}
2.3扫雷游戏完整代码
写到这里,我们的扫雷游戏基本完成了,现在展示完整的扫雷游戏代码。
game.h文件
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define COUNT_MINE 10//设置10个雷
#define ROW 9//行数为9
#define COL 9//列数为9
#define ROWS ROW+2
#define COLS COL+2
void Initialboard(char board[ROWS][COLS],int rows,int cols,char set);
void Displayboard(char board[ROWS][COLS], int row,int col);
void setmine(char board[ROWS][COLS],int row,int col);
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col);
test.c文件
#include "game.h"
void menu()
{
printf("****************************\n");
printf("******* 1.开始游戏 ******\n");
printf("******* 0.结束游戏 ******\n");
printf("****************************\n");
}
void game()
{
//设置放置雷的数组 9*9
char mine[ROWS][COLS];//存放布置的雷
char show[ROWS][COLS];//存放排查出的雷的信息
//将数组初始化
Initialboard(mine,ROWS,COLS,'0');
Initialboard(show,ROWS,COLS,'*');
//打印布置的棋盘
//Displayboard(mine, ROW, COL);
//Displayboard(show, ROW, COL);
//布置雷的坐标
setmine(mine, ROW, COL);
//将放有雷的棋盘以'*'打印
Displayboard(show, ROW, COL);
//Displayboard(mine, ROW, COL);
//排查雷
Findmine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//生成随机数
do
{
menu();//菜单
printf("请输入你的选择:>\n");
scanf("%d", &input);
switch (input)//用来判断是否进行游戏
{
case 1:
printf("游戏开始,祝你游戏愉快\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("数字选择错误,请重新选择");
break;
}
} while (input);
return 0;
}
geme.c文件
#include "game.h"
#include<stdio.h>
void Initialboard(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;//将数组中每个元素设置为传过来的参数'*'或者'0'
}
}
}
void Displayboard(char board[ROWS][COLS], int row, int col)
{
printf("-------扫雷游戏--------\n");
for (int i = 0; i <= row; i++)//打印列的坐标0~9
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)//打印行的坐标0~9
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);//打印放置'0'或者'*'的数组
}
printf("\n");
}
printf("-------扫雷游戏-------\n");
}
void setmine(char board[ROWS][COLS], int row, int col)
{
int cnt = COUNT_MINE;
while (cnt)
{
int x = rand() % row + 1;//设置0~9的随机数
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';//将随机生成的坐标设置为带有'1'的雷
cnt--;
}
}
}
int stasticmine(char mine[ROWS][COLS], int x,int y)
{
return(mine[x][y - 1] + mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1]-8*'0');
}
void Findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while(win< ROW * COL - COUNT_MINE)
{
printf("请输入你要排查雷的坐标:>\n");
scanf("%d %d", &x, &y);
if (x <= row && x >= 1 && y <= col && y >= 1)
{
if (show[x][y]!= '*')
{
printf("该坐标已经排查过了,请重新输入坐标\n");
}
else if (mine[x][y] == '1')
{
printf("boom!!!排查失败!你被炸死了!!\n");
printf("\n");
printf("\n");
Displayboard(mine, ROW, COL);
printf("\n");
printf("\n");
break;
}
else
{
int cnt=stasticmine(mine,x,y);//统计坐标周围的雷的个数
show[x][y] = cnt + '0';
Displayboard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标输入不合法,请重新输入\n");
}
}
if (win == ROW * COL - COUNT_MINE)
{
printf("恭喜你,排雷成功,你获得称号:排雷小霸王\n");
}
}
看到这,想必大概已经学会了一些扫雷代码的技巧,不过该代码还是有一些不足,以下的扩展方面还没有做到。
游戏扩展:
- 是否可以选择游戏难度
◦ 简单 99 棋盘,10个雷
◦ 中等 1616棋盘,40个雷
◦ 困难 30*16棋盘,99个雷 - 如果排查位置不是雷,周围也没有雷,可以展开周围的一片
- 是否可以标记雷
- 是否可以加上排雷的时间显示