c语言扫雷游戏代码学习

c语言扫雷游戏代码学习

目录

    • **c语言扫雷游戏代码学习**
    • 1.基本思路
      • 将程序拆分为三个部分:
    • 2.各个功能实现
      • 首先定义宏,以便后面参数的修改.
      • 写出主函数,确定程序的大体框架.
      • 接下来按照步骤完善game()函数.
      • 菜单功能
      • 棋盘初始化
      • 棋盘的打印
      • 布置地雷
      • 扫雷部分
      • 显示附近的地雷数目
      • 检查非雷格子数目
      • 彩色打印函数
      • 呃,未完成
      • 头文件,函数,变量的声明
      • 头文件引用
    • 3.完整代码
      • game.h
      • test.c
      • game.c
      • 早期游戏通关图片
    • 结束语
    • 参考

运行环境,visual studio 2022社区版

1.基本思路

经典的扫雷界面
c语言扫雷游戏代码学习_第1张图片
这里用c语言模拟实现的扫雷界面
c语言扫雷游戏代码学习_第2张图片

扫雷游戏由一个有X,Y轴的平面格子构成,所以需要用到二位数组.

1.利用两个二位数组分别来布置雷(玩家不可见)char mine[ROWS][COLS],展示扫雷的界面(玩家可见)char show[ROWS][COLS].
2.雷的布置:利用随机数生成布置雷,需要用到时间戳,rand()函数.
3.爆炸式边界展开:当扫雷点四周没有雷的时候,扫雷边界会向四周继续展开, 直到边界出现雷. 利用函数的递归实现该功能.
4.不同颜色显示:利用彩色打印实现不同雷数量的显示,以及疑似有雷位置的标记.
5.标记功能,创建一个一维数组存储标记点的坐标.

将程序拆分为三个部分:

test.c(主程序,游戏的大体框架)
game.c(自定义函数,游戏的各个具体功能实现)
game.h(头文件,宏,函数声明)

2.各个功能实现

首先定义宏,以便后面参数的修改.

定义的棋盘比展示的棋盘多了两行两列,用于解决查询棋盘边缘格子四周的地雷数量,这样边缘格子也存在上下左右的8个相邻格子.扩宽的格子不设置地雷.
这里可以修改棋盘大小,地雷数量,可以调整难度.

//宏定义
//棋盘数量
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//地雷数量
#define easy_count 10

写出主函数,确定程序的大体框架.

主函数放在test,c

int main()
{
	//输入值
	int input = 0;
	//雷布置
	char mine[ROWS][COLS] = { 0 };
	//扫雷展示界面
	char show[ROWS][COLS] = { 0 };
	//随机种子
	srand((unsigned int) time(NULL));
	

	do
	{
		//菜单
		menu();
		//选择
		printf("请输入选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("开始游戏\n");
			start = time(NULL);
			//游戏
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入.\n");
			break;
		}
	} while (input);

		return 0;
}

接下来按照步骤完善game()函数.

这里只是一个大体框架,其中的每一个功能函数后面在实现.就比如标记坐标的存储,一开始写的扫雷代码没有这个,后面有了思路在加上的.

//头文件
#include "game.h"

//游戏
void game()
{
	//存储标记坐标
	int mark[60][2] = { 0 };
	int mark_ = 0;//序号
	//棋盘初始化
	InitBoard(mine, ROWS, COLS, '0');//数组名,数组首元素的地址
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	DisplayBoard(show, ROW, COL,0,0,0);
	//布置地雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL,0,0,0);//测试代码用,作弊
	//扫雷
	FindMine(mine, ROW, COL);

}

菜单功能

不用细说,只是普通的printf()函数.

//菜单
void menu()
{
	printf("**************************\n");
	printf("********   扫雷    *******\n");
	printf("********  1.play   *******\n");
	printf("********  0.exit   *******\n");
	printf("**************************\n");

}

棋盘初始化

