在学习C语言的基础内容后,今天我们来写一个扫雷游戏进行练手。大家跟着我的思路,很快就能掌握相关技巧与逻辑。
目录
扫雷是一款经典的游戏,相信大家都玩过,如果没有玩过的朋友也不用太担心,我给大家先进行游戏介绍。
一、游戏介绍
游戏主界面如下图所示(标准化游戏):
规则:在一个布满方格的界面,如上左图,里面存放若干雷(以10 颗举例),当你点开一个位置时,如若有雷,会爆炸也就是意味游戏失败,如上中图,当点开的位置没有雷,会计算点开位置周围9宫格内部的含雷数量。如下图:
显示1,表明周围8个位置的含雷数目为1个(即周围8个位置中有一个位置是雷)。如果周围8个位置没有,并且自己也不是雷,点开后,则显示空白。
等到把没有雷的位置全部点开,只剩下十个有雷的位置,则证明游戏成功。
二、C语言窗口实现
我们用C语言实现游戏时,不能像上面那种前端界面做的很好,而是利用C语言输出窗口进行判断,输入一个坐标进行判断,确定是否有雷,周围雷的数目,以及游戏的输赢。如下图所示:
如上图,左图为初始界面,中图为游戏中,有图为游戏输界面,其中1表示雷的位置。
在用C语言实现的时候 ,由于代码的功能太过于繁琐,不能将其写在一个文件中,故创建三个文件进行编写。
1、game.c文件,用于书写游戏的主函数,包括游戏的整个逻辑框架;
2、game.h文件,用于书写所用到的头文件以及函数的声明;
3、 text.c文件,用于书写各个功能模块函数;
游戏的整个框架为:
第一步:游戏主界面
第二步:设置游戏
第三步:扫雷
三、分模块实现游戏功能
1、游戏主界面
此处为开始,选择是否进入游戏,输入1进入,输入0退出游戏,输入其他数字,输出-选择错误,请重新输入。
等到游戏结束后,此界面还需要再次出现供玩家选择,因此,此处需要使用一个do……while循环,先执行一次循环,在进行判断。
代码为:
//text。c文件
int main()
{
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);
return 0;
}
其中菜单函数的代码为:
//text.c文件
void menu()
{
printf("****************************************\n");
printf("***************1.play*******************\n");
printf("***************0.exit*******************\n");
printf("****************************************\n");
}
其代码效果为:
2、设置棋盘及初始化
在进入游戏后,首先要进行棋盘的创建:
棋盘规划:
1、底层棋盘(mine)--用于布置雷的位置(不会显示出来),并且用来判断输赢
2、显示棋盘(show)--用于玩家游戏的棋盘(给玩家显示),便于玩家输入。
棋盘的大小:
在此,构建两个二维数组进行棋牌的构建,二维数组的大小应该是棋牌的行数/列数+2,比如:如果是9×9的棋盘,应该构建11×11的数组。因为需要判断棋盘每一个位置周围的雷的数目,如果二维数组的大小和棋盘的大小相同,后面判断最外层棋盘位置的雷的数目时,会进行数组越界访问。
构建棋盘代码:
//game.h文件
#define ROW 9//定义棋盘的行数
#define COL 9//定义棋盘的列数
#define ROWS ROW+2//定义数组的行数
#define COLS COL+2//定义数组的列数
//game.c文件
void game()//定义游戏函数
{
char mine[ROWS][COLS]={0};//定义底层雷的二维数组
char show[ROWS][COLS]={0};//定义显示层的二维数组
}
初始化棋盘:
将构建的二维数组中,底层数组所有位置设置为‘0’(还未设置雷的位置,雷用‘1’设置)。显示层数组的所有位置为‘*’(还未测试,测试后用别的字符代替);
定义一个打印函数,在game函数中调用两次,分别打印底层界面与显示层界面。在打印函数中,利用for循环进行遍历初始化。
代码为:
//game.c文件
void game()
{
char mine[ROWS][COLS]={0};//定义底层雷的二维数组
char show[ROWS][COLS]={0};//定义显示层的二维数组
//初始化数组的内容为指定的内容
//mine 数组在没有布置雷的时候 都是'0'
//show 数组在没有排查雷的时候 都是'*'
Initboard(mine,ROWS,COLS,'0');//调用打印函数,用于打印底层数组
Initboard(show,ROWS,COLS,'*');//调用打印函数,用于打印显示层数组
}
//text.c文件
void Initboard(char board[ROWS][COLS],int rows ,int cols,char set )//函数声明时,可以不需要参数名,函数定义时,必须要有形参名,来接受实参的数
{
int i=0;
int j=0;
for(i=0;i<rows;i++)
{
for(j=0;j<cols;j++)
{
board[i][j]=set;//根据不同打印的参数,打印不同的数组
}
}
}
3、设置雷的位置
首先定义一下雷的数量,并且要方便以后的修改。
SetMine函数进行雷的生成,通过库里面的函数rand()产生随机数,并且判断产生的随机数是否符合棋盘的位置(此处为棋盘的位置并不是之前二维数组的位置),注意位置大小。使用rand()函数时,必须在主函数中进行修饰: srand((unsigned int )time (NULL));,不然,每次生成的随机数都是相同的。
代码为:
//game.h文件
#define EASY_COUNT 10
//game.c文件
void game()
{
char mine[ROWS][COLS]={0};//定义底层雷的二维数组
char show[ROWS][COLS]={0};//定义显示层的二维数组
//初始化数组的内容为指定的内容
//mine 数组在没有布置雷的时候 都是'0'
//show 数组在没有排查雷的时候 都是'*'
Initboard(mine,ROWS,COLS,'0');
Initboard(show,ROWS,COLS,'*');
SetMine(mine,ROW,COL);//雷的位置在底层数组中,其范围为棋盘的范围,注意传参的大小
}
// game.c文件主函数中
//设置随机数的生成起点
srand((unsigned int )time (NULL));
//text.c文件
void SetMine(char board[ROWS][COLS],int row,int col)
{
int count = EASY_COUNT;
//横坐标范围1-9,纵坐标范围1-9
while (count)
{
int x=rand()%row+1;//产生的随机数%行数+1,便使得其位于棋盘横坐标之中
int y=rand()%col+1;//产生的随机数%列数+1,便使得其位于棋盘纵坐标之中
if(board[x][y]=='0')
{
board[x][y]='1';
count--;//产生一个雷,数量减少
}
}
}
4、打印棋盘
打印棋盘,只需要打印显示数组即可,利用for循环进行遍历,为了便于玩家输入,在打印时每一行,每一列都标有行号与列号。
代码如下:
//game.h 文件中 game()函数内容
DisplayBoard(show,ROW,COL);
//text.c文件
void DisplayBoard(char board[ROWS][COLS],int row,int col)
{
int i, j=1;//定义循环变量
printf("--------扫雷游戏----------\n");//便于划分
for(j=0;j<=col;j++)//打印列号
{
printf("%d ",j);
}
printf("\n ");//便于划分
for(j=1;j<=col;j++)//便于划分
{
printf("--");
}
printf("\n");
for(i=1;i<=row;i++)//打印数组元素
{
printf("%d|",i);
for(j=1;j<=col;j++)
printf("%c ",board[i][j]);
printf("|");
printf("\n");
}
printf(" ");
for(j=1;j<=col;j++)
{
printf("--");
}
printf("\n");
printf("--------扫雷游戏----------\n");
}
效果图:
5、排雷
5.1标记
我们在进行排雷的时候,会根据已有的信息进行计算或猜测雷的位置,如果将这些位置进行标记,可以大大提高我们的排雷效率这样。
因此,我们在每一次检测某一位置前,进行判断是否需要标记,如果需要标记,输入标记的坐标,如果坐标合理,不超出棋盘范围,就进行标记,标号为'#',标记在显示层界面,如果不需要标记我们再进行检测。
代码如下:
//text.c文件 FindMine()函数中
Biaoji(show,ROW,COL);
//text.c文件
void Biaoji(char show[ROWS][COLS],int row,int col)//定义标记函数
{
int z;
int x=0;
int y=0;
while(1)
{
printf("您是否需要标记雷的位置,如果是,请输入1,如果否,请输入0:\n");
scanf("%d",&z);
if(z==1)//需要标记
{
printf("请输入要标记的坐标:\n");
scanf("%d%d",&x,&y);
if(x>=1&&x<=row && y>=1&&y<=col)
{
show[x][y]='#';
DisplayBoard(show,ROW,COL);
}
else
{
printf("输入坐标非法,请重新输入。\n");
}
}
else if(z==0)//不需要标记
break;
else//输入错误
printf("输入错误请重新输入:\n");
}
}
结果:
5.2排雷
进行检测,先判断输入坐标是否满足棋盘坐标合法,如果不满,重新输入;如果满足进入检测,检测时判断是否检测过,如果检测过,重新输入。如果没被检测过,进行检测,判断其是否为雷,如果为雷,则为失败,如果不是雷,进入判断周围雷数量的函数,统计周围个数。最后判断检测次数是否为0,若为0,则成功,若不为0,继续检测。
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
int x,y;
int win=WIN_COUNT ;
//测试棋盘非雷的次数(当测试的次数等于棋盘总位置-雷的数量,且没被炸死即为获胜)
do
{
Biaoji(show,ROW,COL);//调用是否需要标记函数
printf("请输入要排查的坐标:\n");//输入需要检测的坐标
scanf("%d%d",&x,&y);
if(x>=1&&x<=row && y>=1&&y<=col)//如果坐标合法
{
if(show[x][y]=='*'||show[x][y]=='#')//判断是否进行检测过
{
if(mine[x][y]=='1')//如果没检测过,检测结果为1就是雷
{
printf("很遗憾,您被炸死!\n");//被炸死
DisplayBoard(mine,ROW,COL);
break;
}
else//如果没被炸死,进行判断周围有几个雷
{
int *p;
p=&win;//将检测次数传至判断周围雷个数的函数中
get_mine_count(mine,show,x,y,p);//统计mine数组中x,y坐标周围有几个雷
DisplayBoard(show,ROW,COL);//打印显示棋盘
}
}
else//如果坐标已经被查就重新选择
printf("该坐标已被排查,请重新选择!\n");
}
else//如果坐标非法,就重新输入
{
printf("输入坐标非法,请重新输入。\n");
continue;
}
}
while(win!=0)//测试的次数还未减为0,进行测试
if(win==0)//判断检测次数是否为0,若为0,则成功
{
printf("恭喜您,排雷成功!\n");
DisplayBoard(mine,ROW,COL);//打印雷棋盘
}
}
部分结果显示:
5.3统计周围个数(含0展开)
在进行某一位置进行检测时,如果检测的位置不是雷,此时应该计算周围八个坐标的雷数,如下所示:
在中间的位置中,显示的为4,则周围的8个地方的雷数之和就是四。
我们判断完不是雷后,进入 get_mine_count函数进行计算周围位置的雷数之和。首先判断需要统计的位置是否满足要求:
1、该坐标是否超限;
2、该坐标是否已被计算过;
满足要求的坐标进行统计,每统计一次,需要检测的次数减一(判断最终取胜的条件),判断完后,需要在底层坐标中将其改为'-'(用于后面判断该位置是否进行过统计),在显示层坐标中,显示统计的结果(用于玩家判断周围雷的位置)。
采用for循环进行遍历的方式,如果位置有雷 z++,将最终的值进行显示;
如果经过统计,该位置周围的8个位置都没有0,此时显示层该位置处应该显示为空,并且进一步扩大展开,判断周围这8个位置的雷数,并显示出来。如下图所示:
当中间蓝色位置的周围灰色8个位置的雷数为零时,这时候我们就可以大胆的点这八个位置,游戏为了简便,便给我们一起就点开了,当然开始计算灰色位置它们自己周围8个雷数的综合并显示出来,此时便用到了递归,统计周围8个位置的雷数,每一次统计,检测次数win都要减一(用于判断输赢的条件),此处需要注意的时,如果上右图的蓝色圈位置周围雷数又为0.则需要进一步展开,这样右图5的位置就进行了重复,陷入死循环,因为它既是0位置的周围8个位置之一,也是蓝色圈位置的周围8个之一。但是我们提前有规定,在进入统计的时候,会对坐标有统计条件,统计过的位置不会再重复统计,这也就是说一个位置只会统计一次,这一点非常重要,不就会在递归调用的时候陷入死循环。还请读者好好理解。
void get_mine_count(char mine[ROWS][COLS],char show[ROWS][COLS], int x,int y,int *win)
{
if(x>=1&&x<=ROW && y>=1&&y<=COL&& mine[x][y]!='-')//判断输入坐标合法,并且未被统计过
{
int i,j;
int z=0;
mine[x][y]='-';//进行统计,统计完后令其为-
(*win)--;//统计一次,检测次数减一
for(i=-1;i<=1;i++)//进行周围雷的数量统计
{
for(j=-1;j<=1;j++)
{
if( mine[x+i][y+j]=='1')
z++;
}
}
show[x][y]=z+'0';//在显示界面层显示雷的数量
if(z==0)//如果周围雷的数量为0,则进一步展开统计
{
show[x][y]=' ';若周围雷的数量为0,则显示空
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
get_mine_count( mine, show,x+i,y+j,win);递归周围位置,检测周围位置的数量。
}
}
}
}
最后判断检测次数是否为0,若为0,则成功,若不为0,继续检测。至此,各个模块的功能与代码介绍完毕。
四、C语言源码
1.game.c文件
#include "game.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};
//初始化数组的内容为指定的内容
//mine 数组在没有布置雷的时候 都是'0'
//show 数组在没有排查雷的时候 都是'*'
Initboard(mine,ROWS,COLS,'0');
Initboard(show,ROWS,COLS,'*');
//设置雷
SetMine(mine,ROW,COL);
DisplayBoard(show,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:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入:\n");
break;
}
}
while( input);
return 0;
}
2.game.h文件
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 1
#define WIN_COUNT ROW*COL-EASY_COUNT
void Initboard(char board[ROWS][COLS],int rows ,int cols,char);
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);
3.text.c文件
#include "game.h"
void Initboard(char board[ROWS][COLS],int rows ,int cols,char set )
{
int i=0;
int j=0;
for(i=0;i<rows;i++)
{
for(j=0;j<cols;j++)
{
board[i][j]=set;
}
}
}
void DisplayBoard(char board[ROWS][COLS],int row,int col)
{
int i=1;
int j=1;
printf("--------扫雷游戏----------\n");
for(j=0;j<=col;j++)
{
printf("%d ",j);
}
printf("\n ");
for(j=1;j<=col;j++)
{
printf("--");
}
printf("\n");
for(i=1;i<=row;i++)
{
printf("%d|",i);
for(j=1;j<=col;j++)
printf("%c ",board[i][j]);
printf("|");
printf("\n");
}
printf(" ");
for(j=1;j<=col;j++)
{
printf("--");
}
printf("\n");
printf("--------扫雷游戏----------\n");
}
void get_mine_count(char mine[ROWS][COLS],char show[ROWS][COLS], int x,int y,int *win)
{
if(x>=1&&x<=ROW && y>=1&&y<=COL&& mine[x][y]!='-')
{
int i,j;
int z=0;
mine[x][y]='-';
(*win)--;
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
{
if( mine[x+i][y+j]=='1')
z++;
}
}
show[x][y]=z+'0';
if(z==0)
{
show[x][y]=' ';
for(i=-1;i<=1;i++)
{
for(j=-1;j<=1;j++)
get_mine_count( mine, show,x+i,y+j,win);
}
}
}
void SetMine(char board[ROWS][COLS],int row,int col)
{
int count = EASY_COUNT;
//横坐标范围1-9,纵坐标范围1-9
while (count)
{
int x=rand()%row+1;
int y=rand()%col+1;
if(board[x][y]=='0')
{
board[x][y]='1';
count--;
}
}
}
void Biaoji(char show[ROWS][COLS],int row,int col)
{
int z;
int x=0;
int y=0;
while(1)
{
printf("您是否需要标记雷的位置,如果是,请输入1,如果否,请输入0:\n");
scanf("%d",&z);
if(z==1)
{
printf("请输入要标记的坐标:\n");
scanf("%d%d",&x,&y);
if(x>=1&&x<=row && y>=1&&y<=col)
{
show[x][y]='#';
DisplayBoard(show,ROW,COL);
}
else
{
printf("输入坐标非法,请重新输入。\n");
}
}
else if(z==0)
break;
else
printf("输入错误请重新输入:\n");
}
}
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
int x=0;
int y=0;
int win=WIN_COUNT ;
//找到非0的次数
do
{
Biaoji(show,ROW,COL);
printf("请输入要排查的坐标:\n");
scanf("%d%d",&x,&y);
if(x>=1&&x<=row && y>=1&&y<=col)
{
if(show[x][y]=='*'||show[x][y]=='#')
{
if(mine[x][y]=='1')
{
printf("很遗憾,您被炸死!\n");
DisplayBoard(mine,ROW,COL);
break;
}
else
{
int *p;
p=&win;
get_mine_count(mine,show,x,y,p);//统计mine数组中x,y坐标周围有几个雷
DisplayBoard(show,ROW,COL);
}
}
else
{
printf("该坐标已被排查,请重新选择!\n");
}
}
else
{
printf("输入坐标非法,请重新输入。\n");
continue;
}
}while(win!=0);
if(win==0)
{
printf("恭喜您,排雷成功!\n");
DisplayBoard(mine,ROW,COL);
}
}
完结撒花,创作不易,还请多多点赞支持~~~