C语言学会数组还可以做扫雷 - 快来试试吧(包含源码)

本篇给大家带来扫雷小游戏,有简单,中等,和困难三种模式供玩家选择,读者如有更好的建议,可以在评论区留言

代码模块将分为3个部分,游戏页面逻辑书写写在 test.c 文件中,游戏所需要的声明书写在 game.h 文件中,实现游戏的函数写在 game.c 文件中


一、test.c 文件

1. 游戏界面的书写

如果小伙伴不喜欢该游戏界面,可以自行更改菜单 mnue 函数

#include "game.h";		//需要用到 game.h 中的声明

void mnue()
{
	printf("******************\n");
	printf("***   1.play   ***\n");
	printf("***   0.exit   ***\n");
	printf("******************\n");
}

为了让玩家有更好的游戏体验,游戏玩了一局之后我们可以选择再来一局,或者退出游戏,这里采用 do while 循环

int main()
{
	int input = 0;
	
	//设置随机数的生成起点
	//需要的头文件是 stdlib.h 和 time.h
	//在布置雷的函数实现中说明
	srand((unsigned int)time(NULL));	
	
	do
	{
		//打印菜单
		mnue();
		printf("请输入:>");
		scanf("%d", &input);
		system("cls");			//将屏幕已经打印的内容清理掉,
								//需要头文件是 stdlib.h

		//用户选择
		switch (input)
		{
		case 1:			//用户选择 1 开始游戏,进入到 game 函数中
			printf("欢迎来到扫雷游戏\n");
			game();
			break;
		case 0:			//用户选择 0 退出游戏
			printf("退出游戏\n");
			break;
		default :		//如果选择的不是 1 则需要重新输入
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);	//选择 0 条件不成立会退出循环,已结束游戏
						//选择 1 或输入了其他数字,判断为真
						//进入循环,打印菜单,让用户重新选择

	return 0;
}

运行截图

2. 游戏逻辑的书写

用户选择 1 之后来到 game 函数

void game_mnue()
{
	printf("************************************\n");
	printf("***   1.easy   2.advn   3.diff   ***\n");
	printf("************************************\n");
}

//代码中字母是全大写的字符放在 game.h 中声明
void game()
{
	int input = 0;
	int dgree_row = 0;			//接收难度的行数
	int dgree_col = 0;			//接收难度的列数
	int mine_num = 0;			//接收雷的数目

	//数组长度定义为困难模式的行和列,已便可以容纳3种模式
	char mine[DIFF_ROW][DIFF_COL];		//放地雷
	char show[DIFF_ROW][DIFF_COL];		//排地雷

	do
	{
		//选择游戏模式
		game_mnue();
		printf("请输入难度:>");
		scanf("%d", &input);
		system("cls");

		switch (input)
		{
		case 1:		//简单模式
			dgree_row = EASY_ROW;
			dgree_col = EASY_COL;
			mine_num = EASY_MINE;
			break;
		case 2:		//中等模式
			dgree_row = ADVN_ROW;
			dgree_col = ADVN_COL;
			mine_num = ADVN_MINE;
			break;
		case 3:		//困难模式
			dgree_row = DIFF_ROW;
			dgree_col = DIFF_COL;
			mine_num = DIFF_MINE;
			break;
		default :	//如果输入错误,重新输入
			printf("输入错误请重新输入:\n");
			break;
		}
	//用户输入不在选择范围,可以重新选择
	} while (input < 1 || input > 3);

	//初始化地雷数组和排地雷数组
	//第四个参数表示初始化的内容
	init(mine, dgree_row, dgree_col, '0');
	init(show, dgree_row, dgree_col, '*');

	//检测程序是否符合预期(实际代码中不需要运行)
	//print(mine, dgree_row, dgree_col);
	//print(show, dgree_row, dgree_col);

	//布置雷,第四个参数表示雷的数目
	set_mine(mine, dgree_row, dgree_col, mine_num);

	//检测程序是否符合预期(实际代码中不需要运行)
	//print(mine, dgree_row, dgree_col);

	//排雷并判断是否胜利
	find_mine(mine, show, dgree_row, dgree_col, mine_num);

	Sleep(5000);	//让输出结果停留 5s 需要的头文件是 Windows.h
	system("cls");
}

运行截图

运行截图


二、game.h 文件

用来存放函数的声明,需要的头文件,以及不同模式下棋盘的大小和雷的数目

该文件中不同模式下的数据是参照扫雷游戏的数据设计的,但是行和列会多上二,具体原因参考本篇目录中 三、 game.c 文件中 1. 设计思路

该文件可以更改对应模式下的棋盘大小和雷的数目应当注意行数和列数都应当设置为想要更改的数目 + 2

#define _CRT_SECURE_NO_WARNINGS 1	//在VS编译器用 scanf 等函数
									//需要定义该符号

//需要包含的头文件
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<time.h>

//这里的行数和列数设计为比实际运行出来的行和列多二
//简单模式
#define EASY_ROW 11
#define EASY_COL 11
#define EASY_MINE 10

//中等模式
#define ADVN_ROW 18
#define ADVN_COL 18
#define ADVN_MINE 40

//困难模式
#define DIFF_ROW 18
#define DIFF_COL 32
#define DIFF_MINE 99

//初始化,第四个参数表示初始化的内容
void init(char arr[DIFF_ROW][DIFF_COL], 
		  int row, int col, char piece);

//打印
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col);

