C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)

目录

一、扫雷游戏介绍及其主要功能点

(1)扫雷游戏介绍

(2)扫雷游戏需要实现的功能点(主要)

二、游戏功能模块的实现

(1)雷盘大小及地雷数量的定义

(2)雷盘的初始化及打印(显示)

 ①雷盘的初始化函数

 ②雷盘的打印(显示)函数

(3)布置地雷的函数

(4)排查格子的函数

①统计格子周围地雷数量的函数

②递归排查信息为‘0’的格子周围格子信息的函数

(5)标记地雷的函数

(6)删除标记的函数

三、游戏运行的截图(主要)

(1)第一次排查坐标

(2)执行一次操作后,用户继续选择

(3)过程中可标记(P)或取消标记地雷

(4)游戏失败,打印mine数组显示地雷位置

(5)游戏成功,打印mine数组显示地雷位置

四、游戏实现代码

(1)头文件game.h

(2)源文件game.c

(3)源文件test.c


一、扫雷游戏介绍及其主要功能点

(1)扫雷游戏介绍

  扫雷游戏游戏大家并不陌生,通常由一个方形区域组成,被分成若干个小方格。(如下图)

    C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第1张图片                           C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第2张图片

  每个方格里都可能有雷,玩家需要不断排查,根据方格提供的数字信息(表示该方格周围8个方格地雷的数量),排查完所有安全的格子,获得游戏胜利。若排查到地雷,则游戏失败。

(2)扫雷游戏需要实现的功能点(主要)

  ①为了游戏体验,第一次排查格子时一定不是雷

  ②玩家排查一个格子时,若其无雷,则自动排查其周围格子的信息,依次循环

  ③玩家确定某格子有雷后,可以标记该格子

  ④玩家可以取消之前做的标记


二、游戏功能模块的实现

(1)雷盘大小及地雷数量的定义

  雷盘的实现主要采用二维数组的方法

#define ROW 9  //二维数组行数
#define COL 9   //二维数组列数
#define MINE 10 //地雷数量 

#define ROWS ROW+2      //选择格子后是检测格子周围八个格子地雷的数量,当检测边缘的格子时,
#define COLS COL+2      //附近会没有其他格子,因此拓宽二维数组的大小,方便后续检测格子周围地雷数量

(2)雷盘的初始化及打印(显示)

 ①雷盘的初始化函数

  设置两个雷盘,一个mine数组保存地雷位置信息,一个show数组用于展示游戏过程信息给玩家

void InitBoard(char board[COLS][ROWS], int cols, int rows, char c) {
	for (int i = 0; i < cols; i++) {
		for (int j = 0; j < rows; j++) {
			board[i][j] = c;
		}
	}
}

 ②雷盘的打印(显示)函数

  游戏过程中不断打印show数组展示给玩家游戏信息

void PrintBoard(char board[COLS][ROWS], int col, int row) {
	printf("---------------------------------\n\n");
	printf("    ");                                                //行坐标格式对齐
	for (int i = 1; i < col + 1; i++) {                            //最上方一行打印列坐标0-9
		printf("%d   ", i);
	}
	printf("\n\n");
	for (int i = 1; i <= col; i++) {
		printf("%d   ", i);                                      //每行打印元素前打印行坐标
		for (int j = 1; j <= row; j++) {
			printf("%c   ", board[i][j]);
		}
		printf("\n\n");
	}
	printf("---------------------------------\n\n\n");


}

(3)布置地雷的函数

  采取随机值的办法布置地雷,在函数中加入一次排查操作,使得设置的随机值不为排查的坐标,以排除该地方是雷的可能性

void SetMine(char board[COLS][ROWS], char show[COLS][ROWS], int col, int row) {
	int count = MINE;      //计数器,用于放置设定的地雷数量
	printf("请输入要排查的坐标(依次输入)\n\n");
	while (1)               //死循环防止输入坐标不合法
	{
		int x, y;
		scanf("%d%d", &x, &y);  //输入第一次要排查的坐标,需要将其设置为没有地雷
		int m, n;                //用于存放随机的行列坐标以放置地雷
		if (x > 0 && x < row + 1 && y>0 && y < col + 1)      //坐标合法
		{
			while (count)
			{
				m = rand() % row + 1;  //行坐标范围1-9
				n = rand() % col + 1;  //列坐标范围1-9

				if (board[m][n] == '0' && (m != x || n != y)) {  //没有放过地雷的地方才能放雷,同时第一个选择的格子不能有雷
					board[m][n] = '1';                       //放置地雷
					count--;                                 //计数器减少
				}
			}
			show[x][y] = get_mine_count(board, x, y) + '0';//show数组中传入第一次排查格子周围雷的数量,因为数组是char类型,所以需要加上'0'转为字符型的量(ASCII码)
			ExpendMine(board, show, x, y);
			PrintBoard(show, col, row);                     //打印show数组
			break;                                           //跳出while(1)死循环
		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");               //执行死循环重新输入坐标
		}
	}
}

