C语言小白急救 扫雷游戏(万字保姆级教程)


前言

扫雷游戏是许多人初玩电脑是常玩的游戏,没完过的话也可以试着玩一玩,这样对于编写扫雷游戏会有一个很好的思路的;在编写的过程中我们既可以增强自己的代码编写能力,又可以引起自己的兴趣。

一、游戏编写思路(游戏具备的各种功能)

  1. 为游戏设置一个菜单根据玩家选择开始游戏或退出
  2. 对布置雷的棋盘和显示的棋盘进行初始化
  3. 对棋盘进行打印,将棋盘与其中的元素显示出来
  4. 对棋盘中对雷进行设置
  5. 玩家进行操作,在棋盘中查找某个坐标
  6. 为玩家的操作做出反馈(包括:(1)此坐标没有雷,为玩家显示周围是否有几颗雷 (2)此坐标就周围无雷,进行爆炸性排除 (3)此坐标有雷,显示"你被炸死了" (4)玩家输入错误,显示“输入错误,请重新输入” (5)此处已被排查过,显示"此处已排查,请重新输入" (6)除雷外的位置都被找到,玩家赢了
  7. 待本局游戏结束后,再次打印菜单,玩家选择游戏或退出

二、功能实现

1.构建游戏菜单

游戏菜单的主要功能是供玩家选择“开始游戏”或“退出”
代码及实现结果如下:

void Menu()
{
	printf("**************\n");
	printf("****0.退出 ***\n");
	printf("****1.Play ***\n");
	printf("**************\n");
}

void Game()
{
	printf("已进入游戏,本次执行完成\n");
}

int main()
{
	int input = 0;
	do
	{
		Menu();
		printf("请输入你想要执行的操作:");
		scanf_s("%d", &input);//玩家输入的值,与菜单和下面的switch语句对应
		switch (input)
		{
		case 1:
			printf("已进入游戏");
			break;
		case 0:
			break;
		default://0和1外的其他值
			printf("输入错误,请重试\n");//输入了菜单中没有的值,要重新输入
		}
	} while (input);//对值进行判定,0为假不继续执行,非零为真,继续循环
}

执行结果
在代码中,我们使用简单的printf函数之间将菜单全部打印在屏幕上,然后在主函数中,使用了do while 语句和switch 语句来对玩家输入的值做出响应。
代码详解:do while语句中循环至少会执行一次,为玩家打印出菜单以及对玩家输入的值做出响应,输入1则会进入Game函数中,这是我设计的游戏函数,之后的游戏相关功能我都会在此设置,在游戏完成后会打印本局游戏已结束,然后向下执行,由于while()中的判断值是玩家输入的值,输入值如果是1,则会继续进行循环(非零为真),再次跳出菜单供玩家选择,如果输入值是0,则会停止(0为假,不满足再次进入循环的条件),如果输入了其他值,在switch语句中的default子句(除case子句中的值 外的值会进入)会显示重新玩家重新输入值,然后再while()处进行判定(非零,满足条件),后进入循环。

2.棋盘初始化

棋盘初始化化的整体思路与遍历类似,就是将其中的元素都设定为一个值。
代码如下:

#define ROW 9
#define COL 9

#define ROWS ROW+2//初始化时在上下各多一行
#define COLS COL+2//初始化时在左右各多一列
//为了以后在排查时不会超限

//初始化
void InitMine(char mine[ROWS][COLS], int row, int col,char symbol)
{
	int i = 0;
	int j = 0;
	for (i=0;i<row;i++)
	{
		for (j=0;j<col;j++)
		{
			mine[i][j] = symbol;//根据不同的符号需求设定不同符号
		}
	}
}

代码详解:由于我们需要设置两个棋盘:设置雷的棋盘和显示给玩家的棋盘(毕竟不能直接把雷的位置显示给玩家),所以我们要为他们设置不同的符号;此外,为了我们方便对棋盘的大小进行设置,使用#define 定义了四个常ROW,COL,ROWS,COL;ROW与COL为9,是我们为玩家显示的棋盘:9*9,ROWS与COLS比显示的棋盘长度多2即上下左右各多一行,是为了便于后面在排查坐标时不会超出范围(对某个坐标周边的全部坐标进行排查,在靠边的坐标处可能会超出范围),函数调用时只需要将symbol改为自己选择的符号 (注符号要打单引号)。

3.屏幕显示

将棋盘中的元素显示给用户,只需在显示棋盘进行调用,相应细节以写在注释中
代码和运行结果如下:

void Game()//调用
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitMine(mine,ROWS,COLS,'0');
	InitMine(show, ROWS, COLS, '*');
	DisplayMine(show, ROW, COL);//显示对玩家的那块棋盘
}