因为上文说到,需要用到两个二维数组,其中一个用来存储地雷坐标,一个用来展示界面.
所以这里需要两种不同的初始化(0和’*'),用set参数区分.

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

棋盘的打印

打印棋盘,因为上文定义的棋盘arr[ROWS][COLS]比展示的棋盘多了两行两列,所以需要参数int row, int col来确定打印边界,不打印边缘多出来的两行.这里打印棋盘的函数看起来有点长,主要是因为需要打印标记点的坐标,还有不同地雷数目的格子用不同颜色打印,醒目.

函数中调用的彩色打印函数传递的参数是字符串首元素地址,void printf_green(char* s),
为了保证参数的传递和打印格式统一,这里用一个临时数组char tmp[4] = { arr[i][j],32,32,‘\0’ };32为空格的ASCII码值.

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("----------扫雷游戏------------\n");
	for (j = 0; j <= col; j++)//打印列号
	{
		printf("%-2d ", j);//打印的棋盘,不改变原有棋盘,所以这里可以为 整形 ,不用 字符型
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%-2d ", i);//打印行号
		for (j = 1; j <= col; j++)
		{
			int jump = 0;//用于跳出多重 for循环 
			//打印标记坐标
			for (int k = 0; k < mark_; k++)
			{
				if ((i == mark[k][0]) && (j == mark[k][1]) && (arr[i][j] == '*'))
				{
					printf_red("*  ");
					jump = 1;
					continue;
				}
			}

			if (jump == 1)
			{
				jump = 0;//还原
				continue;
			}
			//不同地雷数目的格子用不同颜色打印
			if (arr[i][j] == '1')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };//用于与printf_green参数一致.32为空格的ASCII码值,其实也可以用' ',不过懒得换了
				printf_green(tmp);
				continue;
			}
			if (arr[i][j] == '2')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_blue(tmp);
				continue;
			}
			if (arr[i][j] == '3')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_yellow(tmp);
				continue;
			}
			if (arr[i][j] > '3')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_red(tmp);
				continue;
			}
			//除了以上特殊打印,剩下的正常打印
			printf("%c  ", arr[i][j]);
		}
		printf("\n");
	}
	printf("----------扫雷游戏------------\n");

}

布置地雷

利用随机数生成布置雷,需要用到时间戳,rand()函数.随机种子的初始化在main()函数部分. srand((unsigned int) time(NULL));

//布置地雷
//debug: board[][COLS],这里不能是 COL ,否则不完整 
void SetMine(char board[ROWS][COLS], int row, int col) 
{
	//雷坐标
	int x = 0;
	int y = 0;
	//雷数量
	int count = easy_count;
	while (count)
	{
		//随机数范围, 1 ~ row
		x = rand() % row + 1;
		y = rand() % col + 1;
		//判断坐标合法性
		if (board[x][y] == '0')//随机数范围包含了 x >= 1 && x <= row && y >= 1 && y <= col && 
		{
			board[x][y] = '1';
			count--;
		}
	}

}

扫雷部分

该部分主要实现4个功能:
1.扫雷坐标 2.标记坐标 3.取消上次标记 4.取消某点标记
用switch函数分别对应这4个功能.
1.扫雷: 输入扫雷坐标,先检测坐标的合法性,合法时,将坐标与存储地雷的棋盘board[x][y]作比较,并用CountMine(board, x, y, ROW, COL)函数显示附近地雷的数量.
检查非雷格子数目
count = CheckMine(show, ROW, COL);
通关的标准就是把所有没有地雷的格子都找到.失败这是踩到了地雷.
这里偷懒了, 失败的时候直接用goto语句跳转到 lable处,没有换别的方法.
2.标记坐标: 将输入坐标存入数组,mark_表示序号
mark[mark_][0] = x;
mark[mark_][1] = y;
mark_++;
打印棋盘的函数会检测mark[ ][ ]数组中存储的坐标,如果匹配,就用printf_red("* ");来表示标记的位置.
3.取消上次标记 : 将坐标存入mark[ ][ ]数组表示标记,反之,清除mark[ ][ ]数组里的元素表示删除标记.
mark[mark_][0] = 0;
mark[mark_][1] = 0;
mark_–;
4.取消某点标记: 方法比较简单,就是将输入的坐标依次与mark[ ][ ]数组中存储的坐标相比较,如果有,则清除对应元素.

