上次我们写了三子棋,回顾一下吧,用到了很简单的二维数组,全局变量,头文件的创建,函数的封装调用,循环分支。想必大家下来肯定都自己上手练了练的,看会了可不算会,要做就做话少本事大的人,不做屁多话稠的卢瑟,说可以,一做就歇菜,所以编程这条路上没有用眼睛成功的,所有的程序员都是勤于动手动脑的人,想100%成功就要200%的努力,好了回归正题,今天我们来完成三子棋的进阶版,扫雷,想必大家更不陌生,在大家买的第一部电脑上都有这部游戏,蓝灰色的界面,一个小黄脸,很多小格子藏着地雷等你去踩,规则我就不多说了,大家可以自行打开这款游戏回味一下, 我们直接来用C语言实现
希望大家脑海里存着上次我们写三子棋的思路,这就算是一个程序的简化简化简化版,我们就按这个思路来完成我们今天的工作。
首先呢还是我们的游戏封面,当然上次我就说了如果你这个人不怎么在乎审美只想一心一意把代码写好当然可以忽略这个环节,但是我不能,因为我有审美,我还要水博客长度,所以还是照例我们先搞好封面,依旧是1.玩,0.退出
void Gameface()
{
printf("*************************");
printf("****1.play*****0.exit****");
printf("*************************");
printf("请选择:");
}
然后就是我们的test函数,和上次的三子棋结构大体一致,do while循环的结构,1玩游戏0退出其他报错.......
void test()
{
int input = 0;
//srand((unsigned int)time(NULL));
do
{
Gameface();
scanf("%d\n", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏");
break;
default:
printf("输入错误,请重新输入");
break;
}
} while (input);
}
不要问我为什么把srand那一行注释,有人会问我们已经知道那是什么了你遮住有啥用,但是毕竟我们还没到那一步,现在我们只是介绍进入游戏的流程,欧克,我们当然都是网瘾少年了,我要玩游戏!我选1,那一个game函数肯定是必不可少的吧
void game()
{
//雷的信息存储
//布置好雷的信息
char mine[ROWS][COLS] = { 0 };
//排查雷的信息
char show[ROWS][COLS];
//初始化数组
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
Settingmine(mine,ROW,COL);
//DisplayBoard(mine, ROW, COL);
//扫雷
Fidemine(mine,show,ROW,COL);
}
game函数作为最重要的函数,整个雷区要在这里初始化,展示,玩家扫雷后的雷区要展示,所以在game函数里又嵌套了很多函数,好了,重头戏才开始。
按照顺序,首先是创建雷区,大家肯定都能想到用二维数组啊,是的你没错,还是用二维数组,但是不要太天真认为一个二维数组就足够了,我首先先来讲一下我们扫雷的玩法
好,这次明显上次的三子棋有不同,上次是需要两个玩家在空白的棋盘上下棋,这次需要玩家在满是地雷的雷区上排雷,一个是空的,没有任何东西,一个确实“干货”满满,想必大家脑海里已经开始乱了,放心,肯定是和上次不一样的,不然我就不讲了,那这样的雷区改怎么实现呢?当遇到难题时冷静一步步画图思考总是能解决问题的,那我们就先从简单想起吧,肯定是需要二维数组的,那我们先建立一个二维数组,大小就10*10好了,但是不要忘记一个问题,扫雷游戏的规则是格子上的数字会显示周围有几颗雷,我们就不搞那么复杂的了,当遇到雷的时候就显示1,没有雷就显示0,好了我们就一直玩一直玩,哎呀,当遇到边界时会发生什么呢?大家想想雷区只会显示它周围8个方格的雷,但是如果我排到边界的时候地图外的地方是不是也被我纳入排雷范围了,但是我可没有初始化它们,所以它们都是随机值,肯定会对我的排雷产生很大的影响,好,为解决这个问题,我扩大雷区,这个扩大版的雷区只比我打印出来的雷区各边大1,也就是扩大版雷区也会被我全初始化为无雷区,真正的雷区上才会放雷,这样当我点到边界方格它给我返回的也是真正雷区里雷的个数
这样我们就可以创建雷区了,也像昨天一样定义一个二维数组show,然后全局变量ROW,COL,然后再创建一个二维数组mine,然后再创建两个全局变量ROW+1,COL+1,因为我们需要扩大版的雷区,不要忘记头文件的创建和声明哦,像昨天一样,如果忘记就去翻翻昨天的博客吧,然后我们就可以初始化雷区了,InitBoard(mine,ROWS,COLS,'0');InitBoard(show,ROWS,COLS,'*');然后就是InitBoard的写法,用一个很简单的for循环将'0'和'*'放进去
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;
}
}
}
然后就是展示雷区,记住我们的雷区有两个,但是展示给玩家的时候切记不要展示扩大版的,只是我们自己知道有两个就好了,所以你可以看到我的test函数第一个穿mine数组的DisplayBoard函数被注释掉了,DisplayBoard的封装也很简单,由于是二维数组,所以我们需要两个for循环,然后在打印的时候不要自己以为写了两个for循环二维数组就会以网格的形式打印出来,二维数组在我们的逻辑思维里确实是网格状的,但是在内存存储中它其实是连续存放的,说白了和一维数组是一样的连续存放的,所以我们需要在打印完一行后打印一个'\n',然后再打印下一行的内容
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= col;i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <=row;i++)
{
for (j = 1; j <=col;j++)
{
printf("%c",board[i][j]);
}
printf("\n");
}
}
然后就要在雷区里放雷了,我们当然不能自己知道我们创建的是一个10*10的二维数组就只在这100个方格中放,如果我们哪天心大了想玩一个1000*1000的扫雷,难道还需要提前在这1000000个方格里找地方放进去雷?不不不我们依旧用我们的随机生成数,每次玩每次生成雷的个数和地点都是不同的,这才是一款合格的游戏,bug少,这时候srand的注释就可以被拿掉了,我们创建一个全局变量ESAY_BOOMS,然后创建一个int变量booms来接收它,用它来当循环的控制变量,当booms为真时,x和y分别用rand函数%row+1和col+1,得到符合雷区的合法坐标,然后if这个地方没雷,就放上一颗雷,然后boom--,当然ESAT_BOOMS是根据ROW和COL来变化的,它要比ROW和COL大1
void Settingmine(char board[ROWS][COLS], int row, int col)
{
int booms = EASY_BOOMS;
while (booms)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
booms--;
}
}
}
然后就是找雷了,Fidemine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);这里我们要传两个数组的原因就是为了防止点到边界时产生错误,首先进去提示玩家输入两个坐标,如果坐标xy合法的话,也就是x >= 1 && x <= row&&y >= 1 && y <= col,就进入,如果这个地方有雷,提示玩家找雷失败,再需要展示一下雷区然后跳出,如果没有踩雷,需要计算出周围雷的个数,可以创建一个count变量,然后再封装一个get_mine_booms函数,它的作用就是用来返回整个雷区还剩下多少雷,这里用到了递归,无限的套娃,大家一定要自己下来多画图理解一下, 不然很容易绕进去
int get_mine_booms(char mine[ROW][COL],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 Fidemine(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_BOOMS)
{
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
{
int count=get_mine_booms(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(mine, ROW, COL);
}
}
else
{
printf("坐标非法,请重新输入");
}
}
if (win == ROW*COL - EASY_BOOMS)
{
printf("恭喜,你赢了");
DisplayBoard(mine, ROW, COL);
}
}
大家要注意写代码的过程中要自己把main函数写好,然后再调用test函数,也要注意设置游戏的时候也要单独创建一个.c文件,不要全都写道test.c文件里,这里只放main函数就足够,我们现在就要培养起分块工作的思维。
好了,这次的扫雷小游戏就到这里,我们下次再见。
|