C语言趣味小游戏扫雷(数组,函数)

扫雷游戏相关介绍

在这里插入图片描述

扫雷这种经典益智小游戏,相信不少人都玩过或听说过,若没听过,也没事下面有扫雷玩法的介绍。
扫雷的规则如下:
盘面上有许多方格,方格中随机分布着一些雷。你的目标是避开雷,打开其他所有格子。一个非雷格中的数字表示其相邻8格中的雷数,你可以利用这个信息推导出安全格和雷的位置。
看完上面的规则介绍,相信你已经对扫雷已经有一定的了解了,那么现在就让我们利用C语言,开发出一款属于我们自己的简易扫雷游戏。
注:游戏里面涉及的知识为函数,数组,循环,分支以及C语言头文件的创建,开发环境为 Visual Studio 2022。

扫雷游戏实现

首先我们先考虑完成扫雷游戏需要那写步骤:
第一步:开始菜单
第二步:布置棋盘
第三步:打印棋盘
第四步:布置雷
第五步:排查雷
注:
这个游戏一共分为三个部分来实现
666

test.c(写游戏的主函数)
game.c(写游戏中函数的实现等)
game.h(写游戏需要的库函数引用和函数声明等)

开始菜单

最开始,我们肯定是先打印出菜单,然后输入内容,来判断是开始游戏还是退出游戏去执行相应的分支。先输出再判断,C语言中的 do{ } while()语句 正好也是先执行再判断正好满足,接着我们打印菜单,写的过程中我们发现,打印菜单的代码写在主函数中,在阅读程序的时候主函数看上去不会很简洁,这个时候我们选择把打印菜单的语句放到menu()这个函数里。然后用switch语句来执行选择的不同分支,此外选择 0 作为退出游戏是因为在C语言中 0为假 非0为真,到while这里进行判断如果是0就直接退出,若为其他则不会退出 do{ } while()语句。game()这个函数放在game.c中。

void menu()
{
	printf("**********************\n");
	printf("***** 1:开始游戏 *****\n");
	printf("***** 0:退出游戏 *****\n");
	printf("**********************\n");
}
	int input = 0;
	do 
	{
		menu();
		printf("请选择:> \n");
		scanf("%d",&input);
		switch (input)
		{
		    case 1:
				game();
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,重新输入\n");
				break;
		}
	} while (input);

布置棋盘

利用两个for循环把指定的字符存入整个二维数组,第一个for循环是遍历行,第二个for循环是遍历数组的列。就比如这个3行5列的二维数组,我们先是从i=0,j=0;的位置开始向后遍历一直遍历完j=5后,i++ i变成1,然后接着往后面循环,一直到整个二维数组遍历完。还有就是数组初始化的行和列一定要比实际显示出来的内容要多2行2列,因为在排查雷的时候,一个非雷格中的数字要表示其相邻8格中的雷数,若不这样去实现的话会造成数组的越界,出现程序运行错误。
在这里插入图片描述

/*
*   函数功能:将指定的字符存入到二维字符数组
*   输入值:二维字符数组,数组的行,数组的列,存的值
*	 返回值:无
*/
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			Board[i][j] = set;
		}
	}
}

打印棋盘

打印棋盘的原理和上面布置棋盘的原理几乎相同,只是把给数组赋值变为打印数组内容,从数组的1行1列开始一直到9行九列结束,并且在每打印完一行后进行换行。但是为了方便查找,又加了打印每个元素相对应的坐标,首先用一个for循环打印最上面的纵坐标,记得加上空格使数字间隔开来,接着在每一行开始处打印 i的值与空格,做为横坐标。
在这里插入图片描述

/*
*   函数功能:打印二维字符数组的内容到屏幕上
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void DisplayBoard(char Board[ROWS][COLS], int row, int col)
{
   //打印纵坐标
	for (int j = 0; j <= row; j++)
	{
		printf("%d ", j);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i); //打印横坐标
		for (int j = 1; j <=col; j++)
		{
			printf("%c ", Board[i][j]);
		}
		printf("\n");
	}
}

布置雷

布置雷这里我们,需要用到C语言的两个库
#include “stdlib.h”
#include “time.h”
分别引用 srand()初始化rand函数的种子,rand()返回一个范围在 0 到 RAND_MAX 之间的伪随机数,time()返回一个自 1970-01-01 起到现在时间相距的秒数 这三个函数,此外srand()和time()只需要调用一次所以就被放在game()函数里。

/*
*   函数功能:将字符'1'存放到随机的二维数组中,一共存放10个
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void SetMine(char Board[ROWS][COLS], int row, int col)
{
	int count = MODEL;//MOBEL为布置雷的个数,他的声明放在game.h中
	while (count)
	{
		int x = rand() % (row + 1);//得到10以内的随机数赋值给变量X
		int y = rand() % (col + 1);
		if (x >= 1  && y >= 1 )//保证在扫雷的区域范围内
		{
			if (Board[x][y] == '0')//如果不是雷,就布置一个雷
			{
				Board[x][y] = '1';
				count--;//当count减到0时表示雷已经布置完成退出循环
			}
		}
	}
}

排查雷

这部分,我们需要抓住三个点:
1:输入两个值来确定这个扫雷棋盘要扫的雷的位置。
2:如果输入的值不在我们棋盘的范围,就提示错误重新输入防止数组越界。
3:判断是否为雷,如果是雷结束,若不是获取当前输入坐标周围的雷的个数,显示到要排查除雷的信息的数字里。
这个时候我们就可以用到while循环保证一直多次输入,if判断是否超过范围,然后再来一个if来判断输入的坐标在棋盘里面是否为雷,要是为雷退出循环,若不为雷就调用GetMine()函数获取当前输入坐标周围的雷的个数,并且存入到在要排查雷的数组里。
这里面最为特殊的就是整形类型的数字转字符类型的数字,通过加减字符’0’来进行转换。
下图为要显示附近雷的个数的坐标图:
在这里插入图片描述

/*
*   函数功能:查找一个非雷格中的数字要表示其相邻8格中的雷数
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:相邻8格中的雷数
*/
static int GetMine(char Board[ROWS][COLS], int x, int y)
{
	return (Board[x - 1][y - 1] + Board[x - 1][y] +
		Board[x - 1][y + 1] + Board[x][y - 1] +
		Board[x][y + 1] + Board[x + 1][y - 1] +
		Board[x + 1][y] + Board[x + 1][y + 1]-8*'0');
}

