IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言实现:8200字带你详解扫雷 -> 正文阅读

[C++知识库]C语言实现:8200字带你详解扫雷

项目要求

实现一个9×9的扫雷游戏,玩家通过输入坐标进行操作。玩家如果踩到雷了,就提示游戏结束,如果没踩到雷,就统计这个坐标周围的雷数,显示出来。效果如图:

思路

我们先创建3个文件,game.h,game.c,test.c。test.c用于测试逻辑,game.c用于定义具体的游戏函数,game.h用于声明。然后我们在test.c中开始敲代码,和猜数字三子棋游戏一样,进来就要加载游戏界面,所以我们直接给一个do while循环,让玩家进行选择。这里就不再叙述了。代码如下:

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

#include "game.h"

void menu()

{

????printf("***************************\n");

????printf("******???? 1.PLAY??? ******\n");

????printf("******???? 0.EXIT??? ******\n");

????printf("***************************\n");

}

game()

{

}

int main()

????{

????srand((unsigned int)time(NULL));//后面的随机值会用到,后面再分析它的作用

????int input;

????do

????{

????????menu();

????????printf("请选择->");

????????scanf("%d",&input);

????????switch (input)

????????{

????????case 1:

????????????game();

????????????break;

????????case 0:

????????????printf("游戏结束\n");

????????????break;

????????default:

????????????printf("输入有误,请重新输入\n");

????????????break;

????????}

????} while(input);

????return 0;

}

基础的游戏菜单搭建好了,接下来我们该如何实现游戏部分呢?
首先分析,我们要得到一个9*9的棋盘,这里就要用到二维数组了。然后我们分析,如果只定义一个二维数组,布置雷 雷-‘1’ 不是雷-‘0’,然后玩家操作以后显示周围雷的数量,如果周围雷的数量刚好为1,则显示1,这就产生歧义了。所就要写两个char数组,1个数组专门存放布置好的雷的信息,另一个数组存放和排查雷的信息。

game()
{
char mine[ROWS][COLS];//用于布置雷
char show[ROWS][COLS];//用于排查雷
}

然后在game.h中定义常量,这样定义的好处就是能够做到一改全改。

1

2

3

4

#define ROWS ROW+2

#define COLS COL+2

#define ROW 9

#define COL 9

ROLS和COLS为什么要加2呢?
因为我们之后统计坐标的时候,如果刚好是在四边呢?再往外面统计,不就数组越界了吗?
所以我们要在定义的时候+2,不往里面存东西就行了。如果想实现9*9的棋盘,数组的大小应该设计成11*11,否则会导致数组越界
定义好棋盘后,我们就要对棋盘里面存值,进行初始化。
在数组mine里面我们全部初始化0,0为安全区,1为雷区。
在数组show里面我们全部初始化*,*表示玩家没有探索过的区域
于是我们在game()函数里面调用InitBoard函数,用于初始化

1

2

InitBoard(mine, ROWS, COLS, '0');

InitBoard(show, ROWS, COLS, '*');

既然调用函数就要声明函数,我们在game.h中声明,然后在test.c中引入game.h这个头文件就可以了。

1

void InitBoard(char board[ROWS][COLS], int rows, int cols,char ret);//声明初始化函数

函数都声明完了,InitBoard函数也该定义一下了,于是在game.c函数中,引入game.h头文件以后,再给定这样一段代码:

1

2

3

4

5

6

7

8

9

10

11

void InitBoard(char board[ROWS][COLS], int rows, int cols,char ret)//初始化棋盘

{

????int i, j;

????for (i = 0; i < ROWS; i++)

????{

????????for (j = 0; j < COLS; j++)

????????{

????????????board[i][j] = ret;//如果传过来的字符是*,则初始化为*,传过来什么就初始化为什么

????????}

????}

}

初始化以后,我们应该定义一个函数来打印棋盘了。打印9*9的棋盘,我们就只需要把数组以及ROW跟COL传过去就行了。
在game()函数中调用DisplayBoard(show, ROW, COL);
然后在game.h中声明DisplayBoard函数

1

void DisplayBoard(char board[ROWS][COLS], int row, int col);//打印棋盘

声明完函数以后,函数还没定义,于是在game.c中定义:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘

{

int i, j;

printf("-----------------扫雷游戏---------------\n");

for (i = 0; i <=col; i++)//打印列号

{

printf("%d ",i);

}

printf("\n");

for (i = 1; i <=row; i++)//从1开始访问,直到9,这里就是9行

{

printf("%d ",i);//打印行号

for (j = 1; j<=col; j++)//9列

{

printf("%c ",board[i][j]);

}

printf("\n");//打印完一行以后换行

}

printf("-----------------扫雷游戏---------------\n");

}

然后调试我们的程序,运行结果如下:

完成初始化以后,我们就要在mine数组里面放雷了。
在game()函数里面调用Put函数:Put(mine, ROW, COL);
然后在game.h里面声明:

1