//布置雷,第四个参数表示雷的数目
void set_mine(char mine[DIFF_ROW][DIFF_COL], 
			  int row, int col,int mine_num);

//排雷并判断是否胜利
void find_mine(char mine[DIFF_ROW][DIFF_COL], 
			   char show[DIFF_ROW][DIFF_COL], 
			   int row, int col, int mine_num);

三、game.c 文件

1. 设计思路

(1)为什么用字符 ‘1’ 表示雷

扫雷数组设计

(2)为什么需要采用两个数组

在这里插入图片描述

2. 初始化

根据设计思路:mine 数组初始化为全 ‘0’ ,布置雷时改为字符 ‘1’ 即可
show 数组为了达到视觉效果初始化为全 ‘*’,排查雷时改为相应的信息

#include "game.h";		//需要用到 game.h 中的声明

void init(char arr[DIFF_ROW][DIFF_COL], 
		  int row, int col, char piece)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			arr[i][j] = piece;
		}
	}
}

3. 打印

根据设计思路:只需打印第二行到倒数第二行,以及第二列到倒数第二列

注意:数组下标是从零开始的,在该函数中数组下标的范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2

为了让玩家能容易的找到需要排查的坐标,这里打印行号和列号

void print(char arr[DIFF_ROW][DIFF_COL], int row, int col)
{
	int i = 0;
	int j = 0;

	//打印列号,为了对齐,这里从 0 开始打印
	for (i = 0; i < col - 1; i++)
	{
		printf("%d", i);

		//为了对齐,10 以下的数字打印两个空格		
		//10 及以上的数字会占用两个位置,只打印一个空格
		i >= 10 ? printf(" ") : printf("  ");
	}
	printf("\n");

	//打印数组内容
	for (i = 1; i < row - 1; i++)
	{
		//打印行号
		printf("%d", i);
		
		//按照列号的对齐方式
		i >= 10 ? printf(" ") : printf("  ");	
		for (j = 1; j < col - 1; j++)
		{
			printf("%c  ", arr[i][j]);
		}
		printf("\n");
	}
}

4. 布置雷

根据设计思路:雷只能放在第二行到倒数第二行,以及第二列到倒数第二列

注意:数组下标是从零开始的,该函数雷位置的下标范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2

这里介绍生成随机数的方法
想要知道细节可以点击,rand函数srand函数time函数
这些是C语言提供的库函数

  • rand 函数可以随机产生 0 - 32767 之间的整数
  • 但是在使用 rand 函数之前需要调用 srand 函数设置随机数的生成起点
  • 为了让 rand 函数每次运行时产生的随机数不一样,需要给 srand 函数的参数设置为1 个可以变化的值
  • 由于时间戳是不断变化的,可以将时间戳作为 srand 的参数
  • time 函数可以获取当前的时间
