深入剖析扫雷游戏的 C 语言实现


扫雷游戏是一款经典的益智类游戏,在计算机编程领域,使用 C 语言来实现扫雷游戏可以很好地锻炼开发者对数组操作、逻辑控制以及随机数运用等多方面的编程技能。本文将对上述给定的扫雷游戏 C 语言代码进行详细的文字说明,深入剖析其各个组成部分的功能与实现逻辑。

一、头文件部分
(一)#pragma once
#pragma once 是一种预编译指令,其作用是确保头文件在一个编译单元中只被包含一次。这有助于避免因多次包含同一头文件而导致的重复定义错误,提高编译效率并减少代码冗余。

(二)包含的标准库头文件
<stdio.h>:这是 C 语言标准输入输出头文件,提供了诸如 printf、scanf 等用于控制台输入输出操作的函数声明。在扫雷游戏中,printf 函数用于显示游戏菜单、棋盘信息以及提示信息等,scanf 函数则用于获取玩家输入的坐标等信息。
<stdlib.h>:该头文件包含了一些通用的实用函数,例如 system 函数。在本游戏的 main 函数中,system(“pause”) 用于暂停程序执行,等待用户按下任意键继续;system(“cls”) 用于清屏操作,使游戏界面更加整洁。
<time.h>:时间相关的头文件,主要用于获取当前时间信息。在游戏中,结合 srand 函数使用,通过 time(NULL) 获取当前时间的秒数作为随机数生成器的种子,从而实现随机布雷的功能,使得每次游戏的雷区布局都具有随机性。
(三)宏定义
#define ROW 9 和 #define COL 9:分别定义了游戏棋盘的行数和列数,这里设定为 9x9 的棋盘大小。通过宏定义,可以方便地修改棋盘的规模,如果需要调整游戏难度或界面大小,只需更改这些宏的值即可。
#define ROWS ROW + 2 和 #define COLS COL + 2:定义了包含边界的棋盘行数和列数。在实际的游戏逻辑处理中,为了方便处理边界情况,通常会在棋盘的四周额外添加一圈边界元素,这样可以简化判断坐标是否合法以及计算周围雷数等操作的代码逻辑。
#define TNT 10:定义了游戏中炸弹的个数为 10 个。同样,这个值也可以根据游戏难度的调整而修改,例如增加炸弹数量会使游戏难度提高。
(四)函数声明
void initboard(char arr[ROWS][COLS], int rows, int cols, char set):此函数用于初始化棋盘数组。它接受一个二维字符数组 arr、行数 rows、列数 cols 以及一个字符 set 作为参数,将数组中的每个元素都设置为指定的字符 set。在游戏初始化阶段,分别用于将表示雷区的数组 mine 初始化为 ‘0’,表示未布雷;将用于展示给玩家的数组 show 初始化为 ‘*’,表示未排查的区域。
void displayboard(char arr[ROWS][COLS], int row, int col):用于在控制台打印棋盘信息。它接受一个二维字符数组 arr、实际的棋盘行数 row 和列数 col 作为参数,按照一定的格式输出棋盘的内容,包括行号、列号以及每个格子对应的字符,使玩家能够直观地看到游戏棋盘的当前状态。
void setmine(char mine[ROWS][COLS], int row, int col):负责在雷区棋盘上随机布雷。该函数接受表示雷区的二维字符数组 mine、棋盘行数 row 和列数 col 作为参数,根据设定的炸弹数量 TNT,通过随机数生成函数 rand 在合法的棋盘坐标范围内随机选择位置放置炸弹,即将对应位置的元素设置为 ‘1’。
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col):实现玩家排雷的核心逻辑。它接受雷区数组 mine、展示数组 show、棋盘行数 row 和列数 col 作为参数,在循环中不断获取玩家输入的排查坐标,判断坐标的合法性以及该位置是否已经排查过。如果坐标合法且未排查过,进一步判断该位置是否有炸弹,如果有则游戏结束并显示雷区;如果没有炸弹,则统计该坐标周围的炸弹数量,并更新展示数组 show,同时记录已成功排查的格子数量 win,当 win 达到总格子数减去炸弹数时,玩家获胜并显示雷区。
#pragma once

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

#define ROW 9
#define COL 9

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

