室友说你怎么才学到数组,然后我用C语言做了个扫雷(递归展开+难度选择+优化打印)

扫雷

创作不易,还请观众老爷耐心看完儿! 

目录

扫雷

1.扫雷框架

2.初始化棋盘

3.打印棋盘

格式1:

格式2:

4.布置雷

5.排查雷

5.1排查雷的功能

5.2递归展开功能bool_mine

5.3计算该位置的雷的个数

6.扫雷的全部代码

7.作者试玩环节



1.扫雷框架

2.初始化棋盘

        这是用户看到的棋盘,9*9大小,需要自己去排查雷

为了程序员更好地去控制扫雷,我们需要两个数组

  1. 程序员看的雷区数组 -- 里面存放着雷的位置 -- 11*11大小
  2. 用户看的展示数组 -- 全是*号,需要用户去排查 -- 11*11大小

使这两个数组一样大的原因:是为了使我们后面设计的接口函数更加兼容,即使展示的数组本质是11*11大小,但是我们打印的时候打印9*9就可以了

        为了可以提高代码可维护性和兼容性,我们将数组的行数和列数,使用来替换


#define ROWS 11
#define COLS 11


#define ROW 9
#define COL 9
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;
		}
	}
}

        我们将如果初始化的内容以字符的形式作为参数传进去,上述代码就可以很好地解决了兼容性,这一个函数可以解决雷区棋盘和展示用户的棋盘的初始化


3.打印棋盘

         打印棋盘大家可以充分发挥自己的艺术细胞,根据自己的喜好去设置棋盘的打印格式。下面博主提供两种。

格式1:

void print_mineline(int row)//打印分割线
{
	for (int i = 0; i < row; ++i)
	{
		if (i == row / 2)
		{
			printf("扫雷");
		}
		printf("==");
	}
	printf("n");
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	print_mineline(row);//先打印一行分割线
	//打印列号
	for (int j = 0; j <= col; ++j)
	{
		if (0 == j)//将列对齐
		{
			printf("  ");
			continue;
		}
		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");
	}
	print_mineline(row);//最后再打印一行分割线
	printf("n");
}

效果:

格式2:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	//打印列号
	for (int j = 0; j <= col; ++j)
	{
		if (j == 0)
		{
			printf("    ");
			continue;
		}
		printf(" %d  ", j);
	}
	printf("nn");

	for (int i = 1; i <= row; ++i)
	{
		//打印列号
		//1.打印第一部分
		printf(" %d  ", i);

		for (int j = 1; j <= col; ++j)
		{
			printf (" %c ", board[i][j]);
			if (j <= col -1)
				printf("|");
		}
		printf("n");
		//2.打印第二部分
		if (i <= row - 1)
		{
			printf("    ");
			for (int j = 1; j <= col; ++j)
			{
				printf("---");
				if (j <= col - 1)
					printf("+");
			}
		}
		printf("n");
	}

}

效果:

         但是格式2的缺点是如果棋盘是9*9以上大小,那么存在一些对齐问题,当然厉害的同学可以改善一下。这是提供的两个思路


4.布置雷

根据雷的个数n,随机布置n个雷,雷的个数可以根据用户的选择来定,例如

1.简单 -- 10个雷

2.普通 -- 20个雷

3.困难 -- 40个雷

4.疯狂 -- 80个雷

当然以上的布置雷都是限制在了9*9的棋盘中,大家也可以根据难度设计棋盘的大小,尺寸

为了代码的可维护性以及可读性,我们将不同难度下的雷的个数也使用宏来替换,以及可以使用枚举来帮助我们实现难度选择

//雷的个数
#define COUNT_EASY 10

#define COUNT_ORD 20

#define COUNT_DIF 40

#define COUNT_FRE 80


//难度等级
enum degree
{
	EASY = 1,//简单
	ORD,//普通
	DIF,//困难
	FRE//疯狂
};
void menu_degree()//难度选择菜单 -- 跟我们的枚举常量值一致
{
	printf("==========1.简单==========n");
	printf("==========2.普通==========n");
	printf("==========3.困难==========n");
	printf("==========4.疯狂==========n");
}


