【C语言】——扫雷游戏的实现(超详细!)
一、 前言
扫雷游戏是小时候的经典小游戏,相信大家都玩过吧。今天,我们便自己实现扫雷游戏,以对前面学到的知识做一个巩固。
二、 扫雷游戏的功能说明
- 使用控制台实现经典的扫雷游戏
- 游戏可以通过菜单实现继续玩或者退出游戏
- 扫雷的棋盘是 9 ∗ 9 9 * 9 9∗9的格子
- 可以排查雷
(1)如果位置不是雷,就显示周围有几个雷
(2)如果位置是雷,就被炸死,游戏结束
(3)把除10个雷之外的所有非雷区域找出来,排雷成功,游戏结束- 可以标记雷
(1)如果确定是雷,则标记上肯定的记号
(2)如果不确定是雷,则标记上不确定的记号
(3)如果正确标记10个雷的位置,挑战成功,游戏结束
三、 游戏的分析和设计
首先,要设计出一个 9 ∗ 9 9*9 9∗9的棋盘来存储雷的信息,很自然就想到创建一个 9 ∗ 9 9*9 9∗9的数组来实现。
我们可以用数字 1 1 1 来代表雷,如果这个位置为雷,我们就放置 0 0 0 ;非雷,就放置 1 1 1 。
当我们访问 ( 3 , 3 ) (3,3) (3,3)这个坐标时,该坐标不是雷,则我们统计它周围雷的个数是 2 2 2 .

然而,当我们访问 ( 8 , 5 ) (8, 5) (8,5) 这个坐标时,我们访问周围橙色一圈的位置,统计雷的个数,我们会发现数组越界。

怎么办呢?为了防止越界,我们在设计的时候,可以把数组扩大一圈,棋盘还是在中间 9 ∗ 9 9*9 9∗9的位置,外面一圈不布置雷就行。所以我们的数组创建成 11 ∗ 11 11*11 11∗11会更好。

但显然。这个棋盘时不能给玩家看到的。所以,我们需要再创建一个数组,是用来展现给玩家看的。我们将布置雷的数组称为 m i n e s mines mines,展现给玩家看的数组称为 s h o w show show。
同时,为了神秘, s h o w show show数组开始时初始化为 字符 ‘ ∗ ’ ‘*’ ‘∗’,为了保持两个数组的一致性,以便可以用同一套函数处理, m i n e s mines mines数组最开始也初始化成 字符 ‘ 0 ’ ‘0’ ‘0’ 和 字符 ‘ 1 ’ ‘1’ ‘1’ 。
如下图:

四、多文件操作
当代码比较多时,我们往往会根据程序的功能,将代码拆分在多个文件之中。
一般来说, 函数的声明、类型的声明放在头文件 ( . h ) (.h) (.h)中,函数的定义和函数的调用放在不同的源文件 ( . c ) (.c) (.c)中。
例如:
add.c
//函数的定义 int Add(int x, int y) { return x + y; }add.h
//函数的声明 int Add(int x, int y);test.c
#include<stdio.h> #include"add.h"//自己写的头文件用""包含 int main() { int a = 10; int b = 20; //函数调用 int c = Add(a, b); printf("%d\n", c); return 0; }
运行结果:

图解:

通常,放函数定义的源文件 ( . c ) (.c) (.c)和放函数声明的头文件 ( . h ) (.h) (.h)名字相同
为什么要用多文件操作呢,好处如下:
逻辑清晰方便多人协同,效率更高可以适当的隐藏代码
这里,我们也采用多文件的形式
- game.h : 文件中写游戏需要的数据类型和函数声明等
- game.c :文件中写游戏中函数的实现等
- test.c : 文件中写游戏的测试逻辑
五、 游戏菜单的实现
开始游戏之前,往往需要有个菜单, 供玩家选择
要求:
1、 选择 ′ 1 ′ ' 1 ' ′1′ 开始游戏,选择 ′ 0 ′ '0' ′0′ 退出游戏
2、 选择其他选项,提示选择错误
代码实现:
test.c文件
#include<stdio.h>
//打印菜单
void menu(void)
{
printf("**********************\n");
printf("***** 1:play *****\n");
printf("***** 0:exit *****\n");
printf("**********************\n");
}
void game(void)
{
//负责整个游戏逻辑的实现
}
int main()
{
int a = 0;
//do while循环,上来直接让玩家进行选择
do
{
menu();//打印菜单
printf("请选择: ");
scanf(" %d", &a);
switch (a)
{
case 1:
game();//进入“游戏”函数,开始玩游戏
break;
case 0:
printf("游戏结束\n");//结束游戏
break;
default:
printf("选择错误,请重新选择\n");//选择错误
printf("\n");
break;
}
} while (a);
return 0;
}
注:game函数负责整个游戏逻辑的实现,稍后就讲
六、 游戏主逻辑的实现
我们在test.c文件中,专门创建一个game函数来完成整个游戏逻辑的实现
游戏主逻辑:
- 创建 m i n e s mines mines数组和 s h o w show show数组
- 初始化两个数组
- 布置雷
- 打印棋盘( s h o w show show数组)
- 进入循环
(1)玩家选择排查雷还是标记雷
(2)判断玩家是否被“炸死”
(3)判断玩家是否成功挑战游戏
七、 游戏各功能的实现
由于整个游戏代码都是对 m i n e s mines mines和 s h o w show show两个数组进行操作,这里我们不妨在头文件中用宏定义1,使代码更高效:
game.h文件
#include<stdio.h>
#define ROW 9//实际操作的行
#define COL 9//实际操作的列
#define ROWS ROW + 2//扩充后的行
#define COLS COL + 2//扩充后的列
#define easy_count 10//雷的个数
7.1 初始化棋盘
功能:
- 将 m i n e s mines mines函数全部初始化为 ′ 0 ′ '0' ′0′
- 将 s h o w show show函数全部初始化为 ′ ∗ ′ '*' ′∗′
代码实现:
game.c文件
//初始化扫雷
void initialize(char arr[ROWS][COLS], int rows, int cols, char x)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
arr[i][j] = x;
}
}
}
7.2 随机生成雷
(1) 随机数的产生
想要在 9 ∗ 9 9*9 9∗9的棋盘中随机放置10个雷,我们可以通过行和列生成十组随机数来实现,那么怎么才能生成随机数呢?
C语言中,提供了rand函数来生成随机数,他生成随机数的范围是0-RAND_MAX,RAND_MAX依赖编译器的实现,大部分编译器是32767。
原型如下:
int rand (void);
rand函数的使用需包含头文件:<stdlib.h>
但是,rand函数生成的随机数是伪随机的,并不是真正的随机数。虽然单次运行中不同的rand函数会产生不同的随机数,但每次程序运行同一个rand函数生成的随机数是相同的。这是因为rand函数是对一个叫 “种子” 的基准值进行运算产生的随机数,而rand函数默认的种子是 1。而我们要生成不同的随机数,就要让种子不断变化。
C语言中又提供了srand函数,用来初始化随机数的生成器的
原型如下:
void srand(unsigned int seed)
程序在调用rand函数前先调用srand函数,通过srand函数的参数seed来设置rand函数生产随机数的时候的种子,只要种子在变化,每次生成随机数序列也就变化起来了。
但现在,问题又来了,要想rand生成随机数,那就得srand的种子是随机的;我现在想要一个随机数,你却要求我给你一个随机数才能生成随机数,这就矛盾了。
那么有什么东西是一直在变的呢?
时间!
我们可以将时间作为种子,这样种子就一直在变,生成的随机数也是随机的了
C语言提供了一个time函数,可以获取时间
原型如下:
time_t time (time_t* timer);
time函数会返回当前的日历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的差值,单位是秒。返回类型是time_t类型,本质上是32位或64位的整型类型。
- time函数的参数timer如果是非NULL的指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。
- 如果timer是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳2。
time函数需要包含头文件:<time.h>
了解这些知识后,我们就可以生成随机数了。代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
i

本文使用C语言实现扫雷游戏,以巩固所学知识。介绍了游戏功能,如通过菜单选择继续或退出,可排查和标记雷。阐述了游戏设计,采用两个11*11数组,一个存雷信息,一个展示给玩家。还讲解了多文件操作、各功能实现及细节补充,并给出源码和效果演示。
最低0.47元/天 解锁文章
3076





