扫雷游戏作为一款经典的益智类游戏,自诞生以来便以其简单的规则和富有挑战性的玩法吸引了大量玩家。利用C语言实现扫雷游戏,能够帮助我们深入理解数组和函数在程序设计中的关键作用。数组用于存储游戏地图和地雷分布,而函数则负责模块化游戏的各个功能,如初始化地图、生成地雷、计算周围地雷数量以及玩家交互等。这种实现方式不仅锻炼了基础编程能力,还能帮助掌握如何将复杂问题分解为多个子任务,并通过函数逐步解决。 通过编写扫雷游戏,可以进一步熟悉C语言的语法结构,如循环、条件判断和指针操作,同时培养调试和优化的技巧。不仅如此,该项目的具有较强的扩展性,后续可通过图形界面或难度调整提升复杂度,为进阶学习打下基础。 说了这么多,想必大家已经想赶紧开始学习了吧!那么我们现在就开始!

要做出一个功能完善的扫雷游戏,我们需要实现以下基本功能:
以下是博主写的扫雷游戏界面,依次为初始界面、排雷界面、排雷失败界面:



在扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要一定的数据结构来存储这些信息。而我们已知需要在9*9的棋盘上布置雷的信息和排查雷,我们首先就可以想到创建一个9*9的数组来存放信息。如下图所示:

如果一个位置布置雷,那么我们就在这个位置存放1,没有雷就存放0。如下图:

假设我们排查(2,5)坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数为1。
假设我们排查(8,6)坐标时,我们访问周围的一圈8个黄色位置,统计周围雷的个数时,最下面的三个坐标就会越界,为了防止越界,我们在设计的时候,就可以给数组扩大一圈,雷还是布置在中间的9*9的坐标上,周围一圈不去布置雷就行,这样就能解决越界的问题。所以我们将存放数据的数组创建为11*11是比较合适的。如下图,左图为排雷的假设,右图为周围加上一圈之后的棋盘。


再继续分析,我们在棋盘上布置了雷,棋盘上有雷的信息(1)和非雷的信息(0),假设我们排查了某一个位置之后,这个坐标处并不是雷,但坐标的周围有1个雷,那我们就需要将排查出的雷的数量信息记录储存,并打印出来,作为排雷的重要参考信息。那么雷的个数信息又应该存放在何处呢?如果我们将其存放在布置雷的数组中,雷的信息和雷的个数信息就可能缠上混淆和打印上的困难。
当然,我们有许多办法可以解决这个麻烦,比如:雷和雷的信息我们可以不使用数字,只要使用某些字符就行,这样就能避免冲突,但这样做的话棋盘上就有雷和非雷的信息,同时还有排查出的雷的个数信息,这样就会比较混杂,不够方便。
因此在这我们采用另外一种方案,我们可以专门创建一个棋盘(对应一个数组mine),来存放布置好的雷的信息,再给另外一个棋盘(对应另外一个数组show)存放排查出的雷的信息,这样就不会互相干扰了。我们把雷布置到mine数组,在mine数组中对雷进行排查,再将排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。
同时我们为了保持神秘感,show数组开始时可以初始化为字符 '*' ,为了保持两个数组的类型一致,我们可以使用同一套函数进行处理,并且将mine数组最开始初始化为字符 '0' ,布置雷再改成 '1' 。如下图所示,左图为mine数组布置雷后的状态,右图为show输出初始化的状态:


它们对应的数组分别为:
char mine[11][11] = {0};//⽤来存放布置好的雷的信息
char show[11][11] = {0};//⽤来存放排查出的雷的个数信息在之前的博客中,我们学习了多文件形式对函数的声明与定义,在这我们对这些知识加以实践,给我们的扫雷游戏设计三个文件:
test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等接下来我们就正式开始编写代码,实现扫雷游戏了。
在一个项目的头文件中,往往需要有对项目所需的标准库头文件的包含、宏的定义、函数的声明等。在这里我们对这些内容分别展开讨论。
要考虑包含哪些头文件,我们首先需要思考要用到哪些标准库函数?
首先毋庸置疑的是,我们要打印棋盘和输入信息,那么就需要用到printf函数和scanf函数,因此就需要包含<stdio.h>头文件。又因为我们需要随机生成雷的位置信息,所以又需要用到rand函数、srand函数和time函数。所以我们又分别需要包含头文件<stdlib.h>和<time.h>。
因此需要包含的头文件如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h> 宏(Macro)是C语言预处理指令的一种,通过#define定义,用于在编译前将代码中的特定标识符替换为预定义的文本。宏分为两种类型:
#define PI 3.14159。#define MAX(a, b) ((a) > (b) ? (a) : (b))。在写代码时,我们常常在以下场景中使用宏:
#define LOG(msg) printf("%s\n", msg))。#ifdef、#ifndef实现跨平台兼容)。其实宏的用法有很多,需要注意的事项也有很多,博主在以后的博客中会加以详细介绍,在本篇博客中就不过多提及了。这次我们将利用对象宏来定义常量。
我们在这里将雷的个数EASY_COUNT定义为10,棋盘的行数ROW定义为9,棋盘列数COL定义为9,总行数ROWS定义为ROW+2,总列数COLS定义为COL+2。如下所示:
#define EASY_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 board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);首先我们来对初始化棋盘函数InitBoard进行实现。我们需要用两个嵌套的for循环语句对二维数组中的每一个元素依次进行初始化,如下所示:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}然后就是对打印棋盘函数的实现:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("--------扫雷游戏-------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i); //打印列号
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印行号
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]); //打印棋盘
}
printf("\n");
}
}接下来是对布置雷函数的实现:
void SetMine(char board[ROWS][COLS], int row, int col)
{
//布置10个雷
//⽣成随机的坐标,布置雷
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1; //使行坐标为0或1
int y = rand() % col + 1; //使列坐标为0或1
if (board[x][y] == '0')
{
board[x][y] = '1'; //将雷置为1
count--;
}
}
}最后就是对排查雷函数的实现了:
//获取格子周边雷的个数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
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 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");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//该位置不是雷,就统计这个坐标周围有⼏个雷
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标⾮法,重新输⼊\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}#include "game.h"
//打印游戏菜单界面
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n"); //开始游戏
printf("***** 0. exit *****\n"); //退出游戏
printf("***********************\n");
}
//游戏功能主函数 --- 实现游戏逻辑
void game()
{
char mine[ROWS][COLS];//存放布置好的雷
char show[ROWS][COLS];//存放排查出的雷的信息
//初始化棋盘
//1. mine数组最开始是全'0'
//2. show数组最开始是全'*'
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//1. 布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//2. 排查雷
FindMine(mine, show, 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;
}在实现了这些基本的游戏功能之后,我们也可以尝试实现以下扩展功能:
简单 9*9 棋盘,10个雷
中等 16*16棋盘,40个雷
困难 30*16棋盘,99个雷
大家在尝试实现这些扩展功能时也可以参考别人做的在线扫雷游戏: http://www.minesweeper.cn/
以上就是本期博客的全部内容啦~如果觉得对您有所帮助或着启发的话,请给博主点赞收藏关注一键三连哦~!感谢大家的支持!