int SetMine(char board[ROWS][COLS], int row, int col)
{	
	int count = 0;//雷的个数

	system("cls");//我们游戏开始部分肯定会要菜单,所以这里使用一个清屏功能
	int input = 0;

	do
	{
		menu_degree();

		printf("请选择扫雷难度n");
		scanf("%d", &input);
		switch (input)
		{
		case EASY:
			count = COUNT_EASY;
			break;
		case ORD:
			count = COUNT_ORD;
			break;
		case DIF:
			count = COUNT_DIF;
			break;
		case FRE:
			count = COUNT_FRE;
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (input != EASY && input != ORD &&
		input != DIF && input != FRE);

	int _count = count;//保存count的值

	while (count)
	{
		//随机生成十个雷,放在中间的9*9中
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//判断该位置是否已经布置了雷
		if (board[x][y] == '0')//没布置
		{
			board[x][y] = '1';
			--count;
		}
	}

	return _count;//这里我们将布置雷的个数返回了,之后要用
}

解释:布置雷只需要在9*9中布置,所以咱们传参传的是row,col而不是rows,cols


5.排查雷

游戏的核心部分:

1.玩家输入需要排查雷的坐标

2.判断是否有效

3.根据雷区判断,如果是雷,游戏结束

4.如果不是雷,则在显示区棋盘中赋上雷的个数

5.如果雷的个数是0,还是考虑将周围8个位置进行递归展开

5.1排查雷的功能

在排查雷的函数中,mine -- 雷区棋盘,show -- 展示区棋盘,是需要配合使用的。例如判断一个位置是否是雷,我们要去雷区棋盘找,而即使修正展示区的位置内容,我们需要去show棋盘中修改,并且即使打印出来给用户观看。

判断输赢:

  •  如果输入的坐标位置是雷,则输掉游戏
  •  如果不是雷的位置都被排查完了,那么玩家赢得游戏胜利
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
{
    //雷的个数通过SetMine的返回值可以得到,然后我们通过参数的形式,传给FineMine
    //因为我们的版本雷的个数不固定

	int chance = row*col - count;//记录不是雷的个数

	while (chance)
	{
		system("cls");

		DisplayBoard(show, row, col);//展示show棋盘

		printf("请输入需要排查雷的坐标n");
		int x = 0, y = 0;
		scanf("%d %d", &x, &y);
		//检查坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//不是雷--显示雷的个数
			if (mine[x][y] != '1')//没点到雷
			{
                //递归展开该位置
				boom_mine(mine, show, x, y, &chance);//将该位置周围的展开,传坐标
                //展开雷chance是会减少的,使用址传递,在函数里面修改chance
			}
            //如何是雷--爆炸
			else
			{
				printf("很遗憾,你被炸死了n");
				DisplayBoard(mine, row, col);
				break;
			}
		}
		else
		{
			printf("坐标不合法n");
		}
	}
	//判断是否赢了-- 将所有不是雷的地方都排查出来了
	if (chance == 0)
	{
		printf("排雷成功,游戏胜利n");
	}
	Sleep(2000);//停顿两秒看下结果
}

5.2递归展开功能bool_mine

查出该位置周围8个位置的雷的个数

  1. 0个以上的雷  --  将这个show棋盘对应的位置赋值为该数字字符
  2. 0个雷 -- 先这个show棋盘对应的位置赋值为该数字字符,再对该位置周围8个位置进行递归展开

防止出现死递归,我们思考一下递归限制条件:

  1. 如果该位置越界,那么不做任何处理
  2. 如果该位置已经被排查过了就不做任何处理

        如果你的代码出现了错误,调式一看发现有个stack overflow这个报错,原因之一就是你写出死递归了

void boom_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pchance)
{
	//递归限制条件
	//1.如果越界了,直接返回
	if (x == 0 || x == ROW+1 || y == 0 || y == COL+1)
	{
		return;
	}
	//2.如果该位置已经展开,直接返回
	if (show[x][y] != '*')
	{
		return;
	}
	//3.剩下的就是需要展开的情况
	int count = get_mine(mine, x, y);//计算该位置的雷的个数

	if (count)//如果雷的个数不为0,直接展开
	{
		show[x][y] = count + '0';
		--(*pchance);//不是雷的个数也减一
		return;//这里需要返回,不然无限递归下去了
	}
	else//如果雷的个数是0
	{
		//对周围八个位置进行展开操作
		show[x][y] = count + '0';
		--(*pchance);
		boom_mine(mine, show, x - 1, y - 1, pchance);
		boom_mine(mine, show, x - 1, y, pchance);
		boom_mine(mine, show, x - 1, y + 1, pchance);
		boom_mine(mine, show, x, y - 1, pchance);
		boom_mine(mine, show, x, y + 1, pchance);
		boom_mine(mine, show, x + 1, y - 1, pchance);
		boom_mine(mine, show, x + 1, y, pchance);
		boom_mine(mine, show, x + 1, y + 1, pchance);
	}
}