/*
*   函数功能:排查雷
*   输入值:二维字符数组,二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void FindMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0,win=0;
	while (win<(row*col-MODEL))//判断雷是否全部排完
	{
		printf("请输入坐标:>\n");
		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
			{
			   //将GetMine()函数返回的int类型的数字,转换字符型数字
				Show[x][y]=GetMine(Mine,x,y)+'0';
				//打印要排查除雷的信息
				DisplayBoard(Show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("输入的坐标非法!,重新输入\n");
		}
	}
	//如果雷全部找完了
	if (win == (row * col - MODEL))
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(Mine, ROW, COL);
	}
}

三个文件的所有代码

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//菜单
void menu()
{
	printf("**********************\n");
	printf("***** 1:开始游戏 *****\n");
	printf("***** 0:退出游戏 *****\n");
	printf("**********************\n");
}

int main()
{
	int input = 0;
	do 
	{
		menu();
		printf("请选择:> \n");
		scanf("%d",&input);
		switch (input)
		{
		    case 1:
				game();
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误,重新输入\n");
				break;
		}
	} while (input);
	return 0;
}

game.h

#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 MODEL 10
//游戏
void game();
//初始化棋盘
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);

game.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void game()
{
	srand((unsigned int)time(NULL));
	char mine[ROWS][COLS];//存放布置好的雷
	char show[ROWS][COLS];//存放要排查除雷的信息
	//布置棋盘
	 InitBoard(mine, ROWS, COLS, '0');
	 InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	 DisplayBoard(show, ROW, COL);
	//布置雷
	 SetMine(mine, ROW, COL);
	 //DisplayBoard(mine, ROW, COL);
	//排查雷
	 FindMine(mine,show,ROW,COL);
}
/*
*   函数功能:将指定的字符存入到二维字符数组
*   输入值:二维字符数组,数组的行,数组的列,存的值
*	 返回值:无
*/
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			Board[i][j] = set;
		}
	}
}

/*
*   函数功能:打印二维字符数组的内容到屏幕上
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void DisplayBoard(char Board[ROWS][COLS], int row, int col)
{
   //打印纵坐标
	for (int j = 0; j <= row; j++)
	{
		printf("%d ", j);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i); //打印横坐标
		for (int j = 1; j <=col; j++)
		{
			printf("%c ", Board[i][j]);
		}
		printf("\n");
	}
}

/*
*   函数功能:将字符'1'存放到随机的二维数组中,一共存放10个
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void SetMine(char Board[ROWS][COLS], int row, int col)
{
	int count = MODEL;//MOBEL为布置雷的个数,他的声明放在game.h中
	while (count)
	{
		int x = rand() % (row + 1);//得到10以内的随机数赋值给变量X
		int y = rand() % (col + 1);
		if (x >= 1  && y >= 1 )//保证在扫雷的区域范围内
		{
			if (Board[x][y] == '0')//如果不是雷,就布置一个雷
			{
				Board[x][y] = '1';
				count--;//当count减到0时表示雷已经布置完成退出循环
			}
		}
	}
}

/*
*   函数功能:查找一个非雷格中的数字要表示其相邻8格中的雷数
*   输入值:二维字符数组,数组的行,数组的列
*	 返回值:相邻8格中的雷数
*/
static int GetMine(char Board[ROWS][COLS], int x, int y)
{
	return (Board[x - 1][y - 1] + Board[x - 1][y] +
		Board[x - 1][y + 1] + Board[x][y - 1] +
		Board[x][y + 1] + Board[x + 1][y - 1] +
		Board[x + 1][y] + Board[x + 1][y + 1]-8*'0');
}

/*
*   函数功能:排查雷
*   输入值:二维字符数组,二维字符数组,数组的行,数组的列
*	 返回值:无
*/
void FindMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col)
{
	int x = 0, y = 0,win=0;
	while (win<(row*col-MODEL))//判断雷是否全部排完
	{
		printf("请输入坐标:>\n");
		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
			{
			   //将GetMine()函数返回的int类型的数字,转换字符型数字
				Show[x][y]=GetMine(Mine,x,y)+'0';
				//打印要排查除雷的信息
				DisplayBoard(Show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("输入的坐标非法!,重新输入\n");
		}
	}
	//如果雷全部找完了
	if (win == (row * col - MODEL))
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(Mine, ROW, COL);
	}
}