#define TNT 10//设置炸弹个数
//初始化
void initboard(char arr[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void displayboard(char arr[ROWS][COLS], int row, int col);
//布雷
void setmine(char mine[ROWS][COLS], int row, int col);
//排雷
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
游戏主体部分(函数部分)
二、游戏主体函数部分
(一)initboard 函数
这个函数通过两层嵌套的 for 循环遍历整个二维数组 arr,将数组中的每一个元素都赋值为传入的字符 set。在游戏初始化时,对于 mine 数组,set 为 ‘0’,表示该位置无雷;对于 show 数组,set 为 ‘*’,表示该位置尚未被玩家排查。这样就完成了棋盘数组的初始化工作,为后续的布雷和排雷操作奠定了基础。

(二)displayboard 函数
该函数首先打印出游戏的标题 “------- 扫雷游戏 -------”,然后通过两个循环来打印棋盘。外层循环用于控制行数,内层循环用于控制列数。先打印出列号,从 0 到 col,然后在每一行中,先打印出行号,再逐个打印该行中每个格子对应的字符。这样就以一种清晰直观的方式将棋盘的当前状态展示给玩家,包括已排查的格子、未排查的格子以及炸弹的位置(在游戏结束后显示雷区时)。

(三)setmine 函数
在这个函数中,首先设定一个计数器 count,其初始值为炸弹的总数 TNT。然后进入一个 while 循环,循环条件是 count 不为 0,即还有炸弹需要布置。在循环内部,通过 rand 函数生成随机数作为坐标 x 和 y,这里的随机数范围是 1 到棋盘的实际行数 row 和列数 col(因为棋盘有额外的边界行和列,雷是布置在实际游戏区域内)。生成坐标后,检查该坐标对应的 mine 数组元素是否为 ‘0’,如果是,则表示该位置未被布雷,将其设置为 ‘1’,表示放置了炸弹,同时计数器 count 减 1。通过这种方式,确保在不重复的位置上随机布置了指定数量的炸弹。

(四)getcountmine 函数
这是一个静态内部函数,其作用是统计给定坐标 (x, y) 周围 8 个相邻格子(包括对角线格子)中的炸弹数量。通过两层嵌套的 for 循环遍历坐标 (x, y) 周围的 9 个格子(x - 1 到 x + 1,y - 1 到 y + 1),将每个格子对应的字符(如果是 ‘1’ 表示有雷)转换为数字并累加到 countmine 变量中。最后返回 countmine 的值,即周围雷的总数。这个函数在 findmine 函数中被调用,用于确定玩家排查的格子周围的安全情况。

(五)findmine 函数
该函数是玩家排雷操作的核心处理逻辑。首先定义了几个变量,x 和 y 用于存储玩家输入的排查坐标,countmine 用于存储周围雷的数量,win 用于记录已成功排查的格子数量。然后进入一个 while 循环,循环条件是 win 小于总格子数减去炸弹数,即还有未排查的非雷格子。在循环内部,首先提示玩家输入排查坐标,并使用 scanf 函数获取输入。接着判断坐标的合法性,如果坐标在合法范围内且该位置在 show 数组中未被排查过(为 ‘*’),则进一步判断该位置是否有炸弹。如果有炸弹,则输出失败信息并显示雷区,然后跳出循环;如果没有炸弹,则调用 getcountmine 函数统计周围雷数,将结果转换为字符后更新 show 数组中该位置的元素,并显示更新后的棋盘,同时 win 变量加 1。如果玩家输入的坐标不合法或重复,则输出相应的提示信息。当循环结束后,如果 win 达到了总格子数减去炸弹数,说明玩家成功排雷,输出胜利信息并显示雷区。

#define _CRT_SECURE_NO_WARNINGS 1

#include"game.h"
//初始化
void initboard(char arr[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0;i < rows;i++)
{
for (int j = 0;j < cols;j++)
arr[i][j] = set;
}
}
//打印棋盘
void displayboard(char arr[ROWS][COLS], int row, int col)
{
printf(“-------扫雷游戏-------\n”);
int i = 0;
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 ", arr[i][j]);
	}
	printf("\n");
}

}
//布雷
void setmine(char mine[ROWS][COLS], int row, int col)
{
int count = TNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == ‘0’)
{
mine[x][y] = ‘1’;
count–;
}
}
}
//排雷
static int getcountmine(char mine[ROWS][COLS], int x, int y)//统计排查坐标附近雷个数
{
int countmine = 0;
for (int i = x - 1;i <= x + 1;i++)
{
for (int j = y - 1;j <= y + 1;j++)
{
countmine += mine[i][j] - ‘0’;
}
}
return countmine;
}
void findmine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{

int x = 0;
int y = 0;
int countmine = 0;
int win = 0;
while (win < (row * col - TNT))
{
	printf("请输入要排查的坐标->:\n");
	scanf("%d%d", &x, &y);
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		if (show[x][y] == '*')
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾你被炸死了\n");
				displayboard(mine, ROW, COL);
				break;
			}
			else 
			{
				countmine = getcountmine(mine, x, y);
				show[x][y] = countmine + '0';
				displayboard(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("排查坐标重复,请重新输入->:\n");
		}
	}
	else
	{
		printf("坐标非法,请重新选择->:\n");
		break;
	}
}
if (win == row*col - TNT)
{
	printf("恭喜你排雷成功\n");
	displayboard(mine, ROW, COL);
}

}
main()函数部分,游戏菜单
#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[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化
initboard(mine, ROWS, COLS, ‘0’);
initboard(show, ROWS, COLS, ‘*’);
//打印棋盘
displayboard(show, ROW, COL);
/displayboard(mine, ROW, COL);/
//布雷
setmine(mine, ROW, COL);

//排查雷
findmine(mine, show, ROW, COL);

}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();//游戏菜单
printf(“请选择->:\n”);
scanf(“%d”, &input);
switch (input)
{
case 1://游戏主体部分
game();break;
case 0:
printf(“游戏结束\n”);break;
default:printf(“输入错误,请重新输入->:\n”);
}
} while (input);
}

int main()
{
test();
system(“pause”);
system(“cls”);
return 0;
}