5.3计算该位置的雷的个数

        这里就很好地体现了我们在雷区棋盘中放字符0字符1的好处。

        我们将    周围8个位置的字符加进来   -   8*字符0   =   雷的个数

int get_mine(char mine[ROWS][COLS], int x, int y)
{
	//周围八个位置的内容 - 8 * '0'  ==  周围雷的个数
	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');
}

 


6.扫雷的全部代码

        上述的操作是讲解扫雷的核心函数的实现,下面我们进行分块编程,并且设计一些菜单,将他们整合在一起。

game.h

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once

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

//操作棋盘的时候用的
#define ROWS 11
#define COLS 11

//用户看到的
#define ROW 9
#define COL 9

//雷的个数
#define COUNT_EASY 10

#define COUNT_ORD 20

#define COUNT_DIF 40

#define COUNT_FRE 80

enum degree
{
	EASY = 1,//简单
	ORD,//普通
	DIF,//困难
	FRE//疯狂
};


//初始化棋盘
void InitBoard(char board[ROWS][COLS],int rows,int cols,char set);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

//布置雷
int SetMine(char board[ROWS][COLS], int row, int col);

//排查类
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count);

game.c

        打印的两个方式都放在里面了 

#include"game.h"


//初始化棋盘
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 print_mineline(int row)
//{
//	for (int i = 0; i < row; ++i)
//	{
//		if (i == row / 2)
//		{
//			printf("扫雷");
//		}
//		printf("==");
//	}
//	printf("n");
//}

//void DisplayBoard(char board[ROWS][COLS], int row, int col)
//{
//	print_mineline(row);
//	//打印列号
//	for (int j = 0; j <= col; ++j)
//	{
//		if (0 == j)//将列对齐
//		{
//			printf("  ");
//			continue;
//		}
//		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");
//	}
//	print_mineline(row);
//	printf("n");
//}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	//打印列号
	for (int j = 0; j <= col; ++j)
	{
		if (j == 0)
		{
			printf("    ");
			continue;
		}
		printf(" %d  ", j);
	}
	printf("nn");

	for (int i = 1; i <= row; ++i)
	{
		//打印列号
		//1.打印第一部分
		printf(" %d  ", i);

		for (int j = 1; j <= col; ++j)
		{
			printf (" %c ", board[i][j]);
			if (j <= col -1)
				printf("|");
		}
		printf("n");
		//2.打印第二部分
		if (i <= row - 1)
		{
			printf("    ");
			for (int j = 1; j <= col; ++j)
			{
				printf("---");
				if (j <= col - 1)
					printf("+");
			}
		}
		printf("n");
	}

}

void menu_degree()
{
	printf("==========1.简单==========n");
	printf("==========2.普通==========n");
	printf("==========3.困难==========n");
	printf("==========4.疯狂==========n");
}

//布置雷

//mine中,是雷就布置1,不是雷就是0

//布置雷的函数返回值 -- 返回雷的个数

int SetMine(char board[ROWS][COLS], int row, int col)
{	
	int count = 0;//雷的个数

	system("cls");
	int input = 0;

	do
	{
		menu_degree();

		printf("请选择扫雷难度n");
		scanf("%d", &input);
		switch (input)
		{
		case EASY:
			count = COUNT_EASY;
			break;
		case ORD:
			count = COUNT_ORD;
			break;
		case DIF:
			count = COUNT_DIF;
			break;
		case FRE:
			count = COUNT_FRE;
			break;
		default:
			printf("输入错误,请重新输入");
			break;
		}
	} while (input != EASY && input != ORD &&
		input != DIF && input != FRE);

	int _count = count;//保存count的值

	while (count)
	{
		//随机生成十个雷,放在中间的9*9中
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//判断该位置是否已经布置了雷
		if (board[x][y] == '0')//没布置
		{
			board[x][y] = '1';
			--count;
		}
		//布置了
	}

	return _count;
}

