hello,米娜桑。
还记得以前捧到大屁股计算机,和小伙伴们比赛玩扫雷谁厉害的快乐嘛?
趁着年末最后的小尾巴,自己学习一下如何用C语言实现简易版的扫雷游戏吧!
成品展示:
目录
一、思路解析
我们这里制作的是最简单的9×9格的扫雷游戏,这81个格子中存储的类型一定是相同的所以我们很容易想到使用二维数组进行存放。
然后我们开始思考扫雷这个游戏需要做些什么?
ps:我们这里的雷用字符‘1’表示,没有雷的我们用字符‘0’表示,没检测的区域我们用字符‘*’表示。
1.必不可少的是菜单界面,也是最简单的,这里就不多加赘述。
2.初始化棋盘,首先将所有的格子里都不放雷,进行初始化。这时候我们要想到后续中系统要有一个放雷的棋盘(相当于最终答案),以及呈现给用户的排查雷的过程中的棋盘,所以我们在设置时会设置两个棋盘。此时布置棋盘的话,大家第一反应是直接设置9×9的数组对吧?但是,大家再想想看再扫雷时出现的数字时周围8个格子中雷的数量,处于中间的格子还好,若是边框上的格子,不就较难处理了嘛?所以我们选择在四周多加两行两列(拓展区域),使我们想排查的格子周围都能有八个格子。
配图:
3.棋盘初始化之后,我们要把雷安下去,这时候也要特别留意了,安的雷要在中间9×9的格子中,并且是随机安放,所以又要用到srand、rand函数随机数的生成。之后将棋盘进行打印。
4.一切准备就绪之后就到了用户排雷的阶段了,用户排雷首先要检验用户排的方格是否有雷,没有的话检查附近八个方格,有几个格子中有雷,就显示数字几。这里我们还想到当附近好几个格子(不只是八个)中都没有雷时,排查区域会扩散开来,所以我们多写一组扩散函数。
配图:
5.最后,就是判断输赢的函数了,当我们把中间9×9的格子中81(总格数)-10(雷数)全部排查完毕之后,也就代表赢了,若中途踩到了雷,也就是输了
二、包含的头文件及宏定义
#define ROW 11
#define COL 11//用于定义11×11的数组
#define R 9
#define C 9//定义9行0列
#define easy_count 10//定义雷的总个数10个
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//初始化棋盘
void init_board(char arr[ROW][COL], int row, int col, char c);
//布置雷
void set_mine(char mine[ROW][COL],int r,int c);
//排查雷
void find_mine(char mine[ROW][COL],char show[ROW][COL], int r, int c);
//查找附近雷的数量
int get_mine_count(char mine[ROW][COL], int x, int y);
//打印棋盘
void show_board(char arr[ROW][COL], int r, int c);
//展开棋盘
void spread_board(char mine[ROW][COL],char show[ROW][COL], int row, int col);
三、用到的主要函数
1.初始化棋盘
//初始化棋盘
void init_board(char arr[ROW][COL], int row, int col, char c)
{
int i = 0,j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = c;
}
}
}
这里有个小细节,最后一个形参我用的时char c并没有急着用‘0’或‘*’代替,因为我们知道要初始化两个棋盘,分别用‘0’与‘*’打印就有些冗余了。这里用一个新的形参c代替,在调用时输入实参‘0’或‘*’就好了。
另外注意:每次调用数组时都要在心中默默地问自己一下我到底要用11×11的区域还是9×9的区域。在这里我们要将整个棋盘初始化所有会用到11×11的区域,调用此函数时row和col用11。
2.布置雷
//布置雷
void set_mine(char mine[ROW][COL], int r, int c)
{
int count = easy_count;
int x = 0, y = 0;
while (count)
{
x = rand() % r + 1;
y = rand() % c + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
布置雷的函数注意调用时的实参是系统的棋盘,而不是用户的棋盘,这里为了区分,我将形参与实参设置的同名mine[ROW][COL]。
再次询问自己这里用到的是11×11还是9×9,我们这里布置雷是在中间81个格子中布置所以用到的是9×9区域,调用的r和c都是9.
3.排查雷
//排查雷
void find_mine(char mine[ROW][COL], char show[ROW][COL], int r, int c)
{
int x = 0, y = 0,win=0;//这里的win是为后续判断输赢做铺垫的,先不管它
while (win<r*c- easy_count)
{
printf("请输入要排查的坐标:>\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= r && y >= 1 && y <= c)//这里要判断输入的坐标是否合法,在中间9×9的区域内,也就是说x和y的值都要在1~9之间
{
if (mine[x][y] == '1')//之前设置的雷的字符为'1',调用系统棋盘,若输入的x和y正好对应系统棋盘中为'1',那么踩中地雷,游戏结束
{
printf("很遗憾,被炸死了\n");
show_board(mine, R, C);//死也要有所瞑目,最后打印系统棋盘(正确答案)给用户
break;//退出循环
}
else
{
int count = get_mine_count(mine,x,y);//count计数,计周围八个格子中雷的数量,get_mine_count函数正是计算雷的数量的函数
show[x][y] = count + '0';//因为count是int型,而数组是char型,我们将count加上字符'0'也一样能得到字符'n(一个数)'代表雷的个数
spread_board(mine,show, x, y);//扩展函数
show_board(show, R, C);//没有踩到雷就要打印一下用户已经扫过的区域的雷的情况,以方便用户进行下一次扫雷
win=is_win(show, r, c);//判断输赢函数返回一个int型
}
}
else
{
printf("输入坐标非法,请重新输入\n");//输入坐标非法情况
}
}
if (win == r * c - easy_count)//判断输赢
{
printf("恭喜你,排雷成功\n");
show_board(mine, R, C);
}
}
4.查找附近雷的数量
//查找附近雷的数量
int get_mine_count(char mine[ROW][COL], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
查找雷的数量要返回数值所以是int型。mine数组中都是char型,但是只要|有雷的格子|字符'1'减去|没有雷的格子|字符'0'一样可以得到数字1.所以就将周围八个格子中的字符加起来减去八个字符'0'就可以得到返回值。
5.打印棋盘
//打印棋盘
void show_board(char arr[ROW][COL], int r, int c)
{
int i = 0, j = 0;
printf("------------------扫雷------------------\n\n");//分割行,也可以根据个人喜好自己定义
for (i = 0; i <= r; i++)
{
printf(" %d |",i);//依次打印0,1,2,3,4,5,6,7,8,9方便用户在扫雷时读取坐标,此处为横向的坐标
}
printf("\n---|---|---|---|---|---|---|---|---|---|\n");//打印表格的分割线
for (i = 1; i <= r; i++)
{
printf(" %d |", i);//依次打印1|,2|,3|,4|,5|,6|,7|,8|,9|为纵向的坐标以及分割线
for (j = 1; j <= c; j++)
{
printf(" %c |", arr[i][j]);//依次打印用户棋盘以及分割线
}
if(i!=r)
printf("\n---|---|---|---|---|---|---|---|---|---|\n");
}
printf("\n------------------扫雷------------------\n");//分割行,也可以根据个人喜好自己定义
printf("\n");
}
打印棋盘本身操作不难,就是过程有些繁琐,语句较多,需要小伙伴们一边调试一边进行修改。
6.展开棋盘
//展开棋盘
void spread_board(char mine[ROW][COL], char show[ROW][COL], int x, int y)
{
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)//因为这是一组递归函数,我们要设置范围,所以首先判断x与y的值是否合法,不设置的话判断到后面就会出现数组越界的问题
{
while (mine[x - 1][y] == '0'&&show[x-1][y]=='*')
{
int count = get_mine_count(mine, (x - 1), y);
if (count == 0)
{
show[x - 1][y] = '0';
spread_board(mine, show, (x - 1), y);
}
else
break;
}
while (mine[x + 1][y] == '0' && show[x + 1][y] == '*')
{
int count = get_mine_count(mine, (x + 1), y);
if (count == 0)
{
show[x + 1][y] = '0';
spread_board(mine, show, (x + 1), y);
}
else
break;
}
while (mine[x][y - 1] == '0' && show[x][y-1] == '*')
{
int count = get_mine_count(mine, x, (y - 1));
if (count == 0)
{
show[x][y - 1] = '0';
spread_board(mine, show, x, (y - 1));
}
else
break;
}
while (mine[x][y + 1] == '0' && show[x][y+1] == '*')
{
int count = get_mine_count(mine, x, (y + 1));
if (count == 0)
{
show[x][y + 1] = '0';
spread_board(mine, show, x, (y + 1));
}
else
break;
}
}
}
拿其中一个举例子,当系统棋盘中mine[x-1][y]的值为'0'没有地雷,且用户棋盘中show[x-1][y]的值为'*'即用户未进行扫描此格子是否有雷(作者在写的时候一开始也疏忽了这个语句),都满足这两个语句,再进行get_mine_count函数进行读入查看[x-1][y]周围八个格子中是否有雷,再进行判断count是否为0(周围没有地雷)那么[x-1][y]这个点就可以被扫描出来被赋值为'0'。
7.判断输赢
//判断输赢
int is_win(char show[ROW][COL],int r,int c)
{
int x = 1, y = 1,win=0;
for (x = 1; x <= r; x++)
{
for (y = 1; y <= c; y++)
{
if (show[x][y] != '*')
win++;
}
}
return win;
}
每检测出用户棋盘中有一个值不为'*'即每有一个格子被扫描过则win+1,加到最后,只要win的值等于r * c - easy_count(9*9-10=71)【此语句位于3.排查雷中最后一个if判断语句】就证明用户胜利,扫雷成功。
8.整合
void menu()//菜单
{
printf("**************\n");
printf("****1.play****\n");
printf("****2.exit****\n");
printf("**************\n");
}
void game()
{
char mine[ROW][COL]={0};//系统棋盘
char show[ROW][COL]={0};//用户棋盘
init_board(mine, ROW, COL, '0');//初始化系统棋盘全为‘0’
init_board(show, ROW, COL, '*');//初始化用户棋盘全为‘*’(未检测)
set_mine(mine, R, C);//布置雷
show_board(show, R, C);
find_mine(mine,show, R, C);//排查雷
}
void test()
{
int input=0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
break;
}
case 0:
{
printf("退出游戏\n");
break;
}
default:
{
printf("输入错误,请重新输入\n");
break;
}
}
} while (input!=1&&input!=0);
}
int main()
{
srand((unsigned int)time(NULL));
test();
return 0;
}//最后以一个短小精悍的梦函数收尾啦,短短的几个语句背后却包含着如此多的函数代码
最后最后还有七天就要迎来新年啦!这里提前祝大家虎年吉利迎财源,春节祥和喜事连;家庭和睦人长久,社会和谐实力添;新春佳节庆团圆,健康幸福接平安!
快拉上你的好朋友一起来自己写的游戏程序中pk一下吧!
这里附上另一款游戏[开启C语言秃头之旅]三子棋游戏