c语言实现扫雷

时间:2023-01-19 18:57:39

前言:

上一篇博客我们写了三子棋小游戏,紧着这我们趁热打铁,继续巩固知识点,再来写一个更有意思的扫雷吧,想必扫雷大家都玩过,我就不做介绍了。

概述:

我们一样将代码分为三部分来写,主函数main.c(在course-9.c中),游戏函数game.c,函数声明game.h,其中最核心的还是游戏函数game.c,game.c中包括大量主题函数的书写,有初始化函数,打印棋盘函数,布置雷函数,计算雷函数,标记坐标函数(选择拓展),扫雷函数(重点)

演示说明:

由于界面不是我们学习的重点,我们只需要掌握核心的代码即可,所以我们还是通过控制台来使用输入坐标来操作游戏

很不幸我们排的第一个雷就被炸死了,大家感受即可

c语言实现扫雷

程序主函数course-9.c

这里我们先做好函数的整体框架,在添加相应的功能

main主函数

同样我们先从主函数main入手,main是程序执行的入口,我们首先调用test函数,在test函数中做相应工作

int main()
{
test();
return 0;
}

test函数

test的函数主演是接收用户选择输入,导入菜单函数,srand((unsigned int)time(NULL));这个介绍过很多次了,时间戳用来辅助生成随机数的,我们的游戏菜单界面一般都是用do……while()循环来写再结合switch……case语句细化

void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入:");
scanf("%d",&input);
switch(input)
{
case 1:
printf("游戏开始\n");
game();
break;
case 0:
printf("退出游戏:\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while(input);
}

menu函数(菜单)

void menu()
{
printf("*****************************\n");
printf("***** 1.play 0.exit *****\n");
printf("*****************************\n");
}

game游戏函数

我们的游戏函数大概框架如下:我们一共准备两个棋盘,一个系统布置雷的棋盘,一个玩家扫雷的棋盘,进入函数我们首先定义布置雷棋盘和扫雷棋盘,然后进行初始化(初始化目的是将棋盘数据统一,防止出错,布置雷棋盘:0代表没雷,1代表有雷,起开始全部初始化为 0,后面在单独布置雷,扫雷棋盘:起开始全部初始化为 * 号),初始化结束我们打印棋盘,紧接着进行布置雷,排雷操作(这里解释:为什么我们在show排雷棋盘没有布置雷只有*号还能踩到雷,虽然雷在mine棋盘,但是我们在排雷时,玩家输入坐标后,我们是同时在两张棋盘进行验证(两张棋盘结构一样雷在mine,而玩家只能看到show可以理解为mine是show的内核棋盘),一旦mine棋盘踩雷就结束游戏)

//扫雷游戏
void game()
{
//存储雷的信息
//1.布置好的雷的信息
char mine[ROWS][COLS] = { 0 };
//2.排查出的雷的信息
char show[ROWS][COLS] = { 0 };
//初始化
initboard(mine, ROWS, COLS,'0');
initboard(show, ROWS, COLS,'*');
//打印棋盘
//displayboard(mine, ROW, COL);//埋雷棋盘
displayboard(show, ROW, COL);//排雷棋盘
//布置雷
setmine(mine,ROW,COL );
//displayboard(mine, ROW, COL);
//扫雷
findmine(mine,show,ROW,COL);

}

程序的头文件即函数声明game.h

这里代码很简单,主要用到了宏定义,之所以用宏定义是因为其可变性强,我们可以随时改变棋盘大小和雷的个数,我们将棋盘定义为99大小,布置10颗雷,重点:我们需要的是99的棋盘为什么定义棋盘偏要1111的棋盘呢,以为后期我们在遍历棋盘时,比如统计雷的个数当雷处在边界时,可能会产生数组越界,所以大一圈定义为1111的棋盘,只要我们不打印出来1111的棋盘就好啦,把雷也布置进99的棋盘内(特别注意哦,数组下标是从0开始的,只要我们控制好1-9的坐标范围,雷就一定能布置进9*9的棋盘)

函数声明

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//函数声明
void initboard(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);//扫雷

函数部分game.c

初始化函数initboard

这里我们接收埋雷棋盘,和排雷棋盘进行初始化,将埋雷棋盘初始化为字符0,将排雷棋盘初始化为 * 号

void initboard(char board[ROWS][COLS], int rows, int cols,char set)//初始化
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}