(4)排查格子的函数

  输入坐标排查该格子的信息,利用get_mine_count()函数统计该格子周围雷的信息,利用ExpendMine()函数递归遍历信息为‘0’的格子周围格子的信息

int FindMine(char mine[COLS][ROWS], char show[COLS][ROWS], int col, int row) {
	PrintBoard(show, col, row);               //打印show数组

	while (1)
	{
		//1.输入排查的坐标
		int m, n;
		printf("请输入要排查的坐标(依次输入)\n\n");
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] != '*' && show[m][n] != 'P') {             //检查是否输入了重复坐标
				PrintBoard(show, col, row);                          //打印show数组
				printf("ERROR:这个地方排查过了,请重新输入\n\n");
			}
			else
			{
				//2.检查坐标处是否是雷
				if (mine[m][n] == '1')                        //有雷
				{
					printf("\n");
					PrintBoard(mine, col, row);               //打印给玩家具体的地雷位置
					printf("你踩到地雷了,真菜啊!\n\n");
					printf("上方为地雷分布情况,1是地雷,0是安全格子\n\n");
					return 0;                                    //游戏结束,直接返回0
				}
				else
				{
					ExpendMine(mine, show, m, n);//递归探寻	
		         	PrintBoard(show, col, row);               //打印show数组
					return 1;                                 //成功排查一次,返回1
				}
			}
		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");               //执行死循环重新输入坐标

		}
	}
}

①统计格子周围地雷数量的函数

int get_mine_count(char mine[COLS][ROWS], int m, int n) {
	return mine[m - 1][n] +
		mine[m - 1][n - 1] +
		mine[m - 1][n + 1] +
		mine[m][n - 1] +
		mine[m][n + 1] +
		mine[m + 1][n - 1] +
		mine[m + 1][n] +
		mine[m + 1][n + 1] - 8 * '0';//排查坐标附近8个格子雷的数量,但数组是字符型数组,所以每个还要减去'0'(ASCII码)
}

②递归排查信息为‘0’的格子周围格子信息的函数

void ExpendMine(char mine[COLS][ROWS], char show[COLS][ROWS], int m, int n) {
	if (m > 0 && m < ROW + 1 && n>0 && n < COL + 1)
	{
		int count = get_mine_count(mine, m, n);   //不是雷,利用get_mine_count函数统计周围雷的数量
		if (count == 0)
		{
			show[m][n] = '0' + count;

			for (int i = m - 1; i <= m + 1; i++) {

				for (int j = n - 1; j <= n + 1; j++) {//双重循环访问为'0'的格子周围8个格子的信息
					if (show[i][j] == '*')              //递归出口,防止死循环
					{
						ExpendMine(mine, show, i, j);//递归探寻

					}
				}
			}
		}
		else
		{
			show[m][n] = '0' + count;//show数组中传入格子周围雷的数量,因为数组是char类型,所以需要加上'0'转为字符型的量(ASCII码)
		}

	}
}

(5)标记地雷的函数

  玩家经过排查后,可以确定某格子有地雷,则可输入合法的坐标标记地雷

void SignMine(char show[COLS][ROWS], char mine[COLS][ROWS], int col, int row) {   //标记地雷的函数
	PrintBoard(show, col, row);               //打印show数组

	int m, n;
	printf("请输入标记为地雷的坐标\n\n");
	while (1)
	{
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] == '*' || show[m][n] == 'P') {           //只能标记未排查的格子或者重复标记
				show[m][n] = 'P';                                   //标记有地雷的地方为'P'
				PrintBoard(show, col, row);
				break;
			}
			else {
				printf("ERROR:此坐标已排查无雷,请输入正确坐标\n\n");

			}

		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");
		}
	}


}

(6)删除标记的函数

  若玩家游玩过程中发现之前的标记是错误的,则可输入合法的坐标,删除做过的标记

