【C语言】扫雷 !!! 保姆级 !!! 教学(含完整思路和源代码)

本文面向C语言初学者,详细介绍了用C语言实现扫雷游戏的过程。先阐述扫雷游戏规则,接着说明模块化编程的设计思路,包括创建和初始化棋盘、布置地雷等。然后逐步讲解代码实现,如设置菜单、排查坐标等,最后给出完整源代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一.前言

二.扫雷游戏规则       

三.游戏设计思路

四.代码实现过程

①设置菜单

②创建并初始化棋盘 

③布置地雷后打印棋盘

④排查坐标

五.完整源代码!!!


一.前言

       几乎每一台电脑上都有一款系统自带的扫雷游戏,相信许多接触过计算机的人都有玩过这款游戏,如果没有玩过也没关系,下面的链接可以让你在线体验这款游戏。扫雷游戏网页版 - Minesweeper

       对于初学C语言的同学来说,实现一个简单的游戏不仅可以检验自己的能力,也能够在敲代码的过程中激发更多新奇的灵感,加深对知识的理解,那么接下来我会带大家一步一个脚印实现一个属于自己的扫雷游戏!

       希望这篇文章对你有所帮助,你的点赞、收藏和评论是对博主的最大支持!有可以改进的地方欢迎讨论(*^▽^*)

二.扫雷游戏规则       

        扫雷界面一共有n*n个方格,以下9*9为例,左上角的数字显示其中一共隐藏着10颗地雷。(如图一)

        玩家选择其中一个方块,如果踩中地雷,则地雷引爆,玩家失败。(如图二)

        如果成功逃脱,则此方块会显示以此方块为中心四周八个方块一共有多少个地雷,将所有安全地带探索完毕,则玩家胜利!(如图三)      

                                                          

               ↑图一                                        ↑图二                                          ↑图三

三.游戏设计思路

1.编程方式采取模块化编程,因为在编程过程中需要多行代码、多个函数,如果都存放在一个文件里,会影响代码的可阅读性,维护、修改起来也更加繁琐,同时也不利于程序员在写代码的过程中保持清晰的思路,故采取模块化编程,对于不同文件代码,只需要在源文件中包含自定义的头文件即可

2.打印游戏菜单,让玩家选择进入游戏还是退出游戏

3.创建并初始化棋盘

4.布置地雷后打印棋盘

5.排查坐标

如何设置棋盘:

扫雷的过程中涉及到数据存储和坐标,自然而然就会想到利用二维数组构成棋盘,其中有雷和无雷用不同的信息表示,可以用1表示有雷,0表示无雷,如下图

将有雷设置为1,无雷设置为0有一个好处,在后续打印某点周围地雷数的时候,直接将某点四周的元素值相加,通过简单的计算即可得出地雷数

但同时也存在弊端,有雷的时候显示1,四周只有一颗雷的时候也显示1,未排查的区域显示0,四周只有一颗雷的时候也显示0,使玩家在游玩的时候很容易被搞晕,那么为了同时解决字符冲突的问题,又保留1和0便于计算的特点,我们可以构建两个棋盘,一个是存放地雷的棋盘,用‘ 1 ’和‘ 0 ’表示;一个是玩家游玩时的棋盘,用 ' * ' 表示未探索的格子,‘ ! ’ 表示地雷,数字表示四周地雷的数量,如下图

←mine棋盘←show棋盘

看似已经完成,但这个棋盘仍然有弊端,当探索棋盘边上的格子的时候,需要计算四周数组元素的值,这个时候会发生栈溢出!导致计算的结果出错,为了避免这一种情况,给棋盘的四周加上一圈,这一圈全都设置为' 0 ',这样就解决了栈溢出的问题,如图所示

←mine棋盘

←show棋盘

注:最外面的一圈只是起到防止栈溢出的作用,在实际游玩过程中1~9行/列才是真正的格子

四.代码实现过程

注:写代码一定要一步一步来!每完成一个小模块就要验证是否能运行,问题早发现早解决!

①设置菜单

     需求:菜单包含两个选项,1.开始游戏;0.退出游戏

     如何实现:创建menu函数,先在屏幕上打印出来菜单选项,在引导玩家输入选项,根据不同的选项进入不同的分支