打印棋盘函数displayboard

很简单的一个函数只是做了序号的标注,方便用户写出坐标,不做过多解释

void displayboard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
int i = 0;
int j = 0;
//打印列号
printf("\n");
for (i = 1; i <= col; i++)
{
printf(" %d", i);
}
printf("\n");
for (i = 0; i <= col-1; i++)
{
printf(" -");
}
printf("\n");
for (i = 1; i <= row; i++)
{

printf("%d", i);
printf("|");
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}

布置雷函数setmine

EASY_COUNT是宏定义的雷的个数为10个,利用一个while循环,以count雷的个数为循环条件,因为c语言中0可以作为假结束循环,rand() % row + 1设置随机数,保证生成的数字在[1-9]之间,然后根据生成的坐标设置雷,直到10颗雷全部设置完成,结束while循环

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--;
}
}
}

统计雷的个数函数get_mine_count

这里统计雷的个数,运用了一个技巧,就是运用ASCII码计算数值,比如字符3的ascii码减去字符0ascii码等于数字3ascii码 即'3' - '0' = 3,正因如此,我们的埋雷棋盘中的雷是字符1,只要统计出一个坐标周围有多少个字符1再减去字符0就是相应的雷的个数了

c语言实现扫雷

//字符3减去字符0等于数字3(利用ASCII码计算)  '3'-'0'=3
int get_mine_count(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';
}

排雷函数findmine

这个是核心

99一共81个棋格,当所有非雷棋格(71个)被排尽停止进入循环游戏结束,因此需要一个变量win统计扫雷进度,show[x][y] == '' && x >= 1 && x <= row && y >= 1 && y <= col这句判断条件,因为我们在show棋盘扫雷所以玩家输入坐标必须是 * 号即没有被排过区域,还需满足9*9的棋盘坐标合法,不合法会被强制重新输入

c语言实现扫雷