void set_mine(char mine[DIFF_ROW][DIFF_COL], 
			  int row, int col, int mine_num)
{
	//循环次数不能确定
	while (mine_num)
	{
		//生成雷的坐标
		//rand 函数返回的数字是 0 - 32767 之间的数字
		//模上 (row - 2),则会产生 0 - (row - 3) 的数字
		//加 1 产生 0 - (row - 2) 的数字
		int x = rand() % (row - 2) + 1;
		int y = rand() % (col - 2) + 1;	

		//判断雷坐标是否重复
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';	//'1' 表示该位置是雷
			mine_num--; 		//成功布置雷后,雷的数目才减 1
		}
	}
}

5. 判断游戏是否结束以及存储排查雷的信息

根据设计思路:玩家输入的坐标范围只能放在第二行到倒数第二行,以及第二列到倒数第二列

注意:数组下标是从零开始的,玩家排查坐标的范围:
行的范围: 1 - row - 2
列的范围: 1 - col - 2

(1)判断游戏胜利还是失败

void find_mine(char mine[DIFF_ROW][DIFF_COL],
			   char show[DIFF_ROW][DIFF_COL],
			   int row, int col, int mine_num)
{
	int x = 0;
	int y = 0;
	int win = 0;

	//找出所有非雷的位置,游戏胜利
	//非雷位置的数目 = 位置总数目 - 雷的数目
	while (win < (row - 2) * (col - 2) - mine_num)
	{
		//打印 show 数组让玩家可以根据已经排查的坐标
		//(坐标的内容为周围雷的信息)来判断其他坐标是否是雷
		print(show, row, col);
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		system("cls");

		//坐标合法
		if (x >= 1 && x <= row - 2 && y >= 1 && y <= col - 2)
		{
			//坐标是否被排查过,(show 数组中存放排雷的信息)
			if (show[x][y] == '*')
			{
				//是否踩雷,(mine 数组中存放雷的信息)
				if (mine[x][y] == '1')
				{
					printf("很遗憾,游戏失败,你被炸死了\n");
					break;
				}
				else
				{
					//conut_mine 函数计算该坐标周围的雷数并存储到 show 数组中
					//如果该坐标周围雷数为 0,则完成相应的展开
					count_mine(mine, show, row, col, x, y);
					
					//count 是在 count_mine 函数前定义的全局静态变量
					//用来记录当前坐标在 count_mine 函数执行完成后排查的位置个数
					win += count;	
					
					//避免影响下一次 count_mine 函数执行后排查的位置个数
					count = 0;
				}
			}
			else
			{
				printf("输入错误,请重新输入:>\n");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:>\n");
		}

	}

	//判断游戏是否胜利
	if (win == (row - 2) * (col - 2) - mine_num)
		printf("恭喜你,游戏胜利,排雷成功\n");

	print(show, row, col);
}

(2)计算并存储排查雷的信息

当计算该坐标的雷数为 0 时,需要判断该坐标周围的 8 个坐标的信息,如果存在周围的坐标的雷数也为 0 时,就继续判断该周围坐标的周围的 8 个坐标,以此类推,其中如果坐标已被判断过,便直接退出当前所在的函数

由于计算一个坐标周围的雷数的方法是一样的,只是坐标不同,这里采用函数的递归来实现

递归判断

static int count = 0;		//用来记录排查的位置个数

//计算并存储雷的个数,如果该坐标的雷数为 0 则递归判断周围 8 个坐标的雷数
static void count_mine(char mine[DIFF_ROW][DIFF_COL],
					   char show[DIFF_ROW][DIFF_COL], 
					   int row, int col, int x, int y)
{
	//在递归过程中坐标可能会越界
	//坐标也可能已经判断过了,此时直接退出当前所在的函数
	if (x < 1 || x > row - 2 || 
		y < 1 || y > col - 2 || 
		show[x][y] != '*')
		return;	//函数返回类型为空时,return 后面不可以带数值

	count++;	//坐标合法,并且未被排查过,
				//则进入函数一次,加一,表示排查坐标的的数目

	//计算当前坐标周围的雷数,mine 数组中 '0' 表示非雷,'1' 表示雷
	//将周围 8 个坐标相加后减去 8 个'0'
	int mine_num = mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
		mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
		mine[x][y - 1] + mine[x][y + 1] - 8 * '0';

	//坐标周围雷数是 0 时,递归判断周围 8 个坐标周围雷的信息
	if (mine_num == 0)
	{
		//将该坐标设置位空格,表示该坐标周围雷数为 0 
		//并且可以表示该坐标已被排查过
		show[x][y] = ' ';
		count_mine(mine, show, row, col, x - 1, y - 1);
		count_mine(mine, show, row, col, x - 1, y);
		count_mine(mine, show, row, col, x - 1, y + 1);
		count_mine(mine, show, row, col, x, y - 1);
		count_mine(mine, show, row, col, x, y + 1);
		count_mine(mine, show, row, col, x + 1, y - 1);
		count_mine(mine, show, row, col, x + 1, y);
		count_mine(mine, show, row, col, x + 1, y + 1);
	}
	else
	{
		//坐标周围雷数不是 0 时,将计算周围雷数加上 '0' 
		//便可设置为雷数对应的字符
		show[x][y] = mine_num + '0';
	}
}

四、源码

test.c 文件

#include "game.h";		//需要用到 game.h 中的声明

void mnue()
{
	printf("******************\n");
	printf("***   1.play   ***\n");
	printf("***   0.exit   ***\n");
	printf("******************\n");
}

void game_mnue()
{
	printf("************************************\n");
	printf("***   1.easy   2.advn   3.diff   ***\n");
	printf("************************************\n");
}

void game()
{
	int input = 0;
	int dgree_row = 0;			//接收难度的行数
	int dgree_col = 0;			//接收难度的列数
	int mine_num = 0;			//接收雷的数目

	//定义为最大难度的数组长度,便可以容纳3种模式
	char mine[DIFF_ROW][DIFF_COL];		//放地雷
	char show[DIFF_ROW][DIFF_COL];		//排地雷

	do
	{
		//选择游戏模式
		game_mnue();
		printf("请输入难度:>");
		scanf("%d", &input);
		system("cls");

		switch (input)
		{
		case 1:		//简单模式
			dgree_row = EASY_ROW;
			dgree_col = EASY_COL;
			mine_num = EASY_MINE;
			break;
		case 2:		//中等模式
			dgree_row = ADVN_ROW;
			dgree_col = ADVN_COL;
			mine_num = ADVN_MINE;
			break;
		case 3:		//困难模式
			dgree_row = DIFF_ROW;
			dgree_col = DIFF_COL;
			mine_num = DIFF_MINE;
			break;
		default :	//如果输入错误,重新输入
			printf("输入错误请重新输入:\n");
			break;
		}
	} while (input < 1 || input > 3);

	//初始化地雷数组和排地雷数组
	init(mine, dgree_row, dgree_col, '0');
	init(show, dgree_row, dgree_col, '*');

	//检测程序是否符合预期
	//print(mine, dgree_row, dgree_col);
	//print(show, dgree_row, dgree_col);

	//布置雷
	set_mine(mine, dgree_row, dgree_col, mine_num);

	//检测程序是否符合预期
	//print(mine, dgree_row, dgree_col);

	//排雷并判断是否胜利
	find_mine(mine, show, dgree_row, dgree_col, mine_num);

	Sleep(5000);
	system("cls");
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));		//设置随机数的生成起点

	do
	{
		//打印菜单
		mnue();
		printf("请输入:>");
		scanf("%d", &input);
		system("cls");

		switch (input)
		{
		case 1:
			printf("欢迎来到扫雷游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default :
			printf("输入错误,请重新输入:>\n");
			break;
		}
	} while (input);
	
	return 0;
}