void menu()
{
	printf("**************************\n");
	printf("******* 1.开始游戏 ********\n");
	printf("******* 0.结束游戏 ********\n");
	printf("**************************\n");
}
        do //使用do-while循环的原因既然打开了游戏,那么至少进入一次菜单界面
	   {
		  menu();//打印出菜单
		  printf("请选择>");//引导玩家输入选项
		  scanf("%d", &input);//输入选项
		  switch (input)//根据不同选项,进入不同分支
		{
       		case 1: //选项 1 ,开始游戏
			game();
			break;
		    case 0: //选项 0 ,结束游戏
			break;
		    default: //输入1和0以外的字符,提示错误
			printf("输入错误!请重新输入\n");
		} 

	   } while (input);//根据给input赋的值是否为0判断是否进入下一次循环

②创建并初始化棋盘 

     需求:(1)创建两个棋盘,棋盘的大小用宏定义常量表示,以便日后添加修改棋盘改变难度的功能;(2)初始化棋盘,将一个棋盘元素全部初始化为‘ 0 ’,另一个棋盘全部元素全部初始化为‘ * ’(3)打印棋盘,验证是否成功初始化

void game()
{
	//创建棋盘,一个是用于存放雷的mine棋盘,一个是用于存放排查出的周边雷的数量的show棋盘
	char mine[ROWS][COLS];
	char show[ROWS][COLS];

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');//将mine棋盘全部初始化为 '0'
	InitBoard(show, ROWS, COLS, '*');//将show棋盘全部初始化为 '*'

	//写代码过程中用于测试棋盘是否成功初始化,实际游玩时记得屏蔽掉调用过此函数的语句
	TeatDisplayBoard(mine, ROWS, COLS);
	TeatDisplayBoard(show, ROWS, COLS);
}
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

//写代码过程中用于测试棋盘是否成功初始化,实际游玩时记得屏蔽掉调用过此函数的语句
void TeatDisplayBoard(char board[ROWS][COLS], int rows, int cols)
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

验证:输入1,输出结果如下

可见初始化成功,屏蔽掉用于验证的打印棋盘代码,进入下一步

③布置地雷后打印棋盘

    需求:(1)随机生成地雷位置,地雷的数量用宏定义常量表示,以便日后实现改变地雷数量的功能;(2)打印出show棋盘,要求有坐标,方便玩家输入坐标

    如何实现:利用srand()、time()和 rand()函数生成随机坐标

//布置地雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
	srand((unsigned int)time(NULL));
	int count = EASY_COUNT; //地雷的数量
	while (count)
	{
	    int x = rand() % row + 1; //生成行的随机数
		int y = rand() % col + 1; //生成列的随机数
		if (board[x][y] == '0') //若该点未布置雷
		{
			board[x][y] = '1'; //则布置雷
			count--; //并且开始下一次布雷,知道EASY_COUNT颗雷全部布置完,count==0时循环才会结束
		}
	}
}

利用TeatDisplayBoard()函数验证是否布置成功,如图,无论重复布置多少次,都是在第1~9行,1~9列随机布置10个地雷,说明布雷成功,第一个需求,满足了,接下来实现第二个需求

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("————扫雷————\n");//游戏标题,同时分割线能够使界面更整洁
	int i = 0, j = 0;
	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");
	}
}

验证:成功打印出show棋盘

④排查坐标

需求:玩家输入坐标后,有四种结果,(1)踩到地雷,游戏结束;(2)没有踩到地雷,显示周围地雷数量;(3)该坐标已排查过,重新输入坐标;(4)超出棋盘范围,报错后重新输入坐标

如何实现:

玩家输入坐标后,判断mine棋盘中对应坐标是否为1,(1)如果为1,打印出mine棋盘,游戏失败;(2)如果为0,进入一个新的函数,计算并返回周边地雷的数量,赋值给show棋盘对应的坐标并打印show棋盘;(3)如果该坐标已经排查过,提示后重新输入坐标;(4)如果超出棋盘范围,提示超过范围后重新输入坐标。

当排查出( ROW*COL-count )个安全坐标的时候,循环结束,玩家胜利