//扫雷
void FindMine(char board[ROWS][COLS], int row, int col)//board[ROWS][COLS] 应该相当于指针变量
{
	//非雷格子数目
	int all_count = ROW * COL - easy_count;
	int count = 1;
	while (count < all_count)	//如果初始只有 1 个非雷格子,程序漏洞,通关
	{
		int x = 0;
		int y = 0;
		int z = 0;
		//清空缓存区无用输入
		clear();
		printf("请选择输入:> 1.扫雷坐标  2.标记坐标  3.取消上次标记  4.取消某点标记\n");
		scanf("%d", &z);
		switch(z)
		{
		case 1:
			//清空缓存区无用输入
			clear();
			printf("输入扫雷坐标:>\n");
			scanf("%d %d", &x, &y);
			//输入坐标合法性检测
			if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
			{
				//有雷,被炸死,游戏结束
				if (board[x][y] == '1')
				{
					printf_red("很遗憾,你被炸死了,任务失败.\n");
					goto lable;
					break;
				}
				//无雷,显示附近的雷,游戏继续
				if (board[x][y] == '0')
				{
					//显示附近的雷
					CountMine(board, x, y, ROW, COL);//雷的数量

					DisplayBoard(show, ROW, COL);//打印界面
					//检查非雷格子数目
					count = CheckMine(show, ROW, COL);
				}
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;
		
		case 2:
			printf("输入标记坐标:>\n");
			scanf("%d %d", &x, &y);
			//输入坐标合法性检测
			if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
			{
				//存入标记坐标
				mark[mark_][0] = x;
				mark[mark_][1] = y;
				mark_++;
				DisplayBoard(show, ROW, COL);//打印界面
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;

		case 3:
			if (mark_ <= 0)
			{
				printf("已经没有标记\n");
				break;
			}
			printf("取消上一次标记:>\n");
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
			DisplayBoard(show, ROW, COL);//打印界面
			break;

		case 4:
			//定点清除标记
			printf("输入需清除的标记坐标:>\n");
			int x0 = 0;
			int y0 = 0;
			scanf("%d %d", &x0, &y0);
			//输入坐标合法性检测
			if (x0 >= 1 && x0 <= row && y0 >= 1 && y0 <= col && show[x0][y0] == '*')
			{
				int i = 0;
				for (i = 0; i < mark_; i++)
				{
					if ((mark[i][0] == x0) && (mark[i][1] == y0))
					{
						mark[i][0] = 0;
						mark[i][1] = 0;
						continue;
					}
				}
					DisplayBoard(show, ROW, COL);//打印界面
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;

		default:
			printf("坐标错误,请重新输入.\n");
		}
	
	}
	if (count == all_count)
	{

		end = time(NULL);//结束时间
		unsigned int time = (unsigned int)difftime(end,start);//计算耗时 /秒
		printf_green("恭喜你,游戏通关!(~ ̄▽ ̄)~\n");
		printf("\033[0m\033[1;32m你总共用时: %d 秒\033[0m\n",time);
		//清除存储的标记坐标
		while (mark_)
		{
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
		}

	}
lable:
	if (count < all_count)
	{
		end = time(NULL);//结束时间
		unsigned int time = (unsigned int)difftime(end, start);//计算耗时 /秒
		printf("\033[0m\033[1;31m你耗费了: %d 秒\033[0m\n", time);
		//清除存储的标记坐标
		while (mark_)
		{
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
		}
	}

}

显示附近的地雷数目

这里主要实现的功能有:
1.检测坐标点四周格子的地雷数量,并将地雷数量存入展示界面的数组show[x][y].
2.如果被检查格子四周地雷数目为0,则由该格子想四周展开,检测相邻格子地雷数量,直到遇到四周地雷数量不为0的格子停下.该功能在游戏中表现为检查一个格子展开一片的情景.
这里用函数的递归实现.

//显示附近的雷
void CountMine(char board[ROWS][COLS], int x, int y,int row,int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{	//&& (i !=0 || j !=0)可省略,因为中心坐标无雷才能进行这一步
			if (board[x+i][y+j] == '1' && (i !=0 || j !=0) )//&& (i !=0 || j !=0)作用:i,j不同时为 0
			{
				count++;
			}
		}
	}
	//存入(x,y)坐标周围雷数量
	show[x][y] = count + '0';	//原有值 '*' ,不是 '0'
	//一个雷也没有,爆炸式展开
	if (count == 0)
	{
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{	
				//注意:不往回路爆炸展开,防止栈溢出  && show[x + i][y + j] == '*'  ,不等于 '*' ,则是展开过的地方
				//爆炸展开边界	&& x + i >= 1 && x + i <= row && y + j >= 1 && y + j <= col
				if (x + i >= 1 && x + i <= row && y + j >= 1 && y + j <= col && show[x + i][y + j] == '*')
					CountMine(board, x + i, y + j, row, col);
			}
		}
	}

}

检查非雷格子数目

//检查非雷格子数目
int CheckMine(char board[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 (board[i][j] != '*')
				count++;
		}

	}
	return count;
}

