目录
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
int get_mine_count(char mine[ROWS][COLS], int x, int y)
void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
int NoMineCount(char show[ROWS][COLS],int row,int col)
一、游戏规则
1、扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。
2、游戏主区域由很多个方格组成。
选择一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷;如果点开的格子为空白格,即其周围有0颗雷,则其周围格子自动打开;
3、1代表1的上下左右及斜角合计有一颗雷,依次轮推,2则有2颗,3则有3颗。4、胜利条件为打开除了雷以外的全部方框。
二、项目结构
和三子棋一样,一共有三个文件:
分别是
主文件:game.cpp//写函数的具体实现
头文件:game.h//放置define常量和函数声明
测试文件:test.cpp//放入对应头文件后,引用函数即可
三、整体逻辑和局部函数分析
游戏运行整体逻辑、test函数和菜单函数请参照:
C语言入门笔记 小游戏开发 【三子棋】
这里我们直接讲扫雷的逻辑函数——game()。
void game()
void game()
{
//雷的信息存储
//1.布置好的雷的信息
char mine[ROWS][COLS] = { 0 };
//2.排查出的雷的信息
char show[ROWS][COLS] = { 0 };
//初始化雷棋盘
InitBoard(mine, ROWS, COLS, '0');
//初始化玩家棋盘
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//查找雷,mine里找信息,然后传到show数组里
FindMine(mine, show, ROW, COL);
}
对于扫雷这个游戏,我们首先需要:
创建一个二维数组,布置好雷的信息。
然而, 如果只构造这一个数组,不管是存储,还是修改数据,都很不方便,
所以这里我们构造一个雷数组,一个玩家数组。
通过对雷数组的判定,再传递信息给玩家数组,最后展示玩家数组即可。
假如整个区域是9×9,
那么对于边界雷区的判定不太方便。
所以这里我们设置总的数组大小为11×11,
最外一圈全部设置为无雷,
最后展示给玩家的为9×9的数组即可。
创建好雷数组、玩家数组两个数组后,我们需要用到一个初始化雷棋盘的函数。
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
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;
}
}
}
至于set,这是一个字符型变量,用于告诉我们将棋盘初始化成什么样子。
为了区分,
我们将雷棋盘一开始全放0,
将玩家棋盘一开始全放字符‘*’。
然后打印棋盘:
void DisplayBoard(char board[ROWS][COLS], int row, int col);
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++)
{
printf("%d ", i);//打印行号
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
因为我们只需要展示棋盘的内圈,
所以传入的参数是行列均为9,
for循环从1开始即可。
然后我们需要布置雷:
SetMine(mine, ROW, COL)
void SetMine(char board[ROWS][COLS], int row, int col)
{
int number = EASY_COUNT;
while (number)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
number--;
}
}
}
这里还是用到了随机数函数,
随机将10个0置为1。
然后是本游戏的核心函数:扫雷函数。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
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");
DisplayBoard(mine, row, col);
break;
}
else//不是雷,统计雷
{
//计算x,y坐标周围有几个雷
NoMine(mine, show, x, y);
DisplayBoard(show, row, col);
win = NoMineCount(show, row, col);
printf("已打开区域:%d\n", win);
}
}
else
{
printf("输入坐标非法,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功!\n");
}
}
首先进行合法性判断,
然后进入一个while循环。
我们设置一个win变量,用来记录已经扫过的安全区。
这里循环结束的条件是:
1、已扫安全区大于等于全部安全区;
2、扫到了雷。
对于不是雷的区域,
我们首先写一个函数来判断他周围有多少颗雷。
int get_mine_count(char mine[ROWS][COLS], int x, int y)
int get_mine_count(char mine[ROWS][COLS], int x, int y)//因为字符0和字符1的ASCII码之间相差1,所以可以加起来再减去8个0的ASCII码和即可。
{
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';
}
这个函数很巧妙,避免了对每个格子的遍历,
直接返回周边八个格子的ASCII码减去0的ASCII码,因为数字的ASCII码是连续存放的,
举个例子,
该格子的上面不是雷,那么放的就是0,‘0’-‘0’=0;
如果上面是雷,那么放的就是1,‘1’-‘0’=1
巧妙地返回了整型。
接下来,我们写一个展开无雷区函数:
void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int sum = get_mine_count(mine,x,y);
if (sum == 0)
{
show[x][y] = ' ';
if ((x - 1) > 0 && (y - 1) > 0 && (show[x - 1][y - 1] == '*'))
NoMine(mine, show, x - 1, y - 1);
if ((x - 1) > 0 && (y) > 0 && (show[x - 1][y] == '*'))
NoMine(mine, show, x - 1, y);
if ((x - 1) > 0 && (y + 1) > 0 && (show[x - 1][y + 1] == '*'))
NoMine(mine, show, x - 1, y + 1);
if ((x) > 0 && (y - 1) > 0 && (show[x][y - 1] == '*'))
NoMine(mine, show, x, y - 1);
if ((x) > 0 && (y + 1) > 0 && (show[x][y + 1] == '*'))
NoMine(mine, show, x, y + 1);
if ((x + 1) > 0 && (y - 1) > 0 && (show[x + 1][y - 1] == '*'))
NoMine(mine, show, x + 1, y - 1);
if ((x + 1) > 0 && (y) > 0 && (show[x + 1][y] == '*'))
NoMine(mine, show, x + 1, y);
if ((x + 1) > 0 && (y + 1) > 0 && (show[x + 1][y + 1] == '*'))
NoMine(mine, show, x + 1, y + 1);
}
else
show[x][y] = sum + '0';
}
这个函数用到了递归思想。
首先用返回雷数函数,判断一下自己周围是否有雷区。
如果有,那么就把自己的值设置为‘0’+雷数,
设置完以后跳出该函数,进行下一步;
如果没有雷区,那么首先将自己的值设置为‘ ’,也就是一个空格符,
然后依次递归他周围的八个格子,
直到成为了周边有雷区的格子为止。
遍历完成后,再次调用展示棋盘函数,并计算win的值。
这里win的值我用了依次遍历法。
int NoMineCount(char show[ROWS][COLS],int row,int col)
int NoMineCount(char show[ROWS][COLS],int row,int col)
{
int i = 0;
int count = 0;
for (i = 1; i <= row; i++)
{
int j = 0;
for (j = 1; j <= col; j++)
{
if (show[i][j] != '*')
{
count++;
}
}
}
return count;
}
这个办法挺笨的,就是依次判断展示棋盘的字符是否是‘*’。
不过目前没有想到更好的办法,暂时是最优解法,并且时间复杂度也不高。
接下来,会继续进入while循环,
直到游戏结束的两个标志中的一个成立。
最后给出程序运行截图:
四、个人心得
写的第二个游戏项目了,这个项目里有几个点我觉得非常精彩:
首先是用两个棋盘,一个雷区,一个玩家区,通过对雷区的判断修改玩家区的内容,解决了传值和改值不方便这两大问题。
第二是在我们需要的棋盘外再加一圈。这使边界数组元素和中间数组元素可以进行统一判雷。
第三是使用递归的思想来开辟无雷区。
这个项目真的很值得去写,你可以收获递归算法思想和一些关于设计游戏地图坐标的思想。