//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
{
	int ret = 0; //接收GetMineCount()的返回值,也就是地雷的数量
	int win = ROW * COL - count;
	while (win != 0)
	{
		int x = 0, y = 0;//横坐标和纵坐标
		printf("请输入坐标>");
		scanf("%d%*c%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//限定输入坐标的范围必须在棋盘内
		{
			system("cls");//清屏,使界面更整洁
			if (mine[x][y] == '1') //踩到雷了
			{
				printf("踩到雷了!游戏结束!\n");//踩到雷
				DisplayBoard(mine, ROW, COL);//打印mine棋盘显示所有雷的位置
				break;
			}
			else if (show[x][y] == '*')//该点还未被排查过,执行排查
			{
				if (mine[x][y] != '1');
				{
					ret = GetMineCount(mine, x, y);//调用函数计算坐标周围地雷数
					if (ret + '0' == '0')//为了使游戏界面更加整洁,如果得到的地雷数为0,则将该位置清空
					{
						show[x][y] = ' ';
					}
					else//得到的地雷数不为0
					{
						show[x][y] = ret + '0'; //将地雷数转化为字符类型的地雷数
					}
					DisplayBoard(show, ROW, COL);
					win--;
				}
			}
			else if (show[x][y] != '*')//该坐标已经不是未知数,并且此时游戏还在继续,说明该点已经被排查过了
			{
				system("cls");
				printf("该坐标以及排查过了,请重新输入!\n");
				DisplayBoard(show, ROW, COL);
			}
		}
		else//输入坐标超过范围,重新输入
		{
			system("cls");//清屏,使界面更整洁
			printf("输入坐标超过棋盘范围!请重新输入!\n");
			DisplayBoard(show, ROW, COL);
		}
	}
    if (win == 0)
	{
		printf("恭喜胜利!\n");
	}
}
//计算坐标周围地雷数
GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
		+ mine[x][y - 1] + mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
	//返回地雷数,注意这是一个字符类型的数组,要得到地雷数字,需要减去8*'0',得到的差值即是地雷数
}

 验证:可以在游戏开始后打印mine棋盘,开始上帝视角,便于快速完成游戏,测试代码是否可行

←上帝视角

测试1:输入重复坐标                                          测试2:输入超出范围的坐标

测试3:踩雷                                                      测试4:排查出所有雷

     

到这里整一个扫雷游戏就完成啦!

五.完整源代码!!!

game.c

#define _CRT_SECURE_NO_WARNINGS 0
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

//游玩棋盘的长宽
#define ROW 9 
#define COL 9

//实际棋盘的长宽度
#define ROWS ROW + 2
#define COLS COL + 2

//地雷数量
#define EASY_COUNT 10

//游戏运行流程
void game();

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

//写代码过程中用于测试棋盘是否成功初始化,实际游玩时记得屏蔽掉调用过此函数的语句
//void TeatDisplayBoard(char board[ROWS][COLS], int rows, int cols);

//布置地雷
void SetMine(char board[ROWS][COLS], int row, int col);

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

//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int count);

//计算坐标周围地雷数
GetMineCount(char mine[ROWS][COLS], int x, int y);


test.c

#define _CRT_SECURE_NO_WARNINGS 0
#include"game.h"
void menu();//菜单
void game();//游戏运行流程
int main()
{
	int input = 0;//菜单选项
	do //使用do-while循环的原因既然打开了游戏,那么至少进入一次菜单界面
	{
		menu();//打印出菜单
		printf("请选择>");//引导玩家输入选项
		scanf("%d", &input);//输入选项
		switch (input)//根据不同选项,进入不同分支
		{
		case 1: //选项 1 ,开始游戏
			game();
			break;
		case 0: //选项 0 ,结束游戏
			break;
		default: //输入1和0以外的字符,提示错误
			printf("输入错误!请重新输入\n");
		} 

	} while (input);//根据给input赋的值是否为0判断是否进入下一次循环

	return 0;
}
void menu()
{
	printf("**************************\n");
	printf("******* 1.开始游戏 *******\n");
	printf("******* 0.结束游戏 *******\n");
	printf("**************************\n");
}
void game()
{
	//创建棋盘,一个是用于存放雷的mine棋盘,一个是用于存放排查出的周边雷的数量的show棋盘
	char mine[ROWS][COLS];
	char show[ROWS][COLS];

	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');//将mine棋盘全部初始化为 '0'
	InitBoard(show, ROWS, COLS, '*');//将show棋盘全部初始化为 '*'


	//布置地雷
	SetMine(mine, ROW, COL);

	//上帝视角,测试代码的时候用
	DisplayBoard(mine, ROW, COL);

	//写代码过程中用于测试棋盘是否成功初始化,实际游玩时记得屏蔽掉调用过此函数的语句
	//TeatDisplayBoard(mine, ROWS, COLS);
	//TeatDisplayBoard(show, ROWS, COLS);

	//打印棋盘
	DisplayBoard(show, ROW, COL);

	//排查地雷
	FindMine(mine, show, ROW, COL, EASY_COUNT);
}