void Put(char board[ROWS][COLS], int row, int col);//放雷

声明完以后,就要在game.c里面定义函数了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

void Put(char board[ROWS][COLS], int row, int col)//放雷

{

????int count = 10;//地雷数

????while (count)

????{

????????int x = rand() % row + 1;//生成随机的x,y,即坐标

????????int y = rand() % col + 1;

????????if (board[x][y] =='0')//如果里面没有存放东西

????????{

????????????board[x][y] = '1';

????????????count--;

????????}

????}

}

rand和srand是由stdlib.h给出的库函数,rand能够生成0~32767的随机数,而srand则是定义它的随机数初始值,在程序中只需要定义一次,所以我们在main函数里面定义了,并且以时间戳time作为它的参数,增大随机度。然后我们再把它们的头文件stdlib.h和time.h放到game.h里面进行包括。

雷布置好了,那么玩家就可以操作了,在game()函数里面调用排查函数:

1

Find(mine, show, ROW, COL);//(排查需要把两个数组都传进来,由mine函数判断是否踩到雷,或者计算周围雷的数量然后返回给show)

接下来在game.h中声明排查函数:

1

void Find(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷

然后在game.c中定义函数
玩家操作应该是这样的:
1.输入坐标,判断坐标的合法性
2.判断是不是雷
(1)是雷:被炸死了(2)不是雷:计算周围的雷数-放到坐标,游戏继续
3.每次排查完以后,还应该判断输赢。

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

void Find(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查

{

????int x, y;

????int win = 0;//记录行动次数

????while (1)

????{

????????printf("请输入坐标x=? y=");

????????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 if (show[x][y] != '*')//如果这个坐标已经被走过了,show里面就会存数字,就不等于初始化的*了

????????????{

????????????????printf("坐标已被占用,请重新输入");

????????????}

????????????else//计算周围的雷数

????????????{

????????????????win++;//行动合法则行动次数+1

????????????????int count = get_mine_count(mine, x, y);//调用获取周围地雷数量的函数

????????????????show[x][y] = count + '0';//因为返回的count是整形,而show是字符型,所以应该加上字符'0',才可以以对应的字符形式存入数组。

????????????????system("cls");//清空屏幕

????????????????DisplayBoard(show, ROW, COL);//打印

????????????????//每次排查完以后,还应该判断输赢。即当玩家踩完了所有非雷的区域,棋盘的格子数=row*col,雷数=10,当row*col-10=玩家行动合法次数的时候,就判定玩家为赢

????????????????if (ROW * COL - 10 == win)

????????????????{

????????????????????printf("恭喜你,赢了!\n");

????????????????????DisplayBoard(show, ROW, COL);

????????????????????break;

????????????????}

????????????}

????????}

????????else

????????{

????????????printf("输入值不合法,请重新输入坐标值\n");

????????}

????}

}

然后我们发现get_mine_count函数还没定义,我们就直接在game.c这个里面进行定义,不需要再声明了。

1

2

3

4

5

6

7

8

9

10

11

12

13

static int get_mine_count(char mine[ROWS][COLS], int x, int y)//本函数仅用于计算周围的雷数,所以可以用static修饰

{

????int count = 0;

????if (mine[x - 1][y - 1] == '1')count++;

????if (mine[x][y - 1] == '1')count++;

????if(mine[x + 1][y - 1] == '1') count++;

????if(mine[x - 1][y] == '1')count++;

????if(mine[x + 1][y] == '1')count++;

????if(mine[x + 1][y - 1] == '1')count++;

????if(mine[x + 1][y] == '1') count++;

????if(mine[x + 1][y + 1] == '1')count++;

????return count;

}

计算周围的雷即把周围的坐标都判断一次,如果有一个是1,count就+1,判断完之后返回count。
但这样是不是显得有些冗长了呢?于是我们可以改写一下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

//由上面的代码,还可以写成循环的形式,我们观察x的范围是x-1~x+1,y的范围是y-1~y+1

//于是定义两个循环变量

int i, j;

for (i = -1; i <= 1; i++)

{

????for (j = -1; j <= 1; j++)

????{

????????if (i == 0 && j == 0)continue;

????????if (mine[x + i][j + i]=='1')

????????{

????????????count++;

????????}

????}

}

return count;

还可以改写成这样的形式:

1

2

3

4

static int get_mine_count(char mine[ROWS][COLS], int x, int y)//本函数仅用于计算周围的雷数,所以可以用static修饰

{

return mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x - 1][y] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';

}

于是基本的游戏底层逻辑就这样实现了
全部源码如下:

test.c

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

#define ?_CRT_SECURE_NO_WARNINGS 1

#include "game.h"

//实现扫雷项目

void menu()//菜单函数
{
?? ?printf("****************************\n");
?? ?printf("********* ?1.PLAY ?*********\n");
?? ?printf("********* ?0.EXIT ?*********\n");
?? ?printf("****************************\n");
}

