目录
实现思路
回忆一下,我们曾经在windows上玩的小游戏,可以知道,我们需要一个数组布置雷。另外当我们进行扫雷时,每进行一次,我们需要更新一次扫雷时的雷阵信息。因此我们需要两个二维数组。mine[ ][ ]数组用于存放布置雷时的信息。show[ ][ ]用来进行扫雷时,更新雷阵的信息。这里我们需要注意的是: 当进行扫雷时,如果没有雷,需要统计周围八个位置雷的数量,并在show[ ][ ]数组中显示。这里就需要注意一个问题,怎样统计周围边缘部分的雷呢?我们不妨将数组周围扩大一圈。布置雷时周围一圈不放置雷。这样以来,在排查雷,打印雷的信息时,我们不打印外围的一圈。(例如:我们定义二维数组元素个数是11x11,而实际打印时,仅打印9x9的数组)下面我们梳理一下扫雷的流程。开始时,第一次扫雷,如果踩雷,为了增加游戏的可玩性,我们将雷重新布置位置,将利用一个NewSetMine()函数,重新布置第一个雷。如果不是第一次踩雷,那么很遗憾游戏结束。在扫雷的过程中,如果没有雷需要统计周围八个位置雷的数量,并显示。如果周围也没有雷,我们就用递归函数将雷阵展开。以此类推,直到排查出所有的雷游戏结束。
难点1 在扫雷过程中,第一次踩雷,要重新布置雷。
难点2 当周围没有雷时,要递归展开。
具体实现
我们采取分文件编写。分别新建 test.c源文件,game.h头文件,game.c源文件。
test.c中包含程序的整体框架。game.h中存放函数的定义。game.c中是对于自定义函数的具体实现。
主体框架
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void memu()
{
printf("**********************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("**********************\n");
}
int main()
{
int input = 0;
do
{
memu();
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入有误重新输入\n");
break;
}
} while (input);
return 0;
}
效果
函数细节实现
接下来定义数组
//定义mine[][]用于存放布置雷的信息
char mine[ROWS][COLS] = {0};
//定义show[][]用于存放排查雷后每一次更新的信息
char show[ROWS][COLS] = {0};
值得注意的是ROWS COLS 是宏定义方便更改数组的大小
创建InitBoard()函数对数组进行初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
在game.h文件中写入对初始化函数的定义
void InitBoard(char* board[ROWS][COLS],int row,int col,char ch);
在game.c文件中对其进行实现
//初始化函数,分别初始化mine、show 数组
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
int i = 0;
for (i = 0; i < row; ++i)
{
int j = 0;
for (j = 0; j < col; ++j)
{
board[i][j] = ch;
}
}
}
创建打印函数PrintBoard()并测试打印后的效果
PrintBoard(mine, ROW, COL);
PrintBoard(show, ROW, COL);
同样的道理,在game.h中定义,在game.c中具体实现,在以下的每个函数就不在一一强调
void PrintBoard(char board[ROWS][COLS], int row, int col);
//打印函数
void PrintBoard(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)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; ++j)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-----------------------------\n");
}
我们来看一下效果
mine 数组初始化时全部放了字符’0’,而 show 数组全部放了字符’*’。
创建SetMine()函数布置雷
这里我们仅布置10个雷。在game.h文件中采取宏定义的方式
#define EASY_COUNT 10
定义SetMine()函数
void SetMine(char board[ROWS][COLS], int row, int col);
实现SetMine()函数
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
count--;
}
}
}
我们注意到以上的函数是采用随机的方式布置10个雷。这就离不开随机数生成器。不要忘记在主函数中加入下面一行代码:
srand((unsigned int)time(NULL));
创建函数FindMine()实现排雷的过程
接下来就是最重要的。我们要开始排雷了。这才是游戏的关键所在。
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col);
FindMine()函数实现
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 0;//flag = 0表示第一次就踩到雷
while (1)
{
printf("请输入要排查的坐标:>\n");
scanf("%d%d", &x, &y);
//判断坐标是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1' && flag == 0)
{
//此时表示第一次踩到雷,为了增加游戏的可玩性,我们让游戏第一次踩不到雷。
//将雷移到别的地方。
flag = 1;
mine[x][y] = '0';
//用NewSetMine()函数重新布置第一个雷
NewSetMine(mine,row,col, x, y);
//OpenMine()递归函数统计x,y 坐标外8个位置是否有雷如果有雷就展开。
OpenMine(mine, show, x, y);
//打印排查后雷阵的信息
PrintBoard(show, row, col);
}
else if (mine[x][y] == '1' && flag == 1)
{
//此时表示不是第一次踩到雷,那么游戏结束 break 退出游戏
printf("很遗憾你被炸死了!\n");
printf("雷的分布如下:\n");
PrintBoard(mine, row, col);
break;
}
else
{
//此时没有踩到雷,游戏继续
flag = 1;
OpenMine(mine, show, x, y);
PrintBoard(show, row, col);
}
}
else
{
printf("坐标不合法!请重新输入:>\n");
}
}
if (IsWin(show, row, col) == EASY_COUNT)
{
printf("恭喜你!你赢了\n");
printf("雷的分布如下:\n");
PrintBoard(mine, row, col);
}
}
我们注意到,上述的函数中,包含了四个函数没有实现。分别是,第一踩雷时,我们要从新布置雷,用到了 NewSetMine()函数,没有排查到雷时,用到的递归展开函数 OpenMine(),统计雷的个数GetMineCount()函数,以及判断输赢函数 IsWin()。这里需要说明: 我在实现在这些函数时,直接就在调用函数之前就实现了,因此没有在game.h中定义函数。如果想看起来规整,可以在.h文件中声明。下面来具体实现一下以上四个函数。
重新设置第一个雷用到的函数NewSetMine()
void NewSetMine(char mine[ROWS][COLS],int row,int col, int x, int y)
{
int count = 1;
while (count)
{
int a = rand() % row + 1;
int b = rand() % col + 1;
if (mine[a][b] != '1' && x != a && y != b)
{
mine[a][b] = '1';
count--;
}
}
}
递归展开函数OpenMine()
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//统计周围8个位置雷的个数如果为0则递归展开,否则 不展开仅统计个数
if (GetMineCount(mine, x, y) == 0)
{
//递归展开
//现将x,y 中心位置置为空格
show[x][y] = ' ';
if (x - 1 > 0 && y > 0 && show[x - 1][y] == '*')
{
OpenMine(mine, show, x-1, y);
}
if (x - 1 > 0 && y - 1 > 0 && show[x - 1][y - 1] == '*')
{
OpenMine(mine, show, x-1, y-1);
}
if (x - 1 > 0 && y + 1 > 0 && show[x - 1][y + 1] == '*')
{
OpenMine(mine, show, x-1, y+1);
}
if (x > 0 && y-1 > 0 && show[x][y-1] == '*')
{
OpenMine(mine, show, x, y-1);
}
if (x > 0 && y+1 > 0 && show[x][y+1] == '*')
{
OpenMine(mine, show, x, y+1);
}
if (x + 1 > 0 && y > 0 && show[x + 1][y] == '*')
{
OpenMine(mine, show, x+1, y);
}
if (x + 1 > 0 && y-1 > 0 && show[x + 1][y-1] == '*')
{
OpenMine(mine, show, x+1, y-1);
}
if (x + 1 > 0 && y+1 > 0 && show[x + 1][y+1] == '*')
{
OpenMine(mine, show, x+1, y+1);
}
}
else
{
//不展开仅统计个数
show[x][y] = GetMineCount(mine, x, y) + '0';
}
}
另外附一张展开时的图片看一下展开效果。
统计雷的个数函数GetMineCount()
int GetMineCount(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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1]) - 8 *'0');
}
或许这里对于像我一样刚学习C语言的小伙伴不知道为什么反回的位置有 - 8*‘0’。
这里要说一下,在c语言中数字字符减去字符零会得到整形的数字。例如字符’5’-‘0’=5,这个5就是整形 我们定义的数组是char 形数组,存放的是字符,在统计雷的个数时,存放字符 ‘1’ 表示有雷,字符 ‘0’ b表示没有雷,这样周围的8个字符加起来减去 8*‘0’ 就的到了整形数字,return 返回,返回值类型为 int 。
判断输赢函数IsWin()
int IsWin(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;
}
以上我们实现了所有的函数模块。说点题外话,本人也是刚刚学习C语言,对于扫雷的认知,仅限于此。其中或许有不当的地方,希望大家谅解,仅供大家参考。另外欢迎大家批判指正。你的建议会是我坚持下去的动力。
接下来附上 game.h game.c test.c的C语言源码供大家整体把握实现的思路。
game.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void InitBoard(char board[ROWS][COLS], int row, int col, char ch);
void PrintBoard(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);
game.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化函数,分别初始化mine、show 数组
void InitBoard(char board[ROWS][COLS], int row, int col, char ch)
{
int i = 0;
for (i = 0; i < row; ++i)
{
int j = 0;
for (j = 0; j < col; ++j)
{
board[i][j] = ch;
}
}
}
//打印函数
void PrintBoard(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)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; ++j)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-----------------------------\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
count--;
}
}
}
void NewSetMine(char mine[ROWS][COLS],int row,int col, int x, int y)
{
int count = 1;
while (count)
{
int a = rand() % row + 1;
int b = rand() % col + 1;
if (mine[a][b] != '1' && x != a && y != b)
{
mine[a][b] = '1';
count--;
}
}
}
//统计周围雷的个数
int GetMineCount(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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1]) - 8 *'0');
}
//递归函数展开雷阵信息
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
//统计周围8个位置雷的个数如果为0则递归展开,否则 不展开仅统计个数
if (GetMineCount(mine, x, y) == 0)
{
//递归展开
//现将x,y 中心位置置为空格
show[x][y] = ' ';
if (x - 1 > 0 && y > 0 && show[x - 1][y] == '*')
{
OpenMine(mine, show, x-1, y);
}
if (x - 1 > 0 && y - 1 > 0 && show[x - 1][y - 1] == '*')
{
OpenMine(mine, show, x-1, y-1);
}
if (x - 1 > 0 && y + 1 > 0 && show[x - 1][y + 1] == '*')
{
OpenMine(mine, show, x-1, y+1);
}
if (x > 0 && y-1 > 0 && show[x][y-1] == '*')
{
OpenMine(mine, show, x, y-1);
}
if (x > 0 && y+1 > 0 && show[x][y+1] == '*')
{
OpenMine(mine, show, x, y+1);
}
if (x + 1 > 0 && y > 0 && show[x + 1][y] == '*')
{
OpenMine(mine, show, x+1, y);
}
if (x + 1 > 0 && y-1 > 0 && show[x + 1][y-1] == '*')
{
OpenMine(mine, show, x+1, y-1);
}
if (x + 1 > 0 && y+1 > 0 && show[x + 1][y+1] == '*')
{
OpenMine(mine, show, x+1, y+1);
}
}
else
{
//不展开仅统计个数
show[x][y] = GetMineCount(mine, x, y) + '0';
}
}
int IsWin(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;
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 0;//flag = 0表示第一次就踩到雷
while (1)
{
printf("请输入要排查的坐标:>\n");
scanf("%d%d", &x, &y);
//判断坐标是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1' && flag == 0)
{
//此时表示第一次踩到雷,为了增加游戏的可玩性,我们让游戏第一次踩不到雷。
//将雷移到别的地方。
flag = 1;
mine[x][y] = '0';
//用NewSetMine()函数重新布置第一个雷
NewSetMine(mine,row,col, x, y);
//OpenMine()递归函数统计x,y 坐标外8个位置是否有雷如果有雷就展开。
OpenMine(mine, show, x, y);
//打印排查后雷阵的信息
PrintBoard(show, row, col);
}
else if (mine[x][y] == '1' && flag == 1)
{
//此时表示不是第一次踩到雷,那么游戏结束 break 退出游戏
printf("很遗憾你被炸死了!\n");
printf("雷的分布如下:\n");
PrintBoard(mine, row, col);
break;
}
else
{
//此时没有踩到雷,游戏继续
flag = 1;
OpenMine(mine, show, x, y);
PrintBoard(show, row, col);
}
}
else
{
printf("坐标不合法!请重新输入:>\n");
}
}
if (IsWin(show, row, col) == EASY_COUNT)
{
printf("恭喜你!你赢了\n");
printf("雷的分布如下:\n");
PrintBoard(mine, row, col);
}
}
test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//扫雷游戏
void memu()
{
printf("*******************\n");
printf("***** 1.game *****\n");
printf("***** 0.exit *****\n");
printf("*******************\n");
}
void game()
{
//定义mine数组用于存放雷的信息
char mine[ROWS][COLS] = { 0 };
//定义show数组用于存放排查雷时的信息
char show[ROWS][COLS] = { 0 };
//分别初始化数组mine数组没有雷时全部存放字符‘0’
//show 数组当开始扫雷前 全部显示字符‘*’
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//PrintBoard()函数,用于打印扫雷时,每一次扫雷后对应的雷阵信息
//PrintBoard(mine, ROW, COL);
//PrintBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//打印雷阵测试,游戏开始时应该屏蔽
//PrintBoard(mine, ROW, COL);
PrintBoard(show, ROW, COL);
//FindMine()扫雷函数
FindMine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
memu();
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入有误!请重新输入\n");
break;
}
} while (input);
return 0;
}