彩色打印函数

//彩色打印函数
//格式 \033[输入属性m    代码属性;前景色;后景色
// 0 重置 1 高亮
//前景色 31 红 32 绿 33 黄 34 蓝
//后景色 40 黑 41 红
void printf_red(char* s)
{
	printf("\033[0m\033[1;31m%s\033[0m", s);
}

void printf_green(char* s)
{
	printf("\033[0m\033[1;32m%s\033[0m", s);
}

void printf_yellow(char* s)
{
	printf("\033[0m\033[1;33m%s\033[0m", s);
}

void printf_blue(char* s)
{
	printf("\033[0m\033[1;34m%s\033[0m", s);
}

呃,未完成

函数中出现的clear()本来打算清空缓存区无用输入,不过现在没有思路,先留着

//清空缓存区无用输入
void clear()
{
	//暂时没有思路
}

头文件,函数,变量的声明

将上文中提到的函数放在game.h中声明

#pragma once

//宏定义
//棋盘数量
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//地雷数量
#define easy_count 10

//头文件
#include 
#include 
#include 

//变量声明
//雷布置
char mine[ROWS][COLS];
//扫雷展示界面
char show[ROWS][COLS];
//时间记录
time_t start, end;
//存储标记坐标
int mark[60][2];
int mark_;

//函数声明

//菜单
void menu();
//游戏
void game();
//棋盘初始化
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 board[ROWS][COLS],int row,int col);//保证棋盘大小的一致性
//扫雷
void FindMine(char board[ROWS][COLS],int row,int col);
//显示附近的雷
void CountMine(char board[ROWS][COLS], int x, int y, int row, int col);
//检查非雷格子数目
int CheckMine(char board[ROWS][COLS], int row, int col);
//彩色打印函数
void printf_red(char* s);
void printf_green(char* s);
void printf_yellow(char* s);
void printf_blue(char* s);
//清空缓存区无用输入
void clear();

头文件引用

上文将头文件集中放在game.h
所以在game.c和test.c中需要引用#include “game.h”
#define _CRT_SECURE_NO_WARNINGS是为了解决visual sutdio 2022中scanf()警告的问题.

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"

3.完整代码

game.h

#pragma once

//宏定义
//棋盘数量
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//地雷数量
#define easy_count 10

//头文件
#include 
#include 
#include 

//变量声明
//雷布置
char mine[ROWS][COLS];
//扫雷展示界面
char show[ROWS][COLS];
//时间记录
time_t start, end;
//存储标记坐标
int mark[60][2];
int mark_;

//函数声明

//菜单
void menu();
//游戏
void game();
//棋盘初始化
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 board[ROWS][COLS],int row,int col);//保证棋盘大小的一致性
//扫雷
void FindMine(char board[ROWS][COLS],int row,int col);
//显示附近的雷
void CountMine(char board[ROWS][COLS], int x, int y, int row, int col);
//检查非雷格子数目
int CheckMine(char board[ROWS][COLS], int row, int col);
//彩色打印函数
void printf_red(char* s);
void printf_green(char* s);
void printf_yellow(char* s);
void printf_blue(char* s);
//清空缓存区无用输入
void clear();

test.c

#define _CRT_SECURE_NO_WARNINGS
//数组应用,扫雷游戏2.0
//功能优化:
//			1.爆炸式边界展开
//			2.增加标记功能  (功能优化3.0)
//			3.操作步数记录  (为了更具有意义,改为时间记录)
//头文件
#include "game.h"

//游戏
void game()
{
	//存储标记坐标
	int mark[60][2] = { 0 };
	int mark_ = 0;//序号
	//棋盘初始化
	InitBoard(mine, ROWS, COLS, '0');//数组名,数组首元素的地址
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	DisplayBoard(show, ROW, COL,0,0,0);
	//布置地雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL,0,0,0);//测试代码用,作弊
	//扫雷
	FindMine(mine, ROW, COL);

}