game.h 文件

#define _CRT_SECURE_NO_WARNINGS 1

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

//简单模式
#define EASY_ROW 11
#define EASY_COL 11
#define EASY_MINE 10

//中等模式
#define ADVN_ROW 18
#define ADVN_COL 18
#define ADVN_MINE 40

//困难模式
#define DIFF_ROW 18
#define DIFF_COL 32
#define DIFF_MINE 99

//初始化
void init(char arr[DIFF_ROW][DIFF_COL], int row, int col, char piece);

//打印
void print(char arr[DIFF_ROW][DIFF_COL], int row, int col);

//布置雷
void set_mine(char mine[DIFF_ROW][DIFF_COL], int row, int col,int mine_num);

//排雷并判断是否胜利
void find_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col, int mine_num);

game.c 文件

#include "game.h"

void init(char arr[DIFF_ROW][DIFF_COL], int row, int col, char piece)
{
	int i = 0;
	int j = 0;

	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			arr[i][j] = piece;
		}
	}
}

void print(char arr[DIFF_ROW][DIFF_COL], int row, int col)
{
	int i = 0;
	int j = 0;

	//打印列号
	for (i = 0; i < col - 1; i++)
	{
		printf("%d", i);

		i >= 10 ? printf(" ") : printf("  ");	//为了对齐
	}
	printf("\n");

	for (i = 1; i < row - 1; i++)
	{
		//打印行号
		printf("%d", i);
		i >= 10 ? printf(" ") : printf("  ");	//为了对齐

		for (j = 1; j < col - 1; j++)
		{
			printf("%c  ", arr[i][j]);
		}
		printf("\n");
	}
}

