猜数字游戏使用函数

对前面的猜数字游戏引入自定义函数。

将函数的声明放入 function.h 文件中,函数的定义放进 func.c 文件,主函数放在

game.c 中

1
2
3
4
5
6
7
8
9
// function.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//这是游戏的函数声明文件,包含了游戏的主要功能函数。
void menu(); // 显示游戏菜单
void game(); // 进行游戏的主要逻辑
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
// func.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "function.h";
// 这是实现文件,包含函数的具体实现
void menu()
{
// 显示游戏菜单
printf("欢迎来到猜数字游戏!\n");
printf("我已经想好了一个1~100之间的数字,你能猜到它是什么吗?\n");
printf("按1进入游戏\n");
printf("按0退出游戏\n");
}

void game()
{
// 游戏的逻辑
// 生成 1 ~ 100 的随机数
// 限制猜测次数在10次
// 猜没猜中都有提示信息

srand((unsigned)time(NULL)); // 设置随机数种子为当前时间
int aim = rand() % 100 + 1; // 生成1~100之间的随机数
int limit = 10; // 猜测次数限制
int guess = 0; // 用户猜测的数字
while (limit)
{
printf("请输入你猜的数字:>");
scanf("%d", &guess);
if (guess > aim)
{
printf("你猜的数字太大了!\n");
limit--;

}
else if (guess < aim)
{
printf("你猜的数字太小了!\n");
limit--;
}
else
{
printf("恭喜你,猜对了!\n");
printf("\n");
return;// 猜对跳出循环,结束游戏
}
// 每次猜测后提示剩余的机会,猜对了不会执行到这儿
printf("你还有%d次机会!\n", limit);
printf("\n");
}
// 到这里说明用户没有猜对,提示正确的数字
printf("很遗憾,你没有猜对!正确的数字是%d\n", aim);
return;
}
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
#define _CRT_SECURE_NO_WARNINGS 1
#include "function.h"
// 这是正式的游戏文件
int main()
{
menu();
int input = 0; // 判断用户要不要玩
do
{
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("按1继续游戏\n");
printf("按0退出游戏\n");
break;

case 0:
printf("退出游戏!\n");
break;

default:
printf("输入不合法,请重新输入\n");
}
} while (input);

return 0;
}

到这里只是完成最简单的逻辑。还可拓展如难度选择,输入合法性,图形化界面

等。

扫雷游戏

功能说明

  • 使用控制台实现扫雷游戏

  • 游戏可以通过菜单实现退出或继续游玩

  • 棋盘大小为 9 * 9

  • 默认随机布置10个雷

  • 如果排查位置不是雷,显示周围有几个雷

  • 如果排查位置是雷,游戏结束

  • 胜利条件是把所有非雷的位置找出来

设计与分析

首先游戏需要一个菜单。

其次需要一个棋盘用于存放雷与非雷,这个棋盘可以使用一个二维数组表示。

可以用 0 表示非雷,用 1 表示雷。

扫的非雷位置会显示周围有几个雷,原理就是遍历自己这行与相邻两行有多少个

1。

为防止首行和末行会出现越界问题,棋盘可以设计为 11 * 11,在这个11 * 11的

首行和末行不放雷。

文件结构

这里采用 func.h 存放函数声明, func.c 进行函数实现,test.c 用于测试, game.c

实现最终效果。

菜单

首先需要一个游戏菜单,通过自定义函数 menu 来实现。

1
2
3
4
5
6
7
    void menu()
{
printf("********************\n");
printf("*** 1. play game ***\n");
printf("*** 0. exit ***\n");
printf("********************\n");
}

棋盘的实现

棋盘埋雷

生成一个 11 * 11 的二维数组

1
int board[11][11] = { 0 }; // 0 表示非雷,1 表示雷

然后需要在这个棋盘上随机埋10个雷。

埋雷的逻辑作为一个函数 Place_landmines。

1
2
3
4
5
6
7
8
void Place_landmines(int board[11][11])
{
srand((unsigned int)time(NULL)); // 设置随机数种子
// 随即生成雷的行和列,随机数需要限制在 1 ~ 9 之间
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
board[x][y] = 1; //埋雷
}

