大家好,今天小编带大家来设计一款扫雷游戏,话不多说咱们先看效果
从图片不难看出扫雷程序不是十几行代码就可以运行起来的程序,其中需要你有写代码的基本逻辑思维能力,还需要用到二维数组,函数,循环等基本的c语言知识,如果以上你都学过,想必敲出扫雷这个代码不说信手拈来但是也绝非难事。那么我们开始吧!
首先我们先了解一下扫雷的游戏规则:你需要在不点错雷的情况下尽可能快的将所有的雷都标记出来,如果你点错,就得重新开始。
在了解游戏规则后我们先不着急写程序,因为扫雷代码需要很多函数调用,C语言提倡高内聚低耦合。网上找的一个解释是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。简单说明就是每个功能块不要相互有太多粘连,修改一个数据不会牵连整个程序,每个功能块都有自己独立的功能。
这么说就大概了解了,所以我们创建文件需要相互调用不能只用一个test.c函数就把程序写完,这样自己在调试的时候也不容易找到错误,读者在阅读的时候也会特别困难。所以创建函数的时候我们需要创建 test.c 、game.c 、game.h 三个文件分别存储 主程序(函数调用),函数功能实现,函数头文件。
用循环语句来实现菜单,通过输入的值来判断是否玩游戏且打完游戏程序还会询问你是否游戏,调用一个menu可以让玩家知道有什么选项。
为了方便读者快速了解程序的构造,先把头文件和game函数分别放出来(一定看看注释),看看函数功能的划分然后一步步攻克问题!
#include
#include//
#include//两个都是埋雷需要用到的头文件
#define ROW 9//宏定义行的值为9
#define COL 9//列
#define ROWS ROW+2
#define COLS COL+2
#define easy_count 10//雷的个数
//初始化棋盘
void Initboard( char board[ROWS][COLS],int row,int col,char ret );
//打印棋盘
void print_board(char board[ROWS][COLS], int row, int col);
//放雷
void SetMine(char board[ROWS][COLS], int row, int col);
//找雷
void FineMine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col);
从头文件可以发现我们宏定义了几个数据,这些都是我们后期会常用到的数据,也方便后面更改数据。 我们在test.c函数中调用了game.h可以方便game函数在调用时候效率更高效。
#include"game1.h"
void menu() {
printf("*************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("*************************\n");
}
void game() {
char mine[ROWS][COLS] = { 0 };//雷数组
char show[ROWS][COLS] = { 0 };//展示素组
Initboard(mine, ROWS, COLS,'0');//初始化函数的调用,这里初始化mine全为‘0’
Initboard(show, ROWS, COLS,'*');//初始化show数组全是‘*’
print_board(show, ROW, COL);//打印数组函数的调用,这里打印show
SetMine(mine, ROW, COL);//放雷函数的调用
FineMine(mine, show, ROW, COL);//找雷函数的调用
}
int main() {
srand((unsigned int)time(NULL));
int input = 0;
do {
menu();
printf("请输入1或0\n");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误请重新输入\n");
break;
}
} while (input);
return 0;
}
我们这里先以简单模式的扫雷为核心来设计,9*9的棋盘,10个雷,不难想到我们要创建一个二维数组来存放,但是需要了解玩家看到的棋盘是不知道那个地方有雷,所以我们为了设计方便需要创建两个二维数组,一个命名为show用来给玩家看,第二个棋盘用来存放雷。因为每次点开一个空、坐标如果不是雷就要展示周围有几个雷,所以为了避免边界访问我们设计两个11*11的棋盘但是展示出来的只是中间的1-9。雷也要放在中间的1-9里面。
所以我们定义两个char类型数组(这里用char类型是为了后面方便计算坐标周围一圈有多少个雷)
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
在game.h中声明初始化函数
void Initboard( char board[ROWS][COLS],int row,int col,char ret);
在test.c的game.c函数中传值调用初始化函数
两个数组初始化的内容不同,所以在game函数内调用时候就把‘0’和‘*’传过去,
Initboard(mine, ROWS, COLS,'0');
Initboard(show, ROWS, COLS,'*');
在game.c中实现初始化函数
初始化时候将整个棋盘11*11包括边界都初始化,为了防止里面元素是随机值,在统计边界坐标的周围雷的个数时候出现错误
void Initboard(char board[ROWS][COLS], int rows, int cols, char ret)
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = ret;
}
}
}
这里只打印1-9行和1-9列的棋盘,在传递参数的时候,把ROW和COL都传递过去
在game.h中声明
void print_board(char board[ROWS][COLS], int row, int col);
在test.c中的game.c函数中调用打印函数
print_board(mine, ROW, COL);//game函数内部调用打印函数
print_board(show, ROW, COL);
在game.c中实现函数
void print_board(char board[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 ", board[i][j]);
}
printf("\n");
}
}
这里在检查函数功能的时候把两个棋盘都打印出来,检查修正一下
在game.h中声明雷棋盘
void SetMine(char mine[ROWS][COLS], int row, int col);
在test.c函数中的game.c函数里调用埋雷函数
SetMine(mine, ROW, COL);
这里实现埋雷函数有几个点需要注意
1.宏定义雷的个数,方便后续检查更改函数。
#define easy_count 8
2.雷的坐标需要随机,不固定
这里在main函数中调用一次初始值(这里函数不懂不要紧,记住运用方法就行)
srand((unsigned int)time(NULL));
在埋雷函数中调用 ,每次调用会出现随机值然后模上行或列结果空的是0-8之间,所以加上1让随机数为1-9
int x = rand() % row + 1;//1-9
int y = rand() % col + 1;
3.利用while循环,加上count计数,如果该坐标有雷就需要换个坐标,直到雷放到指定的个数为止
void SetMine(char board[ROWS][COLS], int row, int col) {
int count = easy_count;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
在设计找雷函数之前先设计几个子函数,在找雷函数中会调用到。
这里我们的设计思路是在每次输入完一个正确坐标后就判断show数组中的‘*’和设计的雷数量是否相同,如果相同就说明场面上剩余的‘*’都是雷,不同则说明里面还有非雷的坐标没有被排查。
int win(char show[ROWS][COLS], int row, int col) {
int minewin = 0;//存储*的个数
for (int i = 1; i <= row; i++) {
for (int j = 1; j < col; j++) {
if (show[i][j] == '*')
minewin++;
}
}
return minewin== easy_count;
}
这里设计一个检查一圈有多少雷的函数,这里通过下面的图可以看到,每输入一个坐标都可以推出周围一圈八个坐标,然后通过之前对mine棋盘的设计可以知道,雷是‘1’,非雷是‘0’,可以用两个for循环排查这八个坐标,如果是雷就count++,最后函数走完返回count的值,数雷个数的函数如下。
int Getminecount(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]=='1')
count++;
}
}
return count;
}
玩过扫雷的朋友们可以知道,点击一个坐标如果该坐标不是雷且周围也没有雷就会展开很大一圈空白(如下图),可以大大的降低游戏难度,提升游戏体验感,那么这个函数该怎么设计呢,我们接着往下看
首先可以确定调用这个函数的时候输入的肯定是非雷的坐标,那么这个函数需要实现的功能是,如果坐标一圈没有雷,那么这个坐标就设计为空格,之后再对周围八个坐标挨个排查,再对八个坐标的每一个一次排查,直到遇到该坐标周围有雷也就是Getminecount返回值是非0时候就停止,不难想到这里需要用到递归的想法,自己调用自己。
我们设计这个展开函数代码如下,因为是递归函数向四周展开,所以可能遇到数组边界还会继续递归,就会遇到问题,所以我们先限定坐标范围,坐标合法后我们需要增加限定条件,如果这个坐标周围有雷,也就是Getminecount函数返回值不是0,就停止展开,然后需要注意一个重点问题死递归,也就是在展开的过程当中遇到已经被调用的坐标后依旧调用 ,就会产生出大问题,调用多了就会出现栈满的情况,程序也就会出现问题,为了避免这种情况我们增加限定条件,如果一个坐标被调用后(该坐标值为空格),就不再调用,最后限定条件设计完了,开始递归了,每次满足条件后将自己周围的八个坐标都调用这个函数,最后产生的效果不言而喻。
展开函数需要用到两个数组,因为在计算雷个数的函数中需要用到mine数组,在展开时候需要在show数组中展示出来。
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{//展开一片函数
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int count = Getminecount(mine, x, y);
if (count != 0)
show[x][y] = count + '0';
else if (show[x][y] != ' ')
{
show[x][y] = ' ';
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, i, j);
}
}
}
else
{
return;
}
}
}
最后需要被调用到的函数都被设计好了,我们来着手设计最后的找雷函数
老规矩在game.h中声明
void FineMine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col);
在test.c中的game()函数里传值调用函数
FineMine(mine, show, ROW, COL);
在game.c中实现函数game函数中实现找雷,需要排查输入的坐标,先判断是否在合法区域,合法后再判断是该坐标是否是雷,最后进入的肯定是非雷的坐标
void FineMine( char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1) {
printf("请输入坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf( "你被炸死了\n" );
print_board(mine, ROW, COL);
break;
}
else if (show[x][y] != '*') {
printf("该位置已经被排查,请重新输入\n");
}
else
{
Unfold(mine, show, x, y);
print_board(show, ROW, COL);
if (win(show, row, col)) {//如果条件成立说明雷的数量和剩余* 的数量一致
printf("恭喜你排雷成功\n");
break;
}
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
}
以上就是扫雷代码的全部内容了,希望可以帮到大家,欢迎批评指错!
以下是game.c的全部代码(game.h和test.c都在文章开头)
#define _CRT_SECURE_NO_WARNINGS 1
#include"game1.h"
void Initboard(char board[ROWS][COLS], int rows, int cols, char ret)
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = ret;
}
}
}
void print_board(char board[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 ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col) {
int count = easy_count;
while (count) {
int x = rand() % row + 1;//1-9
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
int Getminecount(char mine[ROWS][COLS], int x, int y) {
//return 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]
// + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0';
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]=='1')
count++;
}
}
return count;
}
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{//展开一片函数
/*if (x<1 || x>ROW || y<1 || y>COL) {
return;
}
if (show[x][y] != '*') {
return;
}
int count= Getminecount(mine, x, y);
if (count > 0) {
show[x][y] = count + '0';
(*win)++;
return;
}
if (count == 0) {
show[x][y] = ' ';
(*win)++;
Unfold(mine, show, x-1, y, win);
Unfold(mine, show, x-1,y-1, win);
Unfold(mine, show, x, y-1, win);
Unfold(mine, show, x+1, y-1, win);
Unfold(mine, show, x+1, y, win);
Unfold(mine, show, x+1, y+1, win);
Unfold(mine, show, x, y+1, win);
Unfold(mine, show, x-1, y+1, win);
}*/
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int count = Getminecount(mine, x, y);
if (count != 0)
show[x][y] = count + '0';
else if (show[x][y] != ' ')
{
show[x][y] = ' ';
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, i, j);
}
}
}
else
{
return;
}
}
}
//判断胜利的函数:如果剩余的*和雷的个数一样说明就赢了(因为但凡踩到雷就结束游戏)//如果按照每次排雷成功就++需要指针,比较繁琐
int win(char show[ROWS][COLS], int row, int col) {
int minewin = 0;//存储*的个数
for (int i = 1; i <= row; i++) {
for (int j = 1; j < col; j++) {
if (show[i][j] == '*')
minewin++;
}
}
return minewin== easy_count;//如果*的数量等于雷的数量就返回1,否则返回0,判断等式的定义
}
void FineMine( char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1) {
printf("请输入坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf( "你被炸死了\n" );
print_board(mine, ROW, COL);
break;
}
else if (show[x][y] != '*') {
printf("该位置已经被排查,请重新输入\n");
}
else
{
Unfold(mine, show, x, y);
print_board(show, ROW, COL);
if (win(show, row, col)) {//如果条件成立说明雷的数量和剩余* 的数量一致
printf("恭喜你排雷成功\n");
break;
}
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
}