//显示
void DisplayMine(char mine[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("--扫雷游戏--\n");//美观性设计
	for (j = 0; j <= col; j++)//没有其他条件,默认在第一行打印
	{
		printf("%d",j);//打印列号
	}
	printf("\n");//换行符,将其他打印的元素的起始位置变为下一行

	for (i = 1; i <= row; i++)//i=0时打印列号
	{
		printf("%d", i);//打印行号
		for (j = 1; j <= col; j++)
		{
			printf("%c", mine[i][j]);//打印棋盘中的元素
		}
		printf("\n");//每行进行换行处理
	}
	printf("--扫雷游戏--\n");
}

执行结果

3.雷的布置

使用srand与time函数生成一个随机值,再用rand函数接受,并对齐进行处理,使获得的值符合棋盘范围;srand()和rand()的头文件是<stdlib.h>,time()的头文件是<time.h>;因为sand()只需要调用一次即可,所以将其放入主函数中。其余细节在注释中。
代码及运行结果:

srand((unsigned int)time(NULL));//生成随机值

void Game()//调用功能函数
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitMine(mine,ROWS,COLS,'0');
	InitMine(show, ROWS, COLS, '*');
	DisplayMine(show, ROW, COL);
	SetMine(mine, ROW, COL);
	DisplayMine(mine, ROW, COL);//显示布置了雷后的棋盘
}

//雷的设置
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = mineamount;//最大布置雷的数量
	while (count)//雷的数量不为0
	{
		int m = rand() % row + 1;//对随机值的范围进行改变,使其符合棋盘范围
		int n = rand() % col + 1;
		if (mine[m][n] == '0')//棋盘初始化时的值,避免两个值布置在同一位置
		{
			mine[m][n] = '1';//将雷的位置的符号改变
			count--;//每布置一颗雷,剩余可布置雷数减少1
		}
	}
}

运行结果

4.玩家操作与页面反馈

本功能是整个扫雷游戏中最重要的功能,其中应用到的知识也较难,包括主要包括循环与递归。
代码和结果如下图:

//统计周围有几颗雷
int Statistics(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';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}

//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
	{
		for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
		{
			if (show[i][j] == '*')//未排查过的数
			{
				int count = Statistics(mine, i, j);//与排雷函数一致
				if (count == 0 )
				{
					show[i][j] = ' ';
					Find(mine, show, i, j);//递归,再次对函数进行调用
				}
				else
				{
					show[i][j] = count + '0';
				}
			}
		}
	}
}

