对于扫雷游戏,它需要一个有横竖两种方向坐标的,所以我们要用C语言的二维数组了
思路
思路:第一,若我们需要在一个二维数组上实现埋雷查雷显示雷是有点难以实现的,所以我们需要两个二维数组来实现游戏,一个是mine用来存放雷,一个是show用来给玩家探测雷。第二,由于代码量过多,我们放在一个源文件中会有点多,所以我们要拆分成三个文件:game.h(放头文件、常量和game.c里的函数的声明),game.c(放游戏实现的代码,在这里进行函数的定义),text.c(放main函数和是否进入游戏的判断,还有game.c文件中的函数的调用)
首先我们do while实现先输入是否游玩,mune()负责打印菜单栏,再用switch case语句来实现当我们输入不同数字时所进入的不同情况,输入1:进入游部分,输入0:退出游戏,输入其他数字:提示玩家输入错误。
main函数部分
Mune菜单函数代码如下:
游戏代码实现部分
接着我们实现我们的game游戏实现部分:
game.h部分
game部分我们为了提升代码的精简度,我们要创建一个game.h文件,来存放隔周头文件和#define定义的常量,如下图所示:row和col是我们要显示给玩家游玩的长宽,但实际上若我们在查雷的时候,查到边上的位置时,会导致数组越界访问,所以我们就需要扩大我们创建的二维数组,在原基础上+2。
由于我们自己创建了头文件,后续只需要在其他不同.c源文件中加上#include”game.h”就行。(调用自己创建的头文件用“”双引号)
接着,我们在原来的game()函数中先打印mine和show两个二维数组(长宽在game.h中已经定义),然后对这两个二维数组进行初始化,接着打印两个二维数组检测一下初始化是否正确(但是打印mine只是进行检查,后续需要注释掉)。在打印了show数组后,我们要进行埋雷的动作,根据之前的分析,埋雷是在mine中,所以setmine是对mine数组进行操作,在埋雷后,玩家要进查雷,在查雷内部,我们也要判断游戏是否结束。
text.c中的game函数:
Game函数中我们只调用这些不同作用的函数,而函数具体的内容,我们则在game.c文件中进行实现。(即在game.c中进行各种游戏函数的定义)
下面我们来依次介绍game.c文件中每个函数的具体内容:
初始化二维数组
void ChuShiBoard(char board[ROWS][COLS], int rows, int cols,char set):比较简单,就是根据传进来的字符,来对二维数组进行初始化内容。
打印扫雷图
void PrintBoard(char board[ROWS][COLS], int row, int col):是对传入的数组进行打印,首先打印代表列号的数字,方便玩家观察自己选择要查询的地址位置,然后是打印show二维数组的内容(打印内容前先打印代表行号的数字)
埋雷
void SetMine(char board[ROWS][COLS], int row, int col):我们通过rand和srand还有time三者互相结合,实现随机获取一个数字(具体代码:srand((unsigned int)time(NULL));注意srand只能在全部代码前调用一次),这个随机数字%row/col就会得到0~(row-1)的数字,再+1,得到1~row的数字,刚好就是我们需要埋雷的二维数组的中间位置的数组下标,然后我们只要判断这个点是否被埋雷了,来进行藏雷(‘1’表示有雷,‘0’表示没雷)
玩家检测雷
void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col):在我们电脑随机埋雷结束后,玩家要进行查雷了(这是应该循环),首先玩家要输入要查询的位置坐标,且这个坐标不能够超出范围,然后我们进入排查前也要判断一下,玩家输入的这个坐标是否已经被查询过了。接着就进入计算坐标位置的是否是雷了,如果是,则直接退出循环,打印mine数组给玩家看看自己死在了哪里。如果上述条件均没满足,则是要对改坐标位置周围进行雷数查询并记录距离胜利的情况(我们用win来判断胜利情况,若win==row*col-雷数,那我们就输出游戏胜利;用另一个函数来判断改坐标点的雷个数),int MineCount(char mine[ROWS][COLS], int x, int y)具体代码如下:
计算周围雷数
计算周围雷数时,我们之前使用的字符‘0’和字符‘1’就有了用处,由于‘1’-‘0’就等于1,即字符数字减去字符0,就能得到改数字,然后我们把这周围的1全部加起来,就是我们改点雷的数目。
查雷全部代码
判断雷个数实现后,但我们的排查雷只能一个一个排查,很耗时,所以我们接着就要实现传统扫雷中的点一下,若四周没雷,显示一片的效果。
这其实就是一个递归的效果,查到一个坐标点为返回为0(即周围没雷)进入递归函数,把改点从字符‘0’改为字符空格‘ ’;然后对其周围一片区域进行检查,如果没雷,也该为‘ ’,若有雷,则显示其周围雷数。注意,我们也要把win传进来,在每次递归展开的时候,把那些被改为‘ ’的位置也算上,要不然实现不了游戏最终的胜利。
递归展开代码(点一下若没雷,展开一片)
递归首先要限制一定不能越界,只能在棋盘内展开递归,其次,每次递归后都要把原来的改为字符空格‘ ’,而且也要只对未知坐标‘*’展开,防止递归进入死循环。递归直到某次这个被检测的坐标点周围有雷就停止递归。
全部代码内容
text.c
//扫雷
#include"game.h"
//菜单
void mune()
{
printf("*************************\n");
printf("******** 1.Play *******\n");
printf("******** 0.Exit *******\n");
printf("*************************\n");
}
//游戏内容
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
ChuShiBoard(mine, ROWS, COLS, '0');
ChuShiBoard(show, ROWS, COLS, '*');
//打印棋盘
//PrintBoard(mine, ROW, COL);
PrintBoard(show, ROW, COL);
//埋雷
SetMine(mine, ROW, COL);
//PrintBoard(mine, ROW, COL);
//查雷
CheckMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
mune();
printf("是否游玩(0/1):\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
game.c
#include"game.h"
//初始化棋盘
void ChuShiBoard(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 PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("------------扫雷------------\n");
printf("\n");
printf(" ");//为了美观
for (i = 1; i <= row; i++)
{
printf(" %d ", i);
}
printf("\n");//打印完代表行号的数字之后换行
//用i确定行号
for (i = 1; i <= row; i++)
{
int j = 0;
//打印左边第一列表示列号
printf("%d", i );
//用j确定每行打印几列
for (j = 1; j <= col; j++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
printf("\n");
printf("------------扫雷------------\n");
}
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int n = Mine_Easy;//雷数
while (n)
{
x = (rand() % row) + 1;
y = (rand() % col) + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
n--;
}
}
}
//测周围有多少雷
int MineCount(char mine[ROWS][COLS], 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 + 1][y + 1] +
mine[x + 1][y] +
mine[x + 1][y - 1] +
mine[x][y - 1] - 8 * '0';
}
//递归找没雷的一片区域
void ExpandMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) //限制在棋盘内展开,防止越界
{
//重新再获取雷数(因为每次递归要检测)
int count = MineCount(mine, x, y);//获取雷数
if (count == 0) //四周没雷,进入递归展开
{
show[x][y] = ' ';//四周没雷的改为 空格 ' '
int i = 0;
//向四周共8个位置递归调用
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
//只对 '*' 进行展开,防止死循环
if (show[i][j] == '*')
{
ExpandMine(mine, show, i, j, win);
}
}
}
}
else //四周有雷显示雷数
{
show[x][y] = count + '0';
}
//记录展开的数量
(*win)++;
}
}
//查雷
void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (win < row*col-Mine_Easy)
{
printf("请输入要排查的位置坐标:\n");
scanf("%d %d", &x, &y);
if ((x >= 1 && x <= row) && (y >= 1 && y <= col))
{
//看看是否已经排查过了
if (show[x][y] == '*')
{
if (mine[x][y] == '0')
{
//没被炸死
int a = 0;
a = MineCount(mine, x, y);
//周围没雷进入扩展函数递归
if (MineCount(mine, x, y) == 0)
{
ExpandMine(mine, show, x, y,&win);
PrintBoard(show, ROW, COL);
}
else
{
show[x][y] = a + '0';
PrintBoard(show, ROW, COL);
}
win++;
}
else
{
printf("很遗憾你被炸死了\n");
PrintBoard(mine, ROW, COL);
break;
}
}
else
printf("已经排查过了,请重新输入坐标\n");
}
else
printf("输入坐标超出范围,请重新输入\n");
}
if (win == row*col - Mine_Easy)
{
printf("你赢了\n");
PrintBoard(mine, ROW, COL);
}
}
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 Mine_Easy 10
//初始化棋盘
void ChuShiBoard(char board[ROWS][COLS],int rows, int cols,char set);
//打印棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col);
//查雷
void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
结语
到这里扫雷的全部代码和设计思路已经全部结束了,如果这篇文章对大家有用的话,希望大家多多点赞支持!