本笔记参考B站up鹏哥C语言的视频
且该笔记内写入方法只是实现目标方法的其中一种。
要求与准备
菜单
主函数基本思路
代码(主函数思想)
扫雷游戏思路
进一步思考
初始化棋盘
运用函数[InitBoard]
打印棋盘
运用函数[DisplayBoard]
布置雷
运用函数[SetMine]
排查雷
运用函数[FindMine]
函数[get_mine_count] 部分
判断胜利
额外
可优化处
1.
源代码
game.h
test.c
game.c
- test.c - 扫雷游戏的测试
- game.c - 游戏的函数实现
- game.h - 游戏的函数的声明
代码
void menu()
{
printf("**********************************\n");
printf("**** play:输入1 ****\n");
printf("**** exit:输入0 ****\n");
printf("**********************************\n");
}
规定1 - 玩游戏; 2 - 退出游戏
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("扫雷\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);//为了多次游玩,选择 do while 语句
return 0;
}
选择一个方块:
- 如果是雷,游戏结束;
- 如果不是雷,反馈出方块周围一圈上有几个雷
- 当找出除了雷之外的所有格子,游戏结束
- 当发现不是
这里制作9*9的格子
但是 如果格子内放1,难以区分这个1是格子内是雷,或者是不是雷时反馈回来的1(存在歧义)
注:棋盘上的三种信息 - 雷、非雷、反馈(相关信息过多)
解决
再创建一个数组:专门存放反馈出的信息。(第一个数字专门存放雷)
即要创建两个数组。
结论:如果想实现9*9的棋盘,数组设置为11*11较好。
实践(创建数组)
char mine[ROWS][COLS];//存放布置好的雷的消息
char show[ROWS][COLS];//存放排查出来的信息
注意:这里的 ROWS 及 COLS 均是在 game.h 中定义的变量,目的是方便后期改变棋盘大小。
game.h 中
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
当然,在使用变量时得引头文件。
放雷棋盘[mine]:全部初始化为 字符0
反馈数组[show]:全部初始化为 '*'
在 game.h 部分定义函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
------
test.c 部分(由于函数定义位于 game.h 部分,所有使用时别忘了引头文件)
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
(最后输入初始化棋盘时使用的元素)
必须注意:在每一次调用变量时,必须注意 ROW 和 ROWS、COL 和 COLS 使用情况的区别。
------
game.c 部分
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;
}
}
}
把数组每一个元素初始化。
game.h - 定义函数
void DisplayBoard(char board[ROWS][COLS], int row, int col);
------
test.c - 将函数写入流程
DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
(注意:这里将两个棋盘都打印了,但是实际上出现在玩家的屏幕上的是[show]的打印效果)
------
game.c - 编写函数本体
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
//打印列号
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");
}
printf("------扫雷游戏------\n");
}
依旧是一个一个打印出数组的每一个元素。而为了界面美观,多打印了行和列的数字,效果如下:
(本处只显示打印反馈数列的效果。)
将雷的信息传入存雷数组中。
注:
- rand函数、srand函数 对应的头文件为
- time函数 对应的头文件为
- srand函数 设置随机数的生成器,rand函数 生成随机数,rand() % 常量 限制随机数的生成范围;
- time函数 获得时间戳。
在 game.h 中
void SetMine(char mine[ROWS][COLS], int row, int col);
------
在 test.c 中
SetMine(mine, ROW, COL);
不过除此之外,还要生成随机数。所以在 test.c 的主函数中设置
srand((unsigned int)time(NULL));
注意:这个函数得放在主函数一开始的位置,因为随机数在一次程序运行中只生成一次。
------
在 game.c 中
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = 10;
while (count)
{
//生成随机的下标
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')//字符0
{
mine[x][y] = '1';//字符1
count--;
}
}
}
使用 count 作为计数器,总共10个雷,每成功放入一个雷,count的值就会发生相应的改变。
过程:
- 输入排查的坐标
- 检查坐标(是否为0)
(1)是雷 - 玩家失败 - 游戏结束
(2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组- 游戏继续
在 game.h 中
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
在 test.c 中加入排查雷的步骤
FindMine(mine, show, ROW, COL);
在 game.c 中进行函数的实现
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//过程:
// 1.输入排查的坐标
// 2.检查坐标(是否为0)
// (1)是雷 - 玩家失败 - 游戏结束
// (2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组 - 游戏继续
int x = 0;
int y = 0;
while(1)//因为排雷是重复过程,使用使用 while循环
{
printf("请输入要排查的雷的目标:");
scanf("%d%d", &x, &y);//坐标范围:x - (1,9); y - (1,9)
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,踩到雷了\n");
DisplayBoard(mine, row, col);
break;
}
else
{
//不是雷的情况 - 统计坐标(x,y)周围有几个雷
int count = get_mine_count(mine, x, y);//获取数字
show[x][y] = count + '0';//替换时是字符
DisplayBoard(show, row, col);//显示排查出的信息
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
}
在查询结果不是雷的情况下,需要统计坐标周围八个位置的雷的数量。这里使用 函数[get_mine_count] 实现。
注意,在ASCII码表中存在:
0 (数字)对应ASCII值:0 '0' (字符)对应ASCII值:48 1 (数字)对应ASCII值:1 '1' (字符)对应ASCII值:49 2(数字)对应ASCII值: 2 '2' (字符)对应ASCII值:50 3(数字)对应ASCII值: 3 '3' (字符)对应ASCII值:51 ...... ...... 数字变为对应字符:加上48
而 数字 + '0' = 数字对应的字符
对于坐标(x,y),存在:
(x - 1 , y - 1) | (x - 1, y) | (x - 1, y + 1) |
(x, y - 1) | (x, y) | (x, y +1) |
(x + 1, y - 1) | (x + 1, y) | (x + 1, y + 1) |
结合上述条件,可得代码:
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
//static - 如果只想让该函数在本源文件内使用
{
//另一种写法
//int count = 0;
//int i = 0;
//for ( i = 1; i <= 1; i++)
//{
// int j = 0;
// for (j = 1; j <= 1; j++)
// {
// if (i != 0 && j != 0)
// {
// count += mine[x + i][y + j];
// }
// }
//}
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';//较为简单
}
(ps:static函数 在这里可加可不加。)
一点额外
static的三个作用
- 修饰局部变量
- 修饰全局变量
- 修饰函数
在完成了布置雷和排查雷的步骤后,发现程序并没有结束,因为这里还是缺少 - 判断胜利的步骤。
为了判断胜利条件,在函数[FindMine]中加入 变量win
规定:当 win = 棋盘(9*9)中除雷以外使用格子数时,玩家胜利。
于是当玩家扫雷时,若所选格子不是雷,则规定 win++ :
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,踩到雷了\n");
DisplayBoard(mine, row, col);
break;
}
else
{
//不是雷的情况 - 统计坐标(x,y)周围有几个雷
int count = get_mine_count(mine, x, y);//获取数字
show[x][y] = count + '0';//替换时是字符
DisplayBoard(show, row, col);//显示排查出的信息
win++;
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
这样就完成了玩家胜利条件的判断。
可是,如果玩家两次选择同一格子怎么办?
提供一种解决例子:
- 再加入一个数组,用来存放玩家输入过的坐标;
- 当玩家再次输入坐标时,先比较两次输入坐标是否相同;
- 相同 --- 跳过本次判断,让玩家重新打印;
- 不同 --- 执行下一步。
if (ch[x][y] != mine[x][y])
{
ch[x][y] = mine[x][y];
}
else
{
printf("输入值重复,请重新输入\n");
continue;
}
(记得创建数组时要初始化数组)
如果坐标(x,y)处不是雷,且坐标周围也不是雷 - 展开(递归),直到展开到展开边界坐标周围有雷;
实现的其中一种方法:
运用函数[Extent_show(mine, show, arr, x, y)]
void Extent_show(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int i = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
int count = get_mine_count(mine, x, y);//获取数字
show[x][y] = count + '0';//替换时是字符
if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])
{
arr[x + i][y + j] = mine[x + i][y + j];
int sur = get_mine_count(mine, x + i, y + j);
show[x + i][y + j] = sur + '0';
if (sur = 0)
{
Extent_show(mine, show, arr, x + i, y + j);
}
}
}
}
}
}
这里用一种很笨的方法实现了递归,循环是为了保障每一次的搜索范围是坐标周围的8格, 三个if语句:
第一个
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
是为了限制递归范围,避免栈溢出;
第二个
if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])
这里可以发现多出来了一个数组arr,为了记录查询坐标,防止重复查询;
第三个:
if (sur = 0)
用来判断坐标所在位置附近8个是否有雷,有雷,则停止以该指标为起点进行新的查询。
2.
如果认为某个坐标是雷,标记该坐标。
在函数[FindMine]中插入标记语句:
char ch_S[ROWS][COLS];
InitBoard(ch, ROWS, COLS, '?');
while (win < row * col - ESAY_COUNT)//因为排雷是重复过程,使用使用 while循环
{
printf("选择1,标记雷\n");
printf("选择0,跳过标记雷的过程\n");
printf("请选择:");
int x = 0;
scanf("%d", &x);
switch (x)
{
case 1:
SignMine(show, mine, ch_S, ROW, COL);
break;
case 0:
break;
default:
printf("输入错误,请重新输入\n");
printf("\n");
continue;
}
//之后接原来部分
利用选择处理,让玩家自主选择是否标记雷。
函数[SignMine] 部分
void SignMine(char show[ROWS][COLS], char mine[ROWS][COLS], char ch_S[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
again:
printf("请输入认为是雷的坐标(x y): ");
scanf("%d%d", &x, &y);
if (ch_S[x][y] != mine[x][y])
{
ch_S[x][y] = mine[x][y];
show[x][y] = '!';
}
else
{
printf("输入值重复,请重新输入\n");
goto again;
}
DisplayBoard(show, ROW, COL);
}
这次是多传了 数组ch_S 过去,为的也是方便记录已经输入过的坐标。不过这里使用了 goto语句,在这里是个人认为比较方便的做法。
#include
#define ESAY_COUNT 10
#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);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//排查雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
#include
#include
void menu()
{
printf("**********************************\n");
printf("**** play:输入1 ****\n");
printf("**** exit:输入0 ****\n");
printf("**********************************\n");
}
void game()
{
char mine[ROWS][COLS];//存放布置好的雷的消息
char show[ROWS][COLS];//存放排查出来的信息
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘(只打印中间9*9的范围)
//DisplayBoard(mine, ROW, COL);
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));
//(unsigned int) - 强制类型转化
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();//扫雷游戏
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
//为了多次游玩,选择 do while 语句
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
#include
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;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
//打印列号
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");
}
printf("------扫雷游戏------\n");
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置10个雷
int count = 10;
while (count)
{
//生成随机的下标
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')//字符0
{
mine[x][y] = '1';//字符1
count--;
}
}
}
void SignMine(char show[ROWS][COLS], char mine[ROWS][COLS], char ch_S[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
again:
printf("请输入认为是雷的坐标(x y): ");
scanf("%d%d", &x, &y);
if (ch_S[x][y] != mine[x][y])
{
ch_S[x][y] = mine[x][y];
show[x][y] = '!';
}
else
{
printf("输入值重复,请重新输入\n");
goto again;
}
DisplayBoard(show, ROW, COL);
}
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
//static - 如果只想让该函数在本源文件内使用
{
//另一种写法
int count = 0;
int i = 0;
for ( i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
if (i != 0 && j != 0 && mine[x+i][y+j] == '1')
{
count ++;
}
}
}
return count;
//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 Extent_show(char mine[ROWS][COLS], char show[ROWS][COLS], char arr[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
int i = 0;
for (i = -1; i <= 1; i++)
{
int j = 0;
for (j = -1; j <= 1; j++)
{
int count = get_mine_count(mine, x, y);//获取数字
show[x][y] = count + '0';//替换时是字符
if (mine[x + i][y + j] != '1' && arr[x + i][y + j] != mine[x + i][y + j])
{
arr[x + i][y + j] = mine[x + i][y + j];
int sur = get_mine_count(mine, x + i, y + j);
show[x + i][y + j] = sur + '0';
if (sur = 0)
{
Extent_show(mine, show, arr, x + i, y + j);
}
}
}
}
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//过程:
// 1.输入排查的坐标
// 2.检查坐标(是否为0)
// (1)是雷 - 玩家失败 - 游戏结束
// (2)不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到 show数组 - 游戏继续
int x = 0;
int y = 0;
int win = 0;
char ch[ROWS][COLS];
InitBoard(ch, ROWS, COLS, '?');
char ch_S[ROWS][COLS];
InitBoard(ch, ROWS, COLS, '?');
while (win < row * col - ESAY_COUNT)//因为排雷是重复过程,使用使用 while循环
{
printf("选择1,标记雷\n");
printf("选择0,跳过标记雷的过程\n");
printf("请选择:");
int x = 0;
scanf("%d", &x);
switch (x)
{
case 1:
SignMine(show, mine, ch_S, ROW, COL);
break;
case 0:
break;
default:
printf("输入错误,请重新输入\n");
printf("\n");
continue;
}
Con:
printf("请输入要排查的雷的目标(x y):");
scanf("%d%d", &x, &y);//坐标范围:x - (1,9); y - (1,9)
if (ch[x][y] != mine[x][y])
{
ch[x][y] = mine[x][y];
}
else
{
printf("输入值重复,请重新输入\n");
goto Con;
}
//判断坐标的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,踩到雷了\n");
DisplayBoard(mine, row, col);
break;
}
else
{
//不是雷的情况 - 统计坐标(x,y)周围有几个雷
char arr[ROWS][COLS];
InitBoard(arr, ROWS, COLS, '?');
Extent_show(mine, show, arr, x, y);
DisplayBoard(show, row, col);//显示排查出的信息
win++;
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
if (win == row * col - ESAY_COUNT)
{
printf("恭喜,排雷成功\n");
DisplayBoard(mine, row, col);
}
}