//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i=1;i<=row;i++)
	{
		for (j=1;j<=col;j++)
		{
			if (show[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}

//排雷函数(主体)
void FindMine(char mine[ROWS][COLS],char show[ROWS][ROWS], int row,int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		int count = Full(show,row,col);
		if (count == mineamount)//剩余数与雷数相等则胜利
		{
			printf("你赢了\n");
			break;
		}
		printf("请输入你想要查看的坐标:");
		scanf_s("%d %d", &x, &y);
		if (x>0 && x<=row && y>0 && y<=col)
		{
			if (mine[x][y] == '0')
			{
				int count = Statistics(mine, x, y);//周围有几颗雷
				if (count == 0)//周围没有雷,开始扩大范围查找
				{
					show[x][y] = ' ';//将排查过的位置显示为空
					Find(mine,show,x,y);
					DisplayMine(show,row,col);
				}
				else
				{
					show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
					DisplayMine(show, row, col);
				}
			}
			if (show[x][y] != '*')
			{
				printf("此处已排查,请重新输入\n");
			}
			if (mine[x][y] == '1')
			{
				printf("你被炸死了\n");
				DisplayMine(mine, ROW, COL);
				break;
			}
		}
		else
		{
			printf("输入错误请重新输入:\n");
		}
	}

}

运行结果
代码详细讲解:
(1)主体是最下面的排雷函数,while()循环中,判断值一直是1,所以循环会一直执行,只能用break函数跳出,而跳出的条件我设置了两个:赢或者被炸死
赢的这一部分与其对应的函数如下:

int count = Full(show,row,col);
		if (count == mineamount)//剩余数与雷数相等则胜利
		{
			printf("你赢了\n");
			break;
		}
		
//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i=1;i<=row;i++)
	{
		for (j=1;j<=col;j++)
		{
			if (show[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}

赢的条件是将所有不是雷的位置排除,在我所设置的条件中,一旦某个点被排查,对玩家的显示部分即show[][] 就会发生改变,所以我们可以统计其未被改变的值,如果没有被改变,统计值count就+1,当未被改变的值刚好与我们设置的雷的值相等时,就会break跳出循环,我们就赢了。
(2)输的条件是我们被炸死了,代码如下:

if (mine[x][y] == '1')
			{
				printf("你被炸死了\n");
				DisplayMine(mine, ROW, COL);
				break;
			}

如果我们查找的恰好是我们设置的雷值:‘1’,那么就会显示“你被炸死了”,然后将整个雷的位置分布图打印给我们,然后跳出循环。
(3)判定输入是否正确,就是最外围的if与else,代码如下:

if (x>0 && x<=row && y>0 && y<=col)
        {
        }
else
		{
			printf("输入错误请重新输入:\n");
		}

正常的数组下标是由0开始的,小于我们计算出的数组值(因为我们是从1开始的)但是这里不同,由于我们在第0行与第0列的位置打印了行号和l列号,且我们初始化的时候范围比9*9打,不必担心超范围的事,所以我们的范围是>0即从1开始,一直到行列的最大值,输入的值不满足就会被判定重新输入
(4)主排查区
符合全部要求的就会进入我们的主排查区进行排查,代码与涉及到的函数如下:

if (mine[x][y] == '0')
			{
				int count = Statistics(mine, x, y);//周围有几颗雷
				if (count == 0)//周围没有雷,开始扩大范围查找(爆炸性查找,节省玩家时间)
				{
					show[x][y] = ' ';//将排查过的位置显示为空,便与判段是否被排查过
					Find(mine,show,x,y);
					DisplayMine(show,row,col);//当此次查找完成时,打印出显示图,为玩家下次查找提供参考
				}
				else
				{
					show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
					DisplayMine(show, row, col);//当此次查找完成时,打印出显示图,为玩家下次查找提供参考
				}
			}

//统计周围有几颗雷
int Statistics(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';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}

//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
	{
		for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
		{
			if (show[i][j] == '*')//未排查过的数
			{
				int count = Statistics(mine, i, j);//与排雷函数一致
				if (count == 0 )
				{
					show[i][j] = ' ';
					Find(mine, show, i, j);//函数递归,对满足周围没有雷这一条件的数,再次调用此函数
				}
				else
				{
					show[i][j] = count + '0';
				}
			}
		}
	}
}

三、 完整代码及实现结果

1.mine.h

游戏包含的函数声明,函数的头文件的包含以及宏定义

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

#define ROW 9
#define COL 9
#define mineamount 10

#define ROWS ROW+2//初始化时在上下各多一行
#define COLS COL+2//初始化时在左右各多一列
//为了以后在排查时不会超限

void InitMine(char mine[ROWS][COLS],int row,int col,char symbol);
void DisplayMine(char mine[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][ROWS], int row, int col);

2.mine.c

游戏中使用到的函数

#include "mine.h"

//初始化
void InitMine(char mine[ROWS][COLS], int row, int col,char symbol)
{
	int i = 0;
	int j = 0;
	for (i=0;i<row;i++)
	{
		for (j=0;j<col;j++)
		{
			mine[i][j] = symbol;//根据不同的符号需求设定不同符号
		}
	}
}

//显示
void DisplayMine(char mine[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("--扫雷游戏--\n");
	for (j = 0; j <= col; j++)//没有其他条件,默认在第一行打印
	{
		printf("%d",j);//打印列号
	}
	printf("\n");//换行符,将其他打印的元素的起始位置变为下一行

	for (i = 1; i <= row; i++)//i=0时打印列号
	{
		printf("%d", i);//打印行号
		for (j = 1; j <= col; j++)
		{
			printf("%c", mine[i][j]);//打印棋盘中的元素
		}
		printf("\n");//每行进行换行处理
	}
	printf("--扫雷游戏--\n");
}

//雷的设置
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = mineamount;//最大布置雷的数量
	while (count)//雷的数量不为0
	{
		int m = rand() % row + 1;//对随机值的范围进行改变,使其符合棋盘范围
		int n = rand() % col + 1;
		if (mine[m][n] == '0')//棋盘初始化时的值,避免两个值布置在同一位置
		{
			mine[m][n] = '1';//将雷的位置的符号改变
			count--;//每布置一颗雷,剩余可布置雷数减少1
		}
	}
}

//统计周围有几颗雷
int Statistics(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';//计算周围有几颗雷;'1'-'0'=1;1的ASCII值为049,0的为048;
}

//查找周围值附近的雷
void Find(char mine[ROWS][COLS], char show[ROWS][ROWS], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = x - 1; i >= x - 1 && i <= x + 1; i++)//排查范围
	{
		for (j = y - 1; j >= y - 1 && j <= y + 1; j++)
		{
			if (show[i][j] == '*')//未排查过的数
			{
				int count = Statistics(mine, i, j);//与排雷函数一致
				if (count == 0 )
				{
					show[i][j] = ' ';
					Find(mine, show, i, j);
				}
				else
				{
					show[i][j] = count + '0';
				}
			}
		}
	}
}

//判断还剩余几个位置
int Full(char show[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i=1;i<=row;i++)
	{
		for (j=1;j<=col;j++)
		{
			if (show[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}


//排雷
void FindMine(char mine[ROWS][COLS],char show[ROWS][ROWS], int row,int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		int count = Full(show,row,col);
		if (count == mineamount)//剩余数与雷数相等则胜利
		{
			printf("你赢了\n");
			break;
		}
		printf("请输入你想要查看的坐标:");
		scanf_s("%d %d", &x, &y);
		if (x>0 && x<=row && y>0 && y<=col)
		{
			if (mine[x][y] == '0')
			{
				int count = Statistics(mine, x, y);//周围有几颗雷
				if (count == 0)//周围没有雷,开始扩大范围查找
				{
					show[x][y] = ' ';//将排查过的位置显示为空
					Find(mine,show,x,y);
					DisplayMine(show,row,col);
				}
				else
				{
					show[x][y] = count + '0';//显示此坐标周围有几颗雷,count为int类型+'0'转换为char类型,符合棋盘类型
					DisplayMine(show, row, col);
				}
			}
			if (show[x][y] != '*')
			{
				printf("此处已排查,请重新输入\n");
			}
			if (mine[x][y] == '1')
			{
				printf("你被炸死了\n");
				DisplayMine(mine, ROW, COL);
				break;
			}
		}
		else
		{
			printf("输入错误请重新输入:\n");
		}
	}

}

3.test.c

游戏主体,用于测试

#include"mine.h"

void Menu()
{
	printf("**************\n");
	printf("****0.退出 ***\n");
	printf("****1.Play ***\n");
	printf("**************\n");
}

void Game()//调用功能函数
{
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitMine(mine,ROWS,COLS,'0');
	InitMine(show, ROWS, COLS, '*');
	DisplayMine(show, ROW, COL);
	SetMine(mine, ROW, COL);
	FindMine(mine, show, ROW, COL);
}

int main()
{
	srand((unsigned int)time(NULL));//生成随机值
	int input = 0;
	do
	{
		Menu();
		printf("请输入你想要执行的操作:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			Game();
			printf("本次游戏已结束\n");
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重试\n");
		}
	} while (input);
}

// 
	SetMine(mine,ROW,COL);
	FindMine(mine, show,ROW,COL);

4.运行结果

运行结果

总结

扫雷程序的编写,我认为对于C语言小白来说是一个很大的挑战,但同时,在编写完成后我们对于如何编写一个较大的程序,也会有自己的思考,会成为以后编写的一块垫脚石同时也会提高自己的编程能力。希望我此次的教程能对各位C语言小白的成长起到一定的助力,谢谢大家!

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值