在学习了C语言数组和函数后,我们就可以做一个有意思和非常经典的游戏——扫雷,那么就跟随这一篇文章来看看是如何实现扫雷的吧,在正式开始之前我们先分析一下扫雷是如何实现的
扫雷游戏分析和设计
扫雷游戏的功能说明
- 使⽤控制台实现经典的扫雷游戏
- 游戏可以通过菜单实现继续玩或者退出游戏
- 扫雷的棋盘是9*9的格⼦
- 默认随机布置10个雷
- 可以排查雷
- 如果位置不是雷,就显⽰周围有⼏个雷
- 如果位置是雷,就炸死游戏结束
- 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
那我们最终的游戏界面是这样子的:
游戏的分析和设计
数据结构的分析
这是一个扫雷游戏的布局,我们可以发现整个棋局是由9*9大小的方框组成的,那么此时我们就可以想到,可以利用到数组来实现
在这张图中,我们顺便点了一处,然后就摊开一片,并且有数字,那我们可以想到:
- 如果排查的位置不是雷,那么就显示这个坐标周围有几个雷
- 如果排除的位置是雷,就被炸死了,那么游戏就结束了
那么再由失败的界面我们可以看到一共有10个雷,那么如果把10个雷全部排查出来,游戏就胜利了
雷
那么我们来解决雷的问题,我们排查一个坐标时,那个坐标是不是雷,那么就需要一个9*9的二维数组来记录了
布置雷的棋盘
我们假设:1是雷0不是雷
那么当我们点击一个位置时,就应该显示周围有几个雷,但是如果周围是一个雷的话,那么显示的也是1,此时就会有歧义,这个到底是雷还是周围的雷?所以我们可以在创建一个二维数组来专门存放排查出的雷的信息
那么此时左边的这个图的数组就可以安心的存放雷的信息,右边这个图的数组就专门来显示排查出雷的信息
这个时候雷的棋盘还有一个问题,在扫雷游戏中, 如果排查的不是雷,是会显示的周围8个坐标是否是雷的信息的,那么此时我们设计的棋局,如果排查到边缘,也应该去排查周围8个坐标的信息,但是这样二维数组就越界了
所以我们不能创建9*9的棋局,而且是11*11的棋局,最外围的11行11列不去布置雷,放0,这样我们排查边缘上的坐标就不担心越界了,相同的道理,排查出的雷的信息这个棋局也应该设置为11*11的布局
布置雷 排查出雷的信息
- 那我们将布置好雷的信息放入一个叫做mine数组中
- 将排查出的雷的信息放入一个叫做show数组中
- 那我们在最初还没有开始放雷的情况下,应该将mine数组全部初始化为0
- 对于show来说,最初一个雷都还没有排查的时候,应该全部放*
为了方便打印,因为show数组放的是字符 * 所以我们也将数字0 变成字符 0,同样雷也是字符1,排查后显示周围有几个雷的数字也改为数字字符
在正式进入代码前我们将代码分为三个文件进行存储:
- test.c—测试游戏逻辑 游戏相关的其他逻辑放在这里
- game.c—游戏实现
- game.h—游戏函数的声明
其中会涉及到的函数:
- menu()—放菜单
- game()—放游戏主体
- char mine[ROWS][COLS];—存放雷的信息
- char show[ROWS][COLS];—存放排查雷的信息
- InitBoard—初始化棋盘
- DisplayBoard—打印棋盘
- SetMine—实现布置雷
- FindMine—排查雷
- GetMineCount—显示坐标周围的雷
那我们进入代码:
test.c
#include <stdio.h>
#include "game.h"
void menu()
{
printf("*************************\n");
printf("***** 1.玩游戏 ****\n");
printf("***** 0.退出游戏 *****\n");
printf("*************************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine,ROWS,COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//棋盘的打印
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);//可以打印出来看一下雷布置在哪里
//排查雷
FindMine(mine,show,ROW,COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("\n退出游戏\n");
break;
default:
printf("请输入正确的值!\n");
break;
}
}
while (input);
{
}
return 0;
}
详解:从main函数开始,按照游戏的运行逻辑来一一解释
#include "game.h":调用game.h的头文件
main
- int input = 0:用户选择菜单,之所以放在外面是为了更加全局一点,让while好判断
- srand((unsigned int)time(NULL)):定义随机值让后面布置的雷随机
- do while:用do while循环来让用户进行菜单的选择,如果选择错误或游戏可以重新选择
- menu():调用菜单
- scanf("%d", &input):存放用户输入的值
- switch (input):根据用户输入不同,进入不同的结果
- game():调用游戏的主体
- break:只会跳出Switch
- default:如果用户输入了非0非1的其他值,要求用户重新输入,那么既然不会是0了那么就不会退出while循环的
- while (input):如果用户选择0,那么跳出Switch,来到while,while判断为假,停止循环,退出游戏
menu
单纯的打印菜单
game
- 首先先创建两个数组:
- char mine[ROWS][COLS]:布置雷
- char show[ROWS][COLS]:排查雷的信息
- InitBoard(mine,ROWS,COLS,'0'):调用初始化函数,'0'表示需要初始化为字符0,我们传参过去,方便依照这个初始化
- InitBoard(show, ROWS, COLS,'*'):'*'表示需要初始化为字符*,我们传参过去,方便依照这个初始化
棋盘的打印
- DisplayBoard(show, ROW, COL):只需打印中间的就行了,所以我们只需要 9*9 那么传参就不用传带s的了
布置雷
- SetMine(mine, ROW, COL):将关于雷的数组传参过去,布置也只需要9*9的就行了什么我们不用传带s的了
- DisplayBoard(mine, ROW, COL):这个可以打印出来看一下雷布置在哪里
排查雷
- FindMine(mine,show,ROW,COL):排查雷既需要mine数组的雷的信息,也要show排查后的信息,然后排查雷只需9*9就行了
game.h
这里面主要是放的函数的声明
代码:
#include <stdio.h>
#include <stdlib.h>//因为主函数要调用game.h这个头文件,所以在这里调用,也相当于在test.c中调用
#include<time.h>
//用define来定义关键字,可以在后期方便改变游戏难度,例如:初级,中等,高级等.....
//定义在头文件是为了方便调用
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//函数的声明
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);//接收传过来的参数,set用来接收mine和show需要初始化什么
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int rows, int cols);
//注意:我们在传参的时候,可以传11*11,但是操作的时候只操作9*9就行了
//布置雷
#define EASY_COUNT 10 //设雷为10个
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);
详解在代码中的注释里
game.c
#include <stdio.h>
#include "game.h"//调用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++)
{
//mine 初始化为0
//show 初始化为*
board[i][j] = set;
}
}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("————扫雷游戏————\n");
for (i = 0; i <= row; i++) //打印列标
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);//打印行标
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("————扫雷游戏————\n\n\n");
}
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] =='0')
{
mine[x][y] ='1';
count--;
}
}
}
//排查雷
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
//让8个方位的坐标加起来,并且返回
return( 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] +
mine[x - 1][y]-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)
{
printf("请输入要排查的坐标(行坐标+空格+列坐标):");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("\n\n\n排查的是雷!游戏失败!\n\n\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count=GetMineCount(mine, x, y);
show[x][y]=count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("请输入合法的坐标!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,游戏胜利!\n");
DisplayBoard(mine, ROW, COL);
}
}
详解:
InitBoard
初始化函数:
- void InitBoard(char board[ROWS][COLS], int rows, int cols,char set):接收test中的game传过来的参数,用board接受mine和show,行接收行,列接受列,set接收需要初始化的类型
- int i = 0;for ( i = 0; i <rows; i++):初始化行
- int j = 0; for (j = 0; j < cols; j++):初始化列
- board[i][j] = set:接收传参过来的set,mine传参过来的是字符0,那么初始化为字符0,show传参过来的是*,那么初始化为*
DisplayBoard
打印棋盘函数:
- void DisplayBoard(char board[ROWS][COLS], int row, int col):接收test中的game传过来的参数,用board接受show,行接收行,列接受列,因为只需要打印中间的棋盘,不需要打印外围的大数组棋盘,所以就行列只用接收9*9就行了
- for (i = 0; i <= row; i++):打印列标,让i=0是因为,i=1的话列标对应会错位,第8列会显示9
- printf("\n"):打印完列标后换行,准备打印棋盘
- for (j = 1; j <= col; j++):初始化为1,是因为大数组11*11,我们只显示9*9,所以第一个格子的二维数组坐标就是[1][1]
- printf("%c ", board[i][j]):打印棋盘内容
- printf("\n"):打印一行后换行
SetMine
布置雷函数:
- void SetMine(char mine[ROWS][COLS], int row, int col):接收test中的game传过来的参数,用mine接受show,行接收行,列接受列,因为只需要将雷布置到中间的9*9就行了,不需要布置在外围的大数组棋盘,所以就行列只用接收9*9就行了
- int count = EASY_COUNT:定义一个count来接受game.h中设定的10个雷
- while (count):开始随机坐标布置雷
- int x = rand() % row + 1;int y = rand() % col + 1:&row是让雷的范围为0~9,加上1就是1~10,将雷随机布置到x,y坐标处
- if (mine[x][y] =='0'):判断mine数组中雷的情况,如果该坐标没有布置雷,那么就布置一颗雷,如果布置了,那么不在此位置布置雷了
- count--:每布置一颗雷就减少一颗,总共10颗
GetMineCount
排查雷函数:
- int GetMineCount(char mine[ROWS][COLS], int x, int y):排查周围8个方格的雷, 接收game.c中的FindMine传过来的参数,用mine接受mine,行接收行,列接受列,因为只需要将雷布置到中间的9*9就行了,不需要布置在外围的大数组棋盘,所以就行列只用接收9*9就行
- return( 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] +mine[x - 1][y]-8*'0'):让数字变为数字字符(在show[x][y]中会有解释),让8个方位的坐标加起来,并且返回
- void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col):排查雷既需要mine数组的雷的信息,也要show排查后的信息,所以接受test.c的game传来的两个数组,然后排查雷只需9*9就行了
- int x = 0;int y = 0;int win = 0:定义x和y作为坐标,win作为游戏胜利的判断条件
- while (win<row*col-EASY_COUNT):开始循环的进行排查雷,row*col-EASY_COUNT是9*9-10=71也就是不是雷的格子,如果win小于71,那么就进入循环
- if (x >= 1 && x <= row && y >= 1 && y <= col):判断用户输入坐标的合法性
- if (mine[x][y] == '1'):看用户排查的坐标是不是雷
- DisplayBoard(mine, ROW, COL):游戏失败后,将雷全部显示出来
- Else:如果不是雷,在该坐标显示周围的雷的个数
- int count=GetMineCount(mine, x, y):调用函数去访问mine数组来获取当前x,y周围有几个雷,统计出来赋予count
- show[x][y]=count + '0':我们知道,数字字符0是从48开始的,那就有个规律,假如说字符3减去字符1那么就是'3'-'0'=51-48=3 等于数字3,那我们这里假设统计出来雷的个数count为数字1,那么让它加上一个字符0,就可以得到字符1了,再将字符放入排查出的雷的信息show数组中,就可以在该坐标显示周围的雷的个数了
- DisplayBoard(show, ROW, COL):然后打印show数组,就可以让用户看到当前坐标的信息了
- win++:每一次排查成功我们就让不是雷的格子自增1,如果不雷的格子大于了71,说明雷排除完了,游戏就赢了
- if (win == row * col - EASY_COUNT):如果不是雷的格子等于71了,那么说明雷排除完了游戏赢了
- DisplayBoard(mine, ROW, COL):游戏胜利后将雷的位置全部显示出来
好了到这里就结束了
游戏还不完善比如说:不能展开一大片,不能标记,不能用鼠标点击,还不能设置游戏等级等
这些在后期会跟新出来的...
如有错误请指出