int main()
{
	//输入值
	int input = 0;
	//雷布置
	char mine[ROWS][COLS] = { 0 };
	//扫雷展示界面
	char show[ROWS][COLS] = { 0 };
	//随机种子
	srand((unsigned int) time(NULL));
	

	do
	{
		//菜单
		menu();
		//选择
		printf("请输入选择\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("开始游戏\n");
			start = time(NULL);
			//游戏
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入.\n");
			break;
		}
	} while (input);



		return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"


//菜单
void menu()
{
	printf("**************************\n");
	printf("********   扫雷    *******\n");
	printf("********  1.play   *******\n");
	printf("********  0.exit   *******\n");
	printf("**************************\n");

}

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

//打印棋盘
void DisplayBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("----------扫雷游戏------------\n");
	for (j = 0; j <= col; j++)//打印列号
	{
		//printf("%c ", j + '0');
		printf("%-2d ", j);//打印的棋盘,不改变原有棋盘,所以这里可以为 整形 ,不用 字符型
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%-2d ", i);//打印行号
		for (j = 1; j <= col; j++)
		{
			int jump = 0;//用于跳出多重 for循环 
			//标记坐标
			for (int k = 0; k < mark_; k++)
			{
				if ((i == mark[k][0]) && (j == mark[k][1]) && (arr[i][j] == '*'))
				{
					printf_red("*  ");
					jump = 1;
					continue;
				}
			}

			if (jump == 1)
			{
				jump = 0;//还原
				continue;
			}
			//不同地雷数目的格子用不同颜色打印
			if (arr[i][j] == '1')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };//用于与printf_green参数一致.32为空格的ASCII码值,其实也可以用' ',不过懒得换了
				printf_green(tmp);
				continue;
			}
			if (arr[i][j] == '2')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_blue(tmp);
				continue;
			}
			if (arr[i][j] == '3')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_yellow(tmp);
				continue;
			}
			if (arr[i][j] > '3')
			{
				char tmp[4] = { arr[i][j],32,32,'\0' };
				printf_red(tmp);
				continue;
			}
			printf("%c  ", arr[i][j]);
		}
		//除了以上特殊打印,剩下的正常打印
		printf("\n");
	}
	printf("----------扫雷游戏------------\n");

}

//布置地雷
//debug: board[][COLS],这里不能是 COL ,否则不完整 
void SetMine(char board[ROWS][COLS], int row, int col) 
{
	//雷坐标
	int x = 0;
	int y = 0;
	//雷数量
	int count = easy_count;
	while (count)
	{
		//随机数范围, 1 ~ row
		x = rand() % row + 1;
		y = rand() % col + 1;
		//判断坐标合法性
		if (board[x][y] == '0')//随机数范围包含了 x >= 1 && x <= row && y >= 1 && y <= col && 
		{
			board[x][y] = '1';
			count--;
		}
	}

}

