业精于勤,荒于嬉;行du成于思,毁于随。


扫雷游戏规则

把所有非地雷的格子揭开即胜利,踩到地雷格子即失败。


游戏流程

电脑打印游戏菜单(1.开始游戏 0.退出游戏)——>玩家选择(开始游戏——>进入游戏函数)——>电脑打印出雷盘——>玩家输入需要排雷的坐标——>电脑打印出排完雷后的雷盘(可能性1:被炸死;可能性2:坐标安全,系统显示出周围八个格子内的地雷总数)——>继续排雷——>游戏胜利


扫雷游戏的双层数组

前文有写到三(多)子棋小游戏,三子棋游戏只需要一个数组就够了,但是对于扫雷游戏的实现,一个数组是不够的,需要创建两个数组。

两个二维数组

第一个数组,存放雷的分布信息,面向游戏设计者创建,称为雷盘布置数组,简称布雷数组,如下所示。

1
2
3
4
5
6
7
8
9
10
0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 1 0 1 0
2 0 0 0 0 0 0 1 0 0
3 0 1 0 0 0 0 1 1 0
4 0 0 0 0 0 0 0 0 0
5 0 0 0 0 1 0 0 0 0
6 0 0 0 0 0 0 0 0 0
7 0 0 0 0 1 0 0 0 0
8 0 1 0 0 0 0 1 0 0
9 0 0 0 0 0 0 0 0 0

第二个数组,存放排雷后的雷盘中雷的分布个数信息,面向玩家创建,称为玩家数组,如下所示。

1
2
3
4
5
6
7
8
9
10
0 1 2 3 4 5 6 7 8 9
1 * * * * * * * * *
2 * * * * * * * * *
3 * * 1 * * * * * *
4 * * * * * * * * *
5 * * * * * * * * *
6 * * * * * * * * *
7 * * * * * * * * *
8 * * * * * * * * *
9 * * * * * * * * *

布雷数组技巧的设计

程序设计的时候,需要判断玩家坐标周围八个格子中雷的总个数,那么这就带来一个问题:

玩家选择边角最外环的一层坐标和玩家选择内环坐标判断方法不同。有两种解决方案。

第一种:加if判断

如果是最外面的一圈的就单独拉出来判断其周围的雷的总数,但是这又导致了一个问题,四个角和每条棱的判断方法各不相同,所以这个解决方案很繁琐。

第二种:巧妙地让布雷数组膨胀一圈

假设让布置雷的雷盘是11X11规格的,但是只在9X9的格子里布雷,那么对于每个9X9格子里的坐标,判断周围格子里雷的总数的算法都是一样的。(把巧妙打在公屏上!!!)


程序设计

创建三个文件,game.h 游戏的头文件functions.c游戏所用到功能函数 game.c 游戏框架 三个文件。

头文件

头文件的书写是一步一步需要什么写什么建立而来的,这里先把所有的宏定义、库函数引用、函数声明放这里,后面用到会有解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 10

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void test(void);
void menu(void);
void game(void);

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 board[ROW][COL], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);


游戏实现框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"

int main(void)
{
test();
return 0;
}

//游戏执行测试框架
void test(void)
{
int input = 0;

do {
menu();//系统打印游戏菜单,调用menu函数

printf("请选择:>>>");
scanf("%d", &input);

switch (input)//switch开关语句,作用显而易见
{
case 1:
game();//调用game函数
break;
case 2:
printf("退出游戏\n");
input = 0;
break;
default:
printf("输入值无效\n");
break;
}

} while (input);
}

//菜单打印函数
void menu(void)
{
printf("************************************\n");
printf("*****1.开始游戏 0.退出游戏******\n");
printf("************************************\n");
}

//游戏主函数
void game(void)
{
//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//第二步,初始化
InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
InitBoard(show, ROWS, COLS, '*');
//第三步,布置雷
SetMine(mine, ROW, COL);//布置雷
DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
//第四步,扫雷
FindMine(mine, show, ROW, COL);
}

功能函数

InitBoard

需要对两个数组都进行初始化,但是初始化的分辨元素不一样,所以这里加入一个参数调用set。

1
2
3
4
5
6
7
8
9
10
11
12
13
//初始化函数
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;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
}
}
}

DisplayBoard

初始化函数写好之后可以先写雷盘打印函数进行核验,所以下介绍DisplayBoard函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//雷盘打印
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;