void DeleteMine(char show[COLS][ROWS], char mine[COLS][ROWS], int col, int row) {
	PrintBoard(show, col, row);               //打印show数组
	int flag = 0;
	for (int i = 1; i <= row; i++) {
		for (int j = 1; j <= col; j++) {             //遍历数组,如果没有已经被标记的坐标,则中止函数执行
			if (show[i][j] == 'P')
			{
				flag = 1;                            //有被标记的坐标,改变flag的值
				break;
			}
			if (flag == 1) {
				break;                                //跳出外层循环
			}
		}
	}
	if (flag == 0) {
		printf("请重新选择,没有已被标记的坐标\n\n");
		return;
	}
	int m, n;
	printf("请输入要删除地雷标记的坐标\n\n");
	while (1)
	{
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] == 'P') {                                //只能删除已经标记过的坐标
				show[m][n] = '*';                                   //将标记完的坐标重新置为*
				PrintBoard(show, col, row);
				break;
			}
			else {
				printf("ERROR:此坐标未被标记,请输入正确坐标\n\n");

			}

		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");
		}
	}
}


三、游戏运行的截图(主要)

(1)第一次排查坐标

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第3张图片

(2)执行一次操作后,用户继续选择

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第4张图片

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第5张图片

(3)过程中可标记(P)或取消标记地雷

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第6张图片

(4)游戏失败,打印mine数组显示地雷位置

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第7张图片

(5)游戏成功,打印mine数组显示地雷位置

C语言实现扫雷小游戏(代码含注解、包含递归遍历及标记)_第8张图片


四、游戏实现代码

(1)头文件game.h

  存放符号和函数的定义声明:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include
#include

#define ROW 3  //二维数组行数
#define COL 3   //二维数组列数
#define MINE 2 //地雷数量 

#define ROWS ROW+2      //选择格子后是检测格子周围八个格子地雷的数量,当检测边缘的格子时,
#define COLS COL+2      //附近会没有其他格子,因此拓宽二维数组的大小,方便后续检测格子周围地雷数量

void InitBoard(char board[COLS][ROWS],int cols,int rows,char c);//初始化二维数组函数

void PrintBoard(char board[COLS][ROWS], int col, int row);//打印二维数组函数

void SetMine(char board[COLS][ROWS], char show[COLS][ROWS], int col, int row);//设置地雷函数

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

int get_mine_count(char mine[COLS][ROWS], int m, int n);//统计周围地雷数量函数

void SignMine(char show[COLS][ROWS],char mine[COLS][ROWS],int col,int row);//用户标记地雷函数

void DeleteMine(char show[COLS][ROWS], char mine[COLS][ROWS], int col, int row);//用户删除标记函数

void ExpendMine(char mine[COLS][ROWS], char show[COLS][ROWS], int m, int n);//用于递归访问周围 格子没雷的格子周围格子的情况

(2)源文件game.c

  定义游戏执行过程中的各种功能函数:

#include "game.h"
void InitBoard(char board[COLS][ROWS], int cols, int rows, char c) {
	for (int i = 0; i < cols; i++) {
		for (int j = 0; j < rows; j++) {
			board[i][j] = c;
		}
	}
}

void PrintBoard(char board[COLS][ROWS], int col, int row) {
	printf("---------------------------------\n\n");
	printf("    ");                                                //行坐标格式对齐
	for (int i = 1; i < col + 1; i++) {                            //最上方一行打印列坐标0-9
		printf("%d   ", i);
	}
	printf("\n\n");
	for (int i = 1; i <= col; i++) {
		printf("%d   ", i);                                      //每行打印元素前打印行坐标
		for (int j = 1; j <= row; j++) {
			printf("%c   ", board[i][j]);
		}
		printf("\n\n");
	}
	printf("---------------------------------\n\n\n");


}
void SetMine(char board[COLS][ROWS], char show[COLS][ROWS], int col, int row) {
	int count = MINE;      //计数器,用于放置设定的地雷数量
	printf("请输入要排查的坐标(依次输入)\n\n");
	while (1)               //死循环防止输入坐标不合法
	{
		int x, y;
		scanf("%d%d", &x, &y);  //输入第一次要排查的坐标,需要将其设置为没有地雷
		int m, n;                //用于存放随机的行列坐标以放置地雷
		if (x > 0 && x < row + 1 && y>0 && y < col + 1)      //坐标合法
		{
			while (count)
			{
				m = rand() % row + 1;  //行坐标范围1-9
				n = rand() % col + 1;  //列坐标范围1-9

				if (board[m][n] == '0' && (m != x || n != y)) {  //没有放过地雷的地方才能放雷,同时第一个选择的格子不能有雷
					board[m][n] = '1';                       //放置地雷
					count--;                                 //计数器减少
				}
			}
			show[x][y] = get_mine_count(board, x, y) + '0';//show数组中传入第一次排查格子周围雷的数量,因为数组是char类型,所以需要加上'0'转为字符型的量(ASCII码)
			ExpendMine(board, show, x, y);
			PrintBoard(show, col, row);                     //打印show数组
			break;                                           //跳出while(1)死循环
		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");               //执行死循环重新输入坐标
		}
	}



}