//设计排查雷 -- 展开功能 -- 返回展开的雷的个数



//排查雷
int get_mine(char mine[ROWS][COLS], int x, int y)
{
	//周围八个位置的内容 - 8 * '0'  ==  周围雷的个数
	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');
}

void boom_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* pchance)
{
	//递归限制条件
	//1.如果越界了,直接返回
	if (x == 0 || x == ROW+1 || y == 0 || y == COL+1)
	{
		return;
	}
	//2.如果该位置已经展开,直接返回
	if (show[x][y] != '*')
	{
		return;
	}
	//3.剩下的就是需要展开的情况
	int count = get_mine(mine, x, y);//计算该位置的雷的个数

	if (count)//如果雷的个数不为0,直接展开
	{
		show[x][y] = count + '0';
		--(*pchance);//不是雷的个数也减一
		return;//这里需要返回,不然无限递归下去了
	}
	else//如果雷的个数是0
	{
		//对周围八个位置进行展开操作
		show[x][y] = count + '0';
		--(*pchance);
		boom_mine(mine, show, x - 1, y - 1, pchance);
		boom_mine(mine, show, x - 1, y, pchance);
		boom_mine(mine, show, x - 1, y + 1, pchance);
		boom_mine(mine, show, x, y - 1, pchance);
		boom_mine(mine, show, x, y + 1, pchance);
		boom_mine(mine, show, x + 1, y - 1, pchance);
		boom_mine(mine, show, x + 1, y, pchance);
		boom_mine(mine, show, x + 1, y + 1, pchance);
	}
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
{
	int chance = row*col - count;//记录不是雷的个数

	while (chance)
	{
		system("cls");

		DisplayBoard(mine, row, col);

		DisplayBoard(show, row, col);
		printf("请输入需要排查雷的坐标n");
		int x = 0, y = 0;
		scanf("%d %d", &x, &y);
		//检查坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//如何是雷--爆炸
			//不是雷--显示雷的个数
			if (mine[x][y] != '1')//没点到雷
			{
				boom_mine(mine, show, x, y, &chance);//将该位置周围的展开,传坐标
			}
			else
			{
				printf("很遗憾,你被炸死了n");
				DisplayBoard(mine, row, col);
				break;
			}
		}
		else
		{
			printf("坐标不合法n");
		}
	}
	//判断是否赢了-- 将所有不是雷的地方都排查出来了
	if (chance == 0)
	{
		printf("排雷成功,游戏胜利n");
	}
	Sleep(2000);
}

test.c

#include"game.h"

void game()//游戏函数
{
	srand((unsigned int)time(NULL));

	char mine[ROWS][COLS] = { 0 };//11*11的数组便于操作
	char show[ROWS][COLS] = { 0 };//show数组与mine数组尺寸类型一样,可以使函数更兼容

	//1.初始化棋盘
	InitBoard(mine, ROWS, COLS,'0');//将mine初始化为字符0
	InitBoard(show, ROWS, COLS, '*');//将show初始化为*

	//3.布置雷
	int count = SetMine(mine,ROW,COL);//布置雷是在mine中布置
	//是在9*9中布置
	//4.排查雷
	FindMine(mine, show, ROW, COL,count);
}

void menu()
{
	printf("=================================n");
	printf("======       1. play       ======n");
	printf("======       0. exit       ======n");
	printf("=================================n");
}

void test()//测试函数
{
	int input = 0;
	do
	{
		system("cls");
		menu();
		printf("请输入->:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏退出n");
			break;
		default:
			printf("错误输出n");
			Sleep(1000);
			break;
		}
	} while (input);
}

int main()
{
	test();
	return 0;
}

 


7.作者试玩环节

        打完一把还是需要点时间的,博主就不玩完了。        

 当然了,上述代码肯定还存在着很多不足的地方,如果大家有什么好的建议欢迎在评论区留言,我们下期见吧。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>