,进入扫雷判断(这是判断是否是雷则在mine棋盘判断),如果是雷直接打印埋雷棋盘,break结束游戏,否则进入无雷区域合并函数get_around,然后打印棋盘然后记录下来排雷进度,另外如果玩家输入的是0 0坐标,则进入标雷函数(这个作为拓展),如果win==71则游戏胜利,排雷成功

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 - EASY_COUNT)//一共81个棋格,当所有非雷棋格(71个)被排尽停止进入循环
{
printf("请输入坐标:");
scanf("%d%d", &x, &y);
if (show[x][y] == '*' && x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//坐标合法,判断是不是雷
{
printf("很遗憾,你被炸死了");
displayboard(mine, row, col);
break;
}

else //不踩雷,则计算xy坐标周围有几颗雷
{
get_around(mine, show, x, y, &win);
displayboard(show, row, col);
printf("win=%d\n", win);
}
}
else
{
if (x == 0 && y == 0)//当xy为0 0时可进入此条件,标记雷
{
printf("标记坐标\n");
biao_lei(show,ROW,COL);
}
else
printf("坐标非法,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("------------------------------------\n");
printf(" 恭喜你,排雷成功!!!!!!!!!!\n");
printf("------------------------------------\n");
displayboard(mine, row, col);

}
}

无雷区域合并函数get_around

核心中的核心

这里主要利用递归算法,首先将传来的坐标进行计算周围雷的个数,如果类个数不为0,则将计算结果以字符形式赋值给该坐标,并将win++,因为这也算排雷进度的一部分,否则进行无雷区域合并,首先将该坐标置为空,win++,然后进入递归 递归条件是一起中一个为例show[x - 1][y] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y >= 1 && y <= COLS - 2,要满足是未被排过的坐标且在9*9棋盘内方可进入递归,这块递归很复杂需要仔细琢磨

void get_around(char mine[ROWS][COLS],char show[ROWS][COLS], int x, int y,int* win)//递归算法
{
if (show[x][y] == '*' && x >= 1 && x <= ROWS - 2 && y >= 1 && y <= COLS - 2)
{
int count = get_mine_count(mine, x, y);
if (count == 0)//周围没有雷
{
show[x][y] = ' ';
(*win)++;
//周围八个坐标要满足为'*'并且合法才可继续递归
if (show[x - 1][y] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y >= 1 && y <= COLS - 2)
get_around(mine, show, x - 1, y, win);
if (show[x - 1][y - 1] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x - 1, y - 1, win);
if (show[x][y - 1] == '*' && x >= 1 && x <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x, y - 1, win);
if (show[x + 1][y - 1] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x + 1, y - 1, win);
if (show[x + 1][y] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y >= 1 && y <= COLS - 2)
get_around(mine, show, x + 1, y, win);
if (show[x + 1][y + 1] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x + 1, y + 1, win);
if (show[x][y + 1] == '*' && x >= 1 && x <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x, y + 1, win);
if (show[x - 1][y + 1] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x - 1, y + 1, win);
}
else//周围有雷
{
show[x][y] = count + '0';
(*win)++;
}
}
}

演示当输入一个坐标 9 9 周围没有雷,就会将没雷的坐标置空,和扫雷游戏一样的效果

c语言实现扫雷

标雷函数biao_lei  拓展

就是插扫雷中的小红旗,我们用@代替,因为我是通过输入0 0坐标进入,想退出输入10 10坐标,因为这两个坐标不会影响棋盘,用别的方式字符进入也可以

c语言实现扫雷

void biao_lei(char show[ROWS][COLS],int row,int col)//标记坐标函数
{
int a = 0;
int b = 0;
while (1)
{
printf("请输入:");
scanf("%d%d", &a, &b);
if (a == 10&&b==10)//ab为10 10,退出标记坐标
break;
else
{
if (show[a][b] == '*')
{
show[a][b] = '@';
}
else
printf("坐标错误,请重新输入\n");
}
}
displayboard(show, row, col);//打印标记后的棋盘
}

完整代码

course-9.c

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include "game.h"

//扫雷游戏
void game()
{
//存储雷的信息
//1.布置好的雷的信息
char mine[ROWS][COLS] = { 0 };
//2.排查出的雷的信息
char show[ROWS][COLS] = { 0 };
//初始化
initboard(mine, ROWS, COLS,'0');
initboard(show, ROWS, COLS,'*');
//打印棋盘
//displayboard(mine, ROW, COL);//埋雷棋盘
displayboard(show, ROW, COL);//排雷棋盘
//布置雷
setmine(mine,ROW,COL );
//displayboard(mine, ROW, COL);
//扫雷
findmine(mine,show,ROW,COL);

}
void menu()
{
printf("*****************************\n");
printf("***** 1.play 0.exit *****\n");
printf("*****************************\n");
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入:");
scanf("%d",&input);
switch(input)
{
case 1:
printf("游戏开始\n");
game();
break;
case 0:
printf("退出游戏:\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while(input);
}
int main()
{
test();
return 0;
}

game.h

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//函数声明
void initboard(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);//扫雷

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void initboard(char board[ROWS][COLS], int rows, int cols,char set)//初始化
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}

void displayboard(char board[ROWS][COLS], int row, int col)//打印棋盘
{
int i = 0;
int j = 0;
//打印列号
printf("\n");
for (i = 1; i <= col; i++)
{
printf(" %d", i);
}
printf("\n");
//打印间隔线
for (i = 0; i <= col-1; i++)
{
printf(" -");
}
printf("\n");
for (i = 1; i <= row; i++)
{

printf("%d", i);
printf("|");
for (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;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//字符3减去字符0等于数字3(利用ASCII码计算) '3'-'0'=3
int get_mine_count(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';
}

//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- EASY_COUNT)//一共81个棋格,当所有非雷棋格(71个)被排尽停止进入循环
// {
// printf("请输入坐标:");
// scanf("%d%d", &x, &y);
// if (x >= 1 && x <= row && y >= 1 && y <= col)
// {
// if (mine[x][y] == '1')//坐标合法,判断是不是雷
// {
// printf("很遗憾,你被炸死了");
// displayboard(mine, row, col);
// break;
// }
// else //不踩雷,则计算xy坐标周围有几颗雷
// {
// int count=get_mine_count(mine,x,y);
// show[x][y] = count + '0';//字符3减去字符0等于数字3(利用ASCII码计算) '3'-'0'=3,那反过来3+'0'不就等于字符3嘛
// displayboard(show, row, col);
// win++;
// }
// }
// else
// {
// printf("坐标非法,请重新输入");
// }
// }
// if (win == row * col - EASY_COUNT)
// {
// printf("恭喜你,排雷成功!!!!!!!!!!\n");
// displayboard(mine, row, col);
//
// }
//}


void biao_lei(char show[ROWS][COLS],int row,int col)//标记坐标函数
{
int a = 0;
int b = 0;
while (1)
{
printf("请输入:");
scanf("%d%d", &a, &b);
if (a == 10&&b==10)//ab为10 10,退出标记坐标
break;
else
{
if (show[a][b] == '*')
{
show[a][b] = '@';
}
else
printf("坐标错误,请重新输入\n");
}
}
displayboard(show, row, col);//打印标记后的棋盘
}


void get_around(char mine[ROWS][COLS],char show[ROWS][COLS], int x, int y,int* win)//递归算法
{
if (show[x][y] == '*' && x >= 1 && x <= ROWS - 2 && y >= 1 && y <= COLS - 2)
{
int count = get_mine_count(mine, x, y);
if (count == 0)//周围没有雷
{
show[x][y] = ' ';
(*win)++;
//周围八个坐标要满足为'*'并且合法才可继续递归
if (show[x - 1][y] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y >= 1 && y <= COLS - 2)
get_around(mine, show, x - 1, y, win);
if (show[x - 1][y - 1] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x - 1, y - 1, win);
if (show[x][y - 1] == '*' && x >= 1 && x <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x, y - 1, win);
if (show[x + 1][y - 1] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y - 1 >= 1 && y - 1 <= COLS - 2)
get_around(mine, show, x + 1, y - 1, win);
if (show[x + 1][y] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y >= 1 && y <= COLS - 2)
get_around(mine, show, x + 1, y, win);
if (show[x + 1][y + 1] == '*' && x + 1 >= 1 && x + 1 <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x + 1, y + 1, win);
if (show[x][y + 1] == '*' && x >= 1 && x <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x, y + 1, win);
if (show[x - 1][y + 1] == '*' && x - 1 >= 1 && x - 1 <= ROWS - 2 && y + 1 >= 1 && y + 1 <= COLS - 2)
get_around(mine, show, x - 1, y + 1, win);
}
else//周围有雷
{
show[x][y] = count + '0';
(*win)++;
}
}
}

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 - EASY_COUNT)//一共81个棋格,当所有非雷棋格(71个)被排尽停止进入循环
{
printf("请输入坐标:");
scanf("%d%d", &x, &y);
if (show[x][y] == '*' && x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//坐标合法,判断是不是雷
{
printf("很遗憾,你被炸死了");
displayboard(mine, row, col);
break;
}

else //不踩雷,则计算xy坐标周围有几颗雷
{
get_around(mine, show, x, y, &win);
displayboard(show, row, col);
printf("win=%d\n", win);
}
}
else
{
if (x == 0 && y == 0)//当xy为0 0时可进入此条件,标记雷
{
printf("标记坐标\n");
biao_lei(show,ROW,COL);
}
else
printf("坐标非法,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("------------------------------------\n");
printf(" 恭喜你,排雷成功!!!!!!!!!!\n");
printf("------------------------------------\n");
displayboard(mine, row, col);

}
}

留言:

欢迎评论区交流探讨哦!