int get_mine_count(char mine[COLS][ROWS], int m, int n) {
	return mine[m - 1][n] +
		mine[m - 1][n - 1] +
		mine[m - 1][n + 1] +
		mine[m][n - 1] +
		mine[m][n + 1] +
		mine[m + 1][n - 1] +
		mine[m + 1][n] +
		mine[m + 1][n + 1] - 8 * '0';//排查坐标附近8个格子雷的数量,但数组是字符型数组,所以每个还要减去'0'(ASCII码)
}
void ExpendMine(char mine[COLS][ROWS], char show[COLS][ROWS], int m, int n) {
	if (m > 0 && m < ROW + 1 && n>0 && n < COL + 1)
	{
		int count = get_mine_count(mine, m, n);   //不是雷,利用get_mine_count函数统计周围雷的数量
		if (count == 0)
		{
			show[m][n] = '0' + count;

			for (int i = m - 1; i <= m + 1; i++) {

				for (int j = n - 1; j <= n + 1; j++) {//双重循环访问为'0'的格子周围8个格子的信息
					if (show[i][j] == '*')              //递归出口,防止死循环
					{
						ExpendMine(mine, show, i, j);//递归探寻

					}
				}
			}
		}
		else
		{
			show[m][n] = '0' + count;//show数组中传入格子周围雷的数量,因为数组是char类型,所以需要加上'0'转为字符型的量(ASCII码)
		}

	}
}
int FindMine(char mine[COLS][ROWS], char show[COLS][ROWS], int col, int row) {
	PrintBoard(show, col, row);               //打印show数组

	while (1)
	{
		//1.输入排查的坐标
		int m, n;
		printf("请输入要排查的坐标(依次输入)\n\n");
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] != '*' && show[m][n] != 'P') {             //检查是否输入了重复坐标
				PrintBoard(show, col, row);                          //打印show数组
				printf("ERROR:这个地方排查过了,请重新输入\n\n");

			}
			else
			{
				//2.检查坐标处是否是雷
				if (mine[m][n] == '1')                        //有雷
				{
					printf("\n");
					PrintBoard(mine, col, row);               //打印给玩家具体的地雷位置
					printf("你踩到地雷了,真菜啊!\n\n");
					printf("上方为地雷分布情况,1是地雷,0是安全格子\n\n");
					return 0;                                    //游戏结束,直接返回0
				}
				else
				{
					ExpendMine(mine, show, m, n);//递归探寻	
		         	PrintBoard(show, col, row);               //打印show数组
					return 1;                                 //成功排查一次,返回1
				}
			}


		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");               //执行死循环重新输入坐标

		}
	}


}

void SignMine(char show[COLS][ROWS], char mine[COLS][ROWS], int col, int row) {   //标记地雷的函数
	PrintBoard(show, col, row);               //打印show数组

	int m, n;
	printf("请输入标记为地雷的坐标\n\n");
	while (1)
	{
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] == '*' || show[m][n] == 'P') {           //只能标记未排查的格子或者重复标记
				show[m][n] = 'P';                                   //标记有地雷的地方为'P'
				PrintBoard(show, col, row);
				break;
			}
			else {
				printf("ERROR:此坐标已排查无雷,请输入正确坐标\n\n");

			}

		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");
		}
	}


}


void DeleteMine(char show[COLS][ROWS], char mine[COLS][ROWS], int col, int row) {
	PrintBoard(show, col, row);               //打印show数组
	int flag = 0;
	for (int i = 1; i <= row; i++) {
		for (int j = 1; j <= col; j++) {             //遍历数组,如果没有已经被标记的坐标,则中止函数执行
			if (show[i][j] == 'P')
			{
				flag = 1;                            //有被标记的坐标,改变flag的值
				break;
			}
			if (flag == 1) {
				break;                                //跳出外层循环
			}
		}
	}
	if (flag == 0) {
		printf("请重新选择,没有已被标记的坐标\n\n");
		return;
	}
	int m, n;
	printf("请输入要删除地雷标记的坐标\n\n");
	while (1)
	{
		scanf("%d%d", &m, &n);
		if (m > 0 && m < row + 1 && n>0 && n < col + 1)
		{
			if (show[m][n] == 'P') {                                //只能删除已经标记过的坐标
				show[m][n] = '*';                                   //将标记完的坐标重新置为*
				PrintBoard(show, col, row);
				break;
			}
			else {
				printf("ERROR:此坐标未被标记,请输入正确坐标\n\n");

			}

		}
		else
		{
			printf("ERROR:请输入正确的坐标\n\n");
		}
	}
}