//扫雷
void FindMine(char board[ROWS][COLS], int row, int col)//board[ROWS][COLS] 应该相当于指针变量
{
	//非雷格子数目
	int all_count = ROW * COL - easy_count;
	int count = 1;
	while (count < all_count)	//如果初始只有 1 个非雷格子,程序漏洞,通关
	{
		int x = 0;
		int y = 0;
		int z = 0;
		//清空缓存区无用输入
		clear();
		printf("请选择输入:> 1.扫雷坐标  2.标记坐标  3.取消上次标记  4.取消某点标记\n");
		scanf("%d", &z);
		switch(z)
		{
		case 1:
			//清空缓存区无用输入
			clear();
			printf("输入扫雷坐标:>\n");
			scanf("%d %d", &x, &y);
			//输入坐标合法性检测
			if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
			{
				//有雷,被炸死,游戏结束
				if (board[x][y] == '1')
				{
					printf_red("很遗憾,你被炸死了,任务失败.\n");
					goto lable;
					break;
				}
				//无雷,显示附近的雷,游戏继续
				if (board[x][y] == '0')
				{
					//显示附近的雷
					CountMine(board, x, y, ROW, COL);//雷的数量

					DisplayBoard(show, ROW, COL);//打印界面
					//检查非雷格子数目
					count = CheckMine(show, ROW, COL);
				}
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;
		
		case 2:
			printf("输入标记坐标:>\n");
			scanf("%d %d", &x, &y);
			//输入坐标合法性检测
			if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*')
			{
				//存入标记坐标
				mark[mark_][0] = x;
				mark[mark_][1] = y;
				mark_++;
				DisplayBoard(show, ROW, COL);//打印界面
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;

		case 3:
			if (mark_ <= 0)
			{
				printf("已经没有标记\n");
				break;
			}
			printf("取消上一次标记:>\n");
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
			DisplayBoard(show, ROW, COL);//打印界面
			break;

		case 4:
			//定点清除标记
			printf("输入需清除的标记坐标:>\n");
			int x0 = 0;
			int y0 = 0;
			scanf("%d %d", &x0, &y0);
			//输入坐标合法性检测
			if (x0 >= 1 && x0 <= row && y0 >= 1 && y0 <= col && show[x0][y0] == '*')
			{
				int i = 0;
				for (i = 0; i < mark_; i++)
				{
					if ((mark[i][0] == x0) && (mark[i][1] == y0))
					{
						mark[i][0] = 0;
						mark[i][1] = 0;
						continue;
					}
				}
					DisplayBoard(show, ROW, COL);//打印界面
			}
			else
			{
				printf("坐标错误,请重新输入.\n");
			}
			break;

		default:
			printf("坐标错误,请重新输入.\n");
		}
	
	}
	if (count == all_count)
	{

		end = time(NULL);//结束时间
		unsigned int time = (unsigned int)difftime(end,start);//计算耗时 /秒
		printf_green("恭喜你,游戏通关!(~ ̄▽ ̄)~\n");
		printf("\033[0m\033[1;32m你总共用时: %d 秒\033[0m\n",time);
		//清除存储的标记坐标
		while (mark_)
		{
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
		}

	}
lable:
	if (count < all_count)
	{
		end = time(NULL);//结束时间
		unsigned int time = (unsigned int)difftime(end, start);//计算耗时 /秒
		printf("\033[0m\033[1;31m你耗费了: %d 秒\033[0m\n", time);
		//清除存储的标记坐标
		while (mark_)
		{
			mark[mark_][0] = 0;
			mark[mark_][1] = 0;
			mark_--;
		}
	}

}

//显示附近的雷
void CountMine(char board[ROWS][COLS], int x, int y,int row,int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{	//&& (i !=0 || j !=0)可省略,因为中心坐标无雷才能进行这一步
			if (board[x+i][y+j] == '1' && (i !=0 || j !=0) )//&& (i !=0 || j !=0)作用:i,j不同时为 0
			{
				count++;
			}
		}
	}
	//存入(x,y)坐标周围雷数量
	//show[x][y] = 1+ count + '0';//char类型,不是 int ,按照ASCII码转换	+1证明求过该坐标
	show[x][y] = count + '0';	//不用 +1 证明,因为原有值 '*' ,不是 '0'
	//一个雷也没有,爆炸式展开
	if (count == 0)
	{
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{	
				//注意:不往回路爆炸展开,防止栈溢出  && show[x + i][y + j] == '*'  ,不等于 '*' ,则是展开过的地方
				//爆炸展开边界	&& x + i >= 1 && x + i <= row && y + j >= 1 && y + j <= col
				if (x + i >= 1 && x + i <= row && y + j >= 1 && y + j <= col && show[x + i][y + j] == '*')
					CountMine(board, x + i, y + j, row, col);
			}
		}
	}

	//show[x][y] =count + '0' -1;	// -1还原坐标

}


//检查非雷格子数目
int CheckMine(char board[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 (board[i][j] != '*')
				count++;
		}

	}
	return count;
}


//彩色打印函数
//格式 \033[输入属性m    代码属性;前景色;后景色
// 0 重置 1 高亮
//前景色 31 红 32 绿 33 黄 34 蓝
//后景色 40 黑 41 红
void printf_red(char* s)
{
	printf("\033[0m\033[1;31m%s\033[0m", s);
}

void printf_green(char* s)
{
	printf("\033[0m\033[1;32m%s\033[0m", s);
}

void printf_yellow(char* s)
{
	printf("\033[0m\033[1;33m%s\033[0m", s);
}

void printf_blue(char* s)
{
	printf("\033[0m\033[1;34m%s\033[0m", s);
}


//清空缓存区无用输入
void clear()
{
	//暂时没有思路
}

早期游戏通关图片

c语言扫雷游戏代码学习_第3张图片

结束语

初学者,存在不足,如果你有好的想法,请在评论区留言,谢谢指教.

参考

B站C语言教程 扫雷
printf彩色输出
c语言如何跳出多层循环描述

你可能感兴趣的:(C语言学习笔记,c语言,游戏,学习)