game.c

#define _CRT_SECURE_NO_WARNINGS 0
#include"game.h"

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

////写代码过程中用于测试棋盘是否成功初始化,实际游玩时记得屏蔽掉调用过此函数的语句
//void TeatDisplayBoard(char board[ROWS][COLS], int rows, int cols)
//{
//	int i = 0, j = 0;
//	for (i = 0; i < rows; i++)
//	{
//		for (j = 0; j < cols; j++)
//		{
//			printf("%c ", board[i][j]);
//		}
//		printf("\n");
//	}
//}

//布置地雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
	srand((unsigned int)time(NULL));
	int count = EASY_COUNT; //地雷数量
	while (count)
	{
	    int x = rand() % row + 1; //生成行的随机数
		int y = rand() % col + 1; //生成列的随机数
		if (board[x][y] == '0') //若该点未布置雷
		{
			board[x][y] = '1'; //则布置雷
			count--; //并且开始下一次布雷,知道EASY_COUNT颗雷全部布置完,count==0的时候,循环才会结束
		}
	}
}

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("————扫雷————\n");//游戏标题,同时分割线能够使界面更整洁
	int i = 0, j = 0;
	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");
	}
}

//排查地雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
{
	int ret = 0; //接收GetMineCount()的返回值,也就是地雷的数量
	int win = ROW * COL - count;
	while (win != 0)
	{
		int x = 0, y = 0;//横坐标和纵坐标
		printf("请输入坐标>");
		scanf("%d%*c%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//限定输入坐标的范围必须在棋盘内
		{
			system("cls");
			if (mine[x][y] == '1') //踩到雷了
			{
				printf("踩到雷了!游戏结束!\n");//踩到雷
				DisplayBoard(mine, ROW, COL);//打印mine棋盘显示所有雷的位置
				break;
			}
			else if (show[x][y] == '*')//该点还未被排查过,执行排查
			{
				if (mine[x][y] != '1');
				{
					ret = GetMineCount(mine, x, y);//调用函数计算坐标周围地雷数
					if (ret + '0' == '0')//为了使游戏界面更加整洁,如果得到的地雷数为0,则将该位置清空
					{
						show[x][y] = ' ';
					}
					else//得到的地雷数不为0
					{
						show[x][y] = ret + '0'; //将地雷数转化为字符类型的地雷数
					}
					DisplayBoard(show, ROW, COL);
					win--;
				}
			}
			else if (show[x][y] != '*')//该坐标已经不是未知数,并且此时游戏还在继续,说明该点已经被排查过了
			{
				system("cls");
				printf("该坐标以及排查过了,请重新输入!\n");
				DisplayBoard(show, ROW, COL);
			}
		}
		else//输入坐标超过范围,重新输入
		{
			system("cls");
			printf("输入坐标超过棋盘范围!请重新输入!\n");
			DisplayBoard(show, ROW, COL);
		}
	}
	if (win == 0)
	{
		printf("恭喜胜利!\n");
	}
}

//计算坐标周围地雷数
GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
		+ mine[x][y - 1] + mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
	//返回地雷数,注意这是一个字符类型的数组,要得到地雷数字,需要减去8*'0',得到的差值即是地雷数
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值