测试一下是否埋雷成功

1
2
3
4
5
6
7
8
9
10
11
// 测试埋雷函数
int arr[11][11] = { 0 };
Place_landmines(arr);
for (int i = 0;i < 11;i++)
{
for (int j = 0;j < 11;j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}


形参类型是数组,对形参的操作可以影响到实参。

为了避免重复在同一个坐标埋雷,在 Place_landmines里面增加一个判断。如果

该位置有雷就重新生成埋雷坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Place_landmines(int board[11][11])
{
srand((unsigned int)time(NULL)); // 设置随机数种子
// 随即生成雷的行和列,随机数需要限制在 1 ~ 9 之间
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
int flag = 0; // 判断是否埋雷成功
//至少判断一次
do
{
if (board[x][y] == 1) //如果这个位置已经有雷了,就重新生成
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
flag = 1; // 需要再走一遍这个循环
}
else
{
board[x][y] = 1; //埋雷
return; // 不用埋了
}
} while (flag);
}

去测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // 测试埋雷函数
int arr[11][11] = { 0 };
int num = 10;
while (num--)
{
Place_landmines(arr);
}
for (int i = 0;i < 11;i++)
{
for (int j = 0;j < 11;j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}

两个棋盘

然而这个棋盘上哪儿有雷哪儿没雷一目了然,用于游玩的肯定不是这个棋盘。

可以再生成一个 char 类型的 11 * 11 的二维数组,里面全部放 * 字符。用于给用户

看。

1
2
3
4
5
6
7
8
char show[11][11] = {0}; // 这是玩家看到的棋盘,* 表示没有翻开,数字表示周围有多少雷,0 表示周围没有雷
for (int i = 0;i < 9;i++)
{
for (int j = 0;j < 9;j++)
{
show[i][j] = '*'; // 初始化玩家看到的棋盘,全部都是 *,表示没有翻开
}
}

排雷的实现

让用户输入要查询的坐标,在对应数组中该坐标的值如果为1直接游戏结束。

如果为 0 则显示以该坐标为中心附近 3 * 3 范围内的雷的个数。而在 show 这个数

组更新后需要重新打印,所以可以将打印 show 这个数组的代码写成一个函数

print_show。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void print_show(char show[11][11])
{
// 这是打印玩家看到的棋盘函数的定义
// 这个函数的功能是打印玩家看到的棋盘,* 表示没有翻开,数字表示周围有多少雷,0 表示周围没有雷
for (int i = 1;i < 10;i++)
{
for (int j = 1;j < 10;j++)
{
printf("%c ", show[i][j]);
}
printf("\n");
}
}

1
2
3
4
5
6
7
8
9
char show[11][11] = { 0 }; // 这是玩家看到的棋盘,* 表示没有翻开,数字表示周围有多少雷,0 表示周围没有雷
for (int i = 0;i < 11;i++)
{
for (int j = 0;j < 11;j++)
{
show[i][j] = '*'; // 初始化玩家看到的棋盘,全部都是 *,表示没有翻开
}
}
print_show(show); // 打印玩家看到的棋盘

用一个 No_mines 函数来计算是否排完所有雷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    int No_mines(int board[11][11])
{
int num = 0;
for (int i = 1; i < 10;i++)
{
for (int j = 1;j < 10 ;j++)
{
if (board[i][j] == 2)
num++;
}
}
if (num == 71)
{
// 说明排完了
return 0;
}
else
{
return 1;
}
}

如果要查询的坐标值为2,说明这里已经被排过了,提示重新输入。

将这些功能集成在 clear_mines 函数中。

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
    void  clear_mines(int board[11][11], char show[11][11])
{
// 这是排雷函数的定义
// 这个函数的功能是当玩家翻开一个格子的时候,如果这个格子周围没有雷,就把周围的格子都翻开,如果周围有雷,就把周围3 * 3的雷的数量显示出来
int x = 0;
int y = 0;
int flag = 0; // 判断游戏是否胜利
do
{
printf("请输入你要翻开的格子的行和列:");
printf("输入格式为:x,y\n");
scanf("%d,%d", &x, &y);
if (1 <= x && x <= 9 && 1 <= y && y <= 9) // 判断输入是否合法
{
if (board[x][y] == 1) // 如果这个格子有雷了,游戏结束
{
printf("你踩到雷了,游戏结束!\n");
Sleep(2000);
system("cls"); // 清屏

return 0; // 游戏结束
}
else if (board[x][y] == 2) // 如果这个格子已经被翻开了,就提示玩家重新输入
{
printf("这个格子已经被翻开了,请重新输入!\n");
return clear_mines(board, show); // 重新输入
}
else
{
int count = 0; // 统计周围有多少雷
for (int i = x - 1;i <= x + 1;i++)
{
for (int j = y - 1;j <= y + 1;j++)
{
if (board[i][j] == 1) // 如果周围有雷了,count 就加 1
{
count++;
}
}
}
show[x][y] = count + '0'; // 将雷的数量显示在已经翻开的坐标处, + '0' 是为了把数字转换成字符
board[x][y] = 2; // 被操作的棋盘已经翻开的坐标处替换为2,表示此处已被翻开,不让再翻
system("cls"); // 清屏
print_show(show);// 玩家看到的棋盘元素更新了,重新打印玩家看到的棋盘
}
}

else if (x == 11 && y == 11)
{
// 开发者作弊
flag = 0;
printf("开发者模式直接通过!\n");
system("cls");

}
else
{
printf("输入不合法,请重新输入!\n");
return clear_mines(board, show); // 输入不合法了,就重新输入
}

flag = No_mines(board);
} while (flag);
// 出循环说明玩家已经翻开了所有没有雷的格子,游戏胜利
printf("游戏胜利!\n");
Sleep(2000);
return;
}

游戏逻辑的完整实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int main()
{
int input = 0; // 判断用户玩不玩
do
{
menu();
printf("选择游戏还是退出:> ");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
game();
break;
case 0:
printf("退出游戏!\n");
exit(0);

default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}



杂谈

只是简单实现了一个控制台扫雷游戏,为了方便进行测试在代码中留下了部分代码。

代码中还有诸多可以优化的地方。将在后续进行更新。

优化

-引入了宏定义,将棋盘的行列以及地雷的数量用宏定义,

这样修改数值就可以只修改宏定义处的数值。

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
// func.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
//这是用于扫雷游戏的函数声明和宏定义的头文件

// 定义行列数,方便修改
#define ROW 3
#define COL 3
#define MINES 3; // 定义地雷数量


// 声明了菜单函数
void menu();

// 声明了游戏函数
void game();

// 声明了计算已经翻过格子数函数
int No_mines(int board[11][11]);

// 声明了打印玩家看到的棋盘函数
void print_show(char show[9][9]);

// 声明了布置地雷函数
void Place_landmines(int board[11][11]);

// 声明了排雷函数
void clear_mines();
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// func.c
#define _CRT_SECURE_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1
#include "func.h"

// 这是函数定义文件

// 菜单函数
void menu()
{
printf("********************\n");
printf("*** 1. play game ***\n");
printf("*** 0. exit ***\n");
printf("********************\n");
}

void print_show(char show[ROW + 2][COL + 2])
{
// 这是打印玩家看到的棋盘函数的定义
// 这个函数的功能是打印玩家看到的棋盘,* 表示没有翻开,数字表示周围有多少雷,0 表示周围没有雷
for (int i = 1;i <= ROW;i++)
{
for (int j = 1;j <= COL;j++)
{
printf("%c ", show[i][j]);
}
printf("\n");
}
}
// 布置地雷函数
void Place_landmines(int board[ROW + 2][COL + 2])
{
srand((unsigned int)time(NULL)); // 设置随机数种子
// 随即生成雷的行和列,随机数需要限制在 1 和 ROW 之间
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
int flag = 0; // 判断是否埋雷成功
//至少判断一次
do
{
if (board[x][y] == 1) //如果这个位置已经有雷了,就重新生成
{
x = rand() % ROW + 1;
y = rand() % COL + 1;
flag = 1; // 需要再走一遍这个循环
}
else
{
board[x][y] = 1; //埋雷
return; // 不用埋了
}
} while (flag);
}

// 排雷函数
void clear_mines(int board[ROW + 2][COL + 2], char show[ROW + 2][COL + 2])
{
// 这是排雷函数的定义
// 这个函数的功能是当玩家翻开一个格子的时候,如果这个格子周围没有雷,就把周围的格子都翻开,如果周围有雷,就把周围3 * 3的雷的数量显示出来
int x = 0;
int y = 0;
int flag = 0; // 判断游戏是否胜利
do
{
printf("请输入你要翻开的格子的行和列:");
printf("输入格式为:x,y\n");
scanf("%d,%d", &x, &y);
if (1 <= x && x <= ROW && 1 <= y && y <= COL) // 判断输入是否合法
{
if (board[x][y] == 1) // 如果这个格子有雷了,游戏结束
{
printf("你踩到雷了,游戏结束!\n");
Sleep(2000);
system("cls"); // 清屏

return 0; // 游戏结束
}
else if (board[x][y] == 2) // 如果这个格子已经被翻开了,就提示玩家重新输入
{
printf("这个格子已经被翻开了,请重新输入!\n");
return clear_mines(board, show); // 重新输入
}
else
{
int count = 0; // 统计周围有多少雷
for (int i = x - 1;i <= x + 1;i++)
{
for (int j = y - 1;j <= y + 1;j++)
{
if (board[i][j] == 1) // 如果周围有雷了,count 就加 1
{
count++;
}
}
}
show[x][y] = count + '0'; // 将雷的数量显示在已经翻开的坐标处, + '0' 是为了把数字转换成字符
board[x][y] = 2; // 被操作的棋盘已经翻开的坐标处替换为2,表示此处已被翻开,不让再翻
system("cls"); // 清屏
print_show(show);// 玩家看到的棋盘元素更新了,重新打印玩家看到的棋盘
}
}

else if (x == 11 && y == 11)
{
// 开发者作弊
flag = 0;
printf("开发者模式直接通过!\n");
break;
}
else
{
printf("输入不合法,请重新输入!\n");
return clear_mines(board, show); // 输入不合法了,就重新输入
}

flag = No_mines(board);
} while (flag);
// 出循环说明玩家已经翻开了所有没有雷的格子,游戏胜利
printf("游戏胜利!\n");
Sleep(2000);
system("cls");
return;
}

// 计算已经翻过格子数函数
int No_mines(int board[ROW + 2][COL + 2])
{
int num = 0;
for (int i = 1; i <= ROW;i++)
{
for (int j = 1;j <= COL;j++)
{
if (board[i][j] == 2)
num++;
}
}
int total = ROW * COL - MINES; // 没有雷的格子总数
if (num == total)
{
// 说明排完了
return 0;
}
else
{
return 1;
}
}

// 游戏函数
void game()
{
// 扫雷游戏的运行逻辑
// 首先生成一个 11 * 11 的二维数组,来表示棋盘,其中 0 表示没有雷,1 表示有雷
int board[ROW + 2][COL + 2] = { 0 }; // 这是实际的棋盘,玩家看不到
char show[ROW + 2][COL + 2] = { 0 }; // 这是玩家看到的棋盘,* 表示没有翻开,数字表示周围有多少雷,0 表示周围没有雷
for (int i = 0;i < ROW + 2;i++)
{
for (int j = 0;j < COL + 2;j++)
{
show[i][j] = '*'; // 初始化玩家看到的棋盘,全部都是 *,表示没有翻开
}
}
// 调用埋雷函数
int num = MINES;
while (num--)
{
Place_landmines(board);
}

for (int i = 1;i <= ROW;i++)
{
// 开发者测试用,打印实际的棋盘,1 表示有雷,0 表示没有雷
for (int j = 1;j <= COL;j++)
{
printf("%d ", board[i][j]);
}
printf("\n");
}
// 打印玩家看到的棋盘
print_show(show);
// 游戏的主循环
clear_mines(board, show);

}
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
// game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "func.h"
int main()
{
int input = 0; // 判断用户玩不玩
do
{
menu();
printf("选择游戏还是退出:> ");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
game();
break;
case 0:
printf("退出游戏!\n");
exit(0);

default:
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}