void set_mine(char mine[DIFF_ROW][DIFF_COL], int row, int col,int mine_num)
{
	while (mine_num)
	{	
			//生成雷的坐标(打印时,只打印行:1 - (row - 2) ,列:1 - (col - 2) 之间的内容)
			int x = rand() % (row - 2) + 1;	//产生 1 - (row - 2) 之间的数字
			int y = rand() % (col - 2) + 1;	//产生 1 - (col - 2) 之间的数字

			//判断雷坐标是否重复
			if (mine[x][y] == '0')
			{
				mine[x][y] = '1';
				mine_num--;
			}
	}
}

//排查雷的个数,如果该坐标的雷数为 0 则递归判断周围的雷数
static int count = 0;		//用来记录排查的位置个数

static void count_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col, int x, int y)
{
	//在递归过程中可能会越界,并且有些已经判断过了
	if (x < 1 || x > row - 2 || y < 1 || y > col - 2 || show[x][y] != '*')
		return;

	count++;

	int mine_num = mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] +
		mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
		mine[x][y - 1] + mine[x][y + 1] - 8 * '0';

	if (mine_num == 0)
	{
		show[x][y] = ' ';
		count_mine(mine, show, row, col, x - 1, y - 1);
		count_mine(mine, show, row, col, x - 1, y);
		count_mine(mine, show, row, col, x - 1, y + 1);
		count_mine(mine, show, row, col, x, y - 1);
		count_mine(mine, show, row, col, x, y + 1);
		count_mine(mine, show, row, col, x + 1, y - 1);
		count_mine(mine, show, row, col, x + 1, y);
		count_mine(mine, show, row, col, x + 1, y + 1);
	}
	else
	{
		show[x][y] = mine_num + '0';
	}
}

void find_mine(char mine[DIFF_ROW][DIFF_COL], char show[DIFF_ROW][DIFF_COL], int row, int col ,int mine_num)
{
	int x = 0;
	int y = 0;
	int win = 0;

	//找出所有非雷的位置,游戏胜利
	while (win < (row - 2) * (col - 2) - mine_num)
	{
		print(show, row, col);
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		system("cls");

		//坐标合法
		if (x >= 1 && x <= row - 2 && y >= 1 && y <= col - 2)
		{
			//坐标是否被占用
			if (show[x][y] == '*')
			{
				//是否踩雷
				if (mine[x][y] == '1')
				{
					printf("很遗憾,游戏失败,你被炸死了\n");
					break;
				}
				else
				{
					//判断坐标周围的雷数
					count_mine(mine, show, row, col, x, y);
					win += count;
					count = 0;
				}
			}
			else
			{
				printf("输入错误,请重新输入:>\n");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:>\n");
		}

	}

	if(win == (row - 2) * (col - 2) - mine_num)
		printf("恭喜你,游戏胜利,排雷成功\n");

	print(show, row, col);
}

本代码并未开发标记雷的功能,有兴趣的读者可以自行开发

代码亲测有效,如有建议,可以评论留言哦

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值