void game()//游戏函数
{
?? ?//我们的数据存储为了防止排查的的时候数组越界,所以左右上下应该多出一格
?? ?char mine_board[ROWS][COLS] = { 0 };//内部,给开发者测试的,1是雷,0无雷
?? ?char show_board[ROWS][COLS] = { 0 };//展示给玩家看的,默认存放‘*’
?? ?//初始化
?? ?InitBoard(mine_board,ROWS,COLS,'0');
?? ?InitBoard(show_board, ROWS, COLS, '*');
?? ?//打印
?? ?/*DisplayBoard(mine_board, ROW, COL);*/
?? ?//DisplayBoard(show_board, ROW, COL);
?? ?//布置雷
?? ?SetMine(mine_board, ROW, COL);
?? ?DisplayBoard(show_board, ROW, COL);
?? ?//排查雷
?? ?FindMine(mine_board, show_board,ROW,COL);
}

int main()
{
?? ?int input = 0;
?? ?srand((unsigned int)time(NULL));
?? ?do {
?? ??? ?menu();
?? ??? ?printf("请选择:>");
?? ??? ?scanf("%d",&input);
?? ??? ?switch (input)
?? ??? ?{
?? ??? ?case 1:
?? ??? ??? ?game();
?? ??? ??? ?break;
?? ??? ?case 0:
?? ??? ??? ?printf("游戏结束\n");
?? ??? ??? ?break;
?? ??? ?default:
?? ??? ??? ?printf("输入有误,请重新输入\n");
?? ??? ??? ?break;
?? ??? ?}
?? ?} while (input);
?? ?return 0;

}

game.h

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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

#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define PUT 10//雷数

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_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);//排查雷

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

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

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

#define ?_CRT_SECURE_NO_WARNINGS 1
#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;
?? ??? ?}
?? ?}
}

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 x = 0;
?? ?int y = 0;
?? ?//雷的下标必然为1-9
?? ?int count = PUT;//雷数
?? ?while (count)
?? ?{
?? ??? ?x = rand() % 9 + 1;//rand%9生成0~8,0~8+1即为1~9
?? ??? ?y = rand() % 9 + 1;
?? ??? ?//判断合法性
?? ??? ?if (x >= 1 && x <= row && y >= 1 && y <= col&&board[x][y]=='0')
?? ??? ?{
?? ??? ??? ?board[x][y] = '1';
?? ??? ??? ?count--;
?? ??? ?}
?? ?}
}

static int get_mine_count(char mine_board[ROWS][COLS], int x, int y)
{
?? ?return mine_board[x - 1][y + 1] +
?? ??? ?mine_board[x][y + 1] +
?? ??? ?mine_board[x + 1][y + 1] +
?? ??? ?mine_board[x - 1][y] +
?? ??? ?mine_board[x + 1][y] +
?? ??? ?mine_board[x - 1][y - 1] +
?? ??? ?mine_board[x][y - 1] +
?? ??? ?mine_board[x + 1][y - 1] - 8 * '0';
}
void FindMine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col)
{
?? ?int x = 0;
?? ?int y = 0;
?? ?int win = row * col - PUT;
?? ?while (win)
?? ?{
?? ??? ?printf("请输入坐标:>\n");
?? ??? ?scanf("%d%d", &x, &y);
?? ??? ?if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
?? ??? ?{
?? ??? ??? ?if (mine_board[x][y] == '1')
?? ??? ??? ?{
?? ??? ??? ??? ?printf("非常遗憾,你被炸死了\n");
?? ??? ??? ??? ?DisplayBoard(mine_board, ROW, COL);
?? ??? ??? ??? ?break;
?? ??? ??? ?}
?? ??? ??? ?else if (show_board[x][y] != '*')//如果这个坐标已经被走过了,show里面就会存数字,就不等于初始化的*了
?? ??? ??? ?{
?? ??? ??? ??? ?printf("坐标已被占用,请重新输入\n");
?? ??? ??? ?}
?? ??? ??? ?else
?? ??? ??? ?{
?? ??? ??? ??? ?int count = get_mine_count(mine_board, x, y);
?? ??? ??? ??? ?show_board[x][y] = '0'+count;
?? ??? ??? ??? ?win--;
?? ??? ??? ??? ?system("cls");//清空屏幕
?? ??? ??? ??? ?DisplayBoard(show_board, ROW, COL);
?? ??? ??? ?}
?? ??? ?}
?? ??? ?else
?? ??? ?{
?? ??? ??? ?printf("输入坐标不合法,请重新输入\n");
?? ??? ?}
?? ?}
?? ?if (win == 0)
?? ?{
?? ??? ?printf("恭喜你赢了\n");
?? ?}
}

我们还可以对游戏进行进一步优化

①设置难度,即定义棋盘大小

②如果不是雷,可以展开一片-函数递归

③提供选项,进行标记

等我技术变好了,再回来优化!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:43:52  更:2022-03-06 12:43:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:05:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码