printf("\n");//打印个换行符分割下,不是必要

//打印列号
for (i = 0; i <= row; 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");//打印个换行符分割下,不是必要
}


SetMine

这里就要布置雷了,采用随机值的方法进行布雷,传递整个数组,但控制雷的分布只出现在内环9*9的网格里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

while (count)//控制雷的个数
{
int x = rand() % row + 1;
int y = rand() % col + 1;

if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}

FindMine

进行扫雷,如果玩家踩雷就被炸死,未踩雷,系统报告周围雷的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//扫雷
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");
DisplayBoard(mine, row, col);
break;
}
else
{//计算周围8个方块中雷的总数
int count = (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';
show[x][y] = count + '0';
DisplayBoard(show, row, col);
win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
}
}
else
{
printf("非法输入!!!");
}
}
}

完整程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 3
#define COL 3

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 3

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

void test(void);
void menu(void);
void game(void);

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 board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"

int main(void)
{
test();
return 0;
}

//游戏执行测试框架
void test(void)
{
int input = 0;

do {
menu();//系统打印游戏菜单,调用menu函数

printf("请选择:>>>");
scanf("%d", &input);

switch (input)//switch开关语句,作用显而易见
{
case 1:
game();//调用game函数
break;
case 2:
printf("退出游戏\n");
input = 0;
break;
default:
printf("输入值无效\n");
break;
}

} while (input);
}

//菜单打印函数
void menu(void)
{
printf("************************************\n");
printf("*****1.开始游戏 0.退出游戏******\n");
printf("************************************\n");
}

//游戏主函数
void game(void)
{
//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//第二步,初始化
InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。
InitBoard(show, ROWS, COLS, '*');
//第三步,布置雷
SetMine(mine, ROW, COL);//布置雷
DisplayBoard(mine, ROW, COL);//打印雷盘分布,上帝视角,测试需要。
DisplayBoard(show, ROW, COL);//更新打印好后的雷盘
//第四步,扫雷
FindMine(mine, show, ROW, COL);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "game.h"

//初始化函数
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;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
}
}
}

//打印雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;

printf("\n");//打印个换行符分割下,不是必要

//打印列号
for (i = 0; i <= row; 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 board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

while (count)//控制雷的个数
{
int x = rand() % row + 1;
int y = rand() % col + 1;

if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}

//扫雷
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");
DisplayBoard(mine, row, col);
break;
}
else
{//计算周围8个方块中雷的总数
int count = (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';
show[x][y] = count + '0';
DisplayBoard(show, row, col);
win++;//玩家输入一次win加一次,等到棋盘上只有雷就胜利了
}
}
else
{
printf("非法输入!!!");
}
}
}

游戏测试

这里将ROWCOL更改成3EASY_COUNT改成了5进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>1

0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1


0 1 2 3
1 * * *
2 * * *
3 * * *

请输入排查雷的坐标:>>>1 1

啊哦,你被炸死了!!!

0 1 2 3
1 1 1 0
2 1 0 1
3 0 0 1

************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>1

0 1 2 3
1 0 1 1
2 1 0 1
3 0 1 0


0 1 2 3
1 * * *
2 * * *
3 * * *

请输入排查雷的坐标:>>>2 2

0 1 2 3
1 * * *
2 * 5 *
3 * * *

请输入排查雷的坐标:>>>1 1

0 1 2 3
1 2 * *
2 * 5 *
3 * * *

请输入排查雷的坐标:>>>3 1

0 1 2 3
1 2 * *
2 * 5 *
3 2 * *

请输入排查雷的坐标:>>>3 3

0 1 2 3
1 2 * *
2 * 5 *
3 2 * 2


恭喜你,历经千辛万苦排雷成功!

************************************
*****1.开始游戏 0.退出游戏******
************************************
请选择:>>>

写在后面

  总的来说,这个程序除游戏基本框架(菜单框架等等)外,只用了四个功能函数,实现了基本的扫雷程序,但是游戏还是非常鸡肋的,在真正的扫雷游戏中,当玩家选择的坐标周围八个坐标均没有雷时雷盘会之间展开,这还是很必要的,因为当扫雷的棋盘很大时,一个一个的选择坐标实属太没有游戏体验了。

  加入递归展开后,游戏胜利判断条件也需要改变,相对复杂,所以写在了下一篇博客,欢迎各位大佬检阅、批评和指正,非常感谢!!!