(3)源文件test.c

  游戏执行函数主体:

#include "game.h"
void menu(){                      //打印菜单函数
	printf("*******************************\n");
	printf("********    1.play     ********\n");
	printf("********    0.exit     ********\n");
	printf("*******************************\n");

}
void menu2() {                    //打印用户选择操作的菜单函数
	printf("请输入操作选择(1-3)\n\n");
	printf("1-----排查地雷\n\n");
	printf("2-----标记地雷\n\n");
	printf("3-----取消标记\n\n");
	


}

void game() {                    //游戏执行函数
	char mine[ROWS][COLS];       //定义一个存放地雷信息的二维数组(‘1’代表地雷,‘0’代表无雷,采用char类型是为了两个数组统一类型方便后续操作)
	char show[ROWS][COLS];       //定义一个展示格子周围地雷数量的二维数组用于游戏执行(采用char类型是因为要显示*字符代表格子未被排查)
	//1.初始化+打印二维数组
	InitBoard(mine, ROWS, COLS, '0');    //初始化地雷信息的二维数组,一开始全为无雷状态
	InitBoard(show, ROWS, COLS, '*');    //初始化展示地雷数量的二维数组,一开始全为*
	//PrintBoard(mine, ROW, COL);          //打印地雷信息的二维数组
	PrintBoard(show, ROW, COL);          //打印地雷数量的二维数组
	
	//2.设置地雷
	
    SetMine(mine,show, ROW, COL);             //设置地雷
	//PrintBoard(mine, ROW, COL);
	//PrintBoard(show, ROW, COL);
	
	//3.排查地雷
	//FindMine(mine, show, ROW, COL);   //排查地雷
	int choice;
	while (1)
	{
		int win = 0;                        //计数器,用于判断游戏是否成功
		int ret = -1;                    //用于接收查找函数的返回值,赋初值不能是0或1,因为0代表游戏失败,1代表排查成功
		menu2();                        //打印用户选择菜单
		scanf("%d", &choice);
		switch (choice)
		{
		case 1:
			ret=FindMine(mine, show, ROW, COL);        //排查地雷

			break;
		case 2:
			SignMine(show, mine, ROW, COL);        //标记地雷
			break;
		case 3:
			DeleteMine(show,mine,ROW,COL);         //取消标记
			break;
		default:
			printf("ERROR:请输入1-3\n");
			PrintBoard(show, COL, ROW);               //打印show数组

			break;
		}
		if (ret == 0) {
			break;                                  //再次跳出while循环
		}
		else if(ret==1){                             //成功排查一次
			for (int i = 1; i <= ROW; i++) {
				for (int j = 1; j <= COL; j++) {      //遍历show数组
					if (show[i][j] == '*' || show[i][j] == 'P') {            //统计show数组中*和P的数量
						win += 1;
					}
				}
		}
			if (win==MINE)                                             //如果show数组中*和P的数量正好等于雷数,则游戏成功
			{
				printf("游戏成功,你有点东西的!\n\n");    
				PrintBoard(show, COL, ROW);               //打印show数组
				break;                                    //再次跳出while循环
			}
		}


	}


}

int main() {
	int input = 0;
	srand((unsigned int)time(NULL));               //随机数生成器
	do                                             //游戏执行循环体
	{
		menu();                                    //打印菜单
		printf("请选择>\n\n");
		scanf("%d", &input);
		printf("\n");
		switch (input)
		{
		case 0:
			printf("退出游戏成功!\n");
			break;
		case 1:
			game();//执行游戏
			break;
		default:
			printf("请输入0或1\n\n");
			break;
		}

	} while (input);             //当input=0时退出循环
	return 0;
}

以上为C语言实现扫雷小游戏的全部内容,感谢您花费宝贵的时间阅读本文章!

你可能感兴趣的:(C语言相关技术贴,c语言,开发语言,数据结构,算法,学习)