扫雷——C语言实现

news2024/11/15 18:06:35

扫雷

文章目录

  • 扫雷
    • 实现代码
    • 什么是扫雷
    • 基本功能实现
      • 显示选择菜单
      • 定义几个二维数组?
      • 确定数组大小
      • 初始化数组
      • 布置地雷
      • 打印展示数组
      • 排查地雷
        • 记录指定区域周围地雷的个数
        • 判断排雷成功
        • 排查地雷实现代码
      • 基本功能的实现代码和效果展示
    • 拓展功能
      • 简化游戏界面
      • 改变字体颜色
      • 实现爆炸展开效果
        • 实现效果
      • 插旗功能
        • 插旗
        • 取消所插旗子
        • 调整MineFind()函数
        • 实现效果
      • 拓展功能实现代码

实现代码

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

#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define MINENUMBER 10

void BoardInit(char board[][COLS], int row, int col, char set);
void BoardDisplay(char board[][COLS], int row, int col);
void MineSet(char board[][COLS], int row, int col);
int MineNumber(char board[][COLS], int x, int y);
void MineFind(char mine[][COLS], char show[][COLS], int row, int col);
bool MineFinish(char board[][COLS], int row, int col);
void Explode(char mine[][COLS], char show[][COLS], int x, int y);
void Flag_In(char show[][COLS]);
void Flag_Out(char show[][COLS]);

void meau()
{
	printf("****************\n");
	printf("*****1.Play*****\n");
	printf("*****0.Exit*****\n");
	printf("****************\n");
}

void game()
{
	//生成时间戳
	srand((unsigned int)time(NULL));

	//定义存放地雷的数组,和展示结果的数组
	char mineBoard[ROWS][COLS];
	char showBoard[ROWS][COLS];

	//对两个数组进行初始化
	BoardInit(mineBoard, ROWS, COLS, '0');
	BoardInit(showBoard, ROWS, COLS, '*');

	//设置地雷
	MineSet(mineBoard, ROW, COL);

	//BoardDisplay(mineBoard, ROW, COL);
	BoardDisplay(showBoard, ROW, COL);

	//排查地雷
	MineFind(mineBoard, showBoard, ROW, COL);
}

void BoardInit(char board[][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
		for (int j = 0; j < col; j++)
			board[i][j] = set;
}

void BoardDisplay(char board[][COLS], int row, int col)
{
	for (int i = 0; i <= col; i++)
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
		printf("%d ", i);
	}

	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
		printf("%d ", i);
		for (int j = 1; j <= col; j++)
		{
			SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | 16);
			if(board[i][j] == '$')
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6 | 16);
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | 16);
}

void MineSet(char board[][COLS], int row, int col)
{
	int count = 0;
	while (count < MINENUMBER)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count++;
		}
	}
}

void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x, y;
	char ch;
	while (1)
	{
		printf("请输入您要排查的坐标(用空格分隔):");
		scanf_s("%d %d", &x, &y);
		system("cls");
		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,排雷失败\n");
				BoardDisplay(mine, row, col);
				return;
			}
			else
			{
				Explode(mine, show, x, y);

				if (MineFinish(show, row,col))
				{
					printf("恭喜你,排雷成功\n");
					BoardDisplay(mine, row, col);
					return;
				}

				BoardDisplay(show, row, col);

				getchar();

				printf("是否需要插旗(Y/N):\n");
				if((ch = getchar()) == 'Y')
					Flag_In(show);

				getchar();

				printf("是否需要取消所插的旗子(Y/N):\n");
				if ((ch = getchar()) == 'Y')
					Flag_Out(show);
			}
		}
		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			printf("该位置已经被检查,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
			
		else
		{
			printf("坐标非法,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
	}
}

int MineNumber(char board[][COLS], int x, int y)
{
	return (board[x][y - 1] + board[x][y + 1]
		+ board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1]
		+ board[x + 1][y] + board[x + 1][y - 1] + board[x + 1][y + 1]
		- 8 * '0');
}

bool MineFinish(char board[][COLS], int row, int col)
{
	int count = 0;
	for(int i = 1; i <= row; i++)
		for (int j = 1; j <= col; j++)
		{
			if (board[i][j] != '*')
			{
				count++;
				if (count == row * col - MINENUMBER)
					return true;
			}
		}
	return false;
}

void Explode(char mine[][COLS], char show[][COLS], int x, int y)
{
	if (show[x][y] == ' ' || x < 1 || y < 1 || x > ROW || y > COL)
		return;
	if (MineNumber(mine, x, y) == 0)
		show[x][y] = ' ';
	else
	{
		show[x][y] = MineNumber(mine, x, y) + '0';
		return;
	}

	Explode(mine, show, x, y - 1);
	Explode(mine, show, x, y + 1);
	Explode(mine, show, x + 1, y + 1);
	Explode(mine, show, x + 1, y);
	Explode(mine, show, x + 1, y - 1);
	Explode(mine, show, x - 1, y + 1);
	Explode(mine, show, x - 1, y - 1);
	Explode(mine, show, x - 1, y);
}

void Flag_In(char show[][COLS])
{
	int row = 0, col = 0;
	printf("您想在哪个位置插入旗子:\n");
	printf("注:用空格分隔,输入0 0结束插旗\n");
	while(1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '*')
			printf("给位置不能插旗\n");
		else
		{
			system("cls");
			show[row][col] = '$';
			BoardDisplay(show, ROW, COL);
		}
	}
}

void Flag_Out(char show[][COLS])
{
	int row, col;
	printf("您想取消哪个位置的旗子:\n");
	printf("注:用空格分隔,输入0 0结束\n");
	while (1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '$')
			printf("该位置不是旗子\n");
		else
		{
			system("cls");
			show[row][col] = '*';
			BoardDisplay(show, ROW, COL);
		}
	}
}

int main()
{
	system("color 17");
	int input;
	meau();
	printf("请输入您的选择:");
	while (1)
	{
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏,感谢游玩\n");
			break;
		default :
			printf("输入错误,请重新输入:");
			break;
		}
		meau();
		printf("是否重新游玩:");
	}
	return 0;
}

什么是扫雷

  • 要用代码来实现扫雷这个小游戏,我们就需要先了解扫雷这个游戏的规则和有关操作。

  • 我们先来看一段扫雷游戏的动画:

    在这里插入图片描述

  • 游戏目标:在N*N的区域中找到M个随机布置的地雷

  • 若点击的区域不是雷,那么就会显示一个数字,这个数字代表着这块区域周围雷的个数

  • 若点击的区域是雷,那么就表示排雷失败,游戏结束

基本功能实现

显示选择菜单

  • 游戏的最开始,我们需要提醒用户如何进行游戏以及如何退出游戏

  • 我们可以用以下代码实现

    #include<stdio.h>
    
    void meau()
    {
    	printf("****************\n");
    	printf("*****1.Play*****\n");
    	printf("*****0.Exit*****\n");
    	printf("****************\n");
    }
    
    int main()
    {
    	int input;
    	meau();
    	printf("请输入您的选择:");
    	while (1)
    	{
    		scanf_s("%d", &input);
    		switch (input)
    		{
    		case 1:
    			game();	//实现扫雷的函数
    			break;
    		case 0:
    			printf("退出游戏,感谢游玩\n");
    			break;
    		default :
    			printf("输入错误,请重新输入:");
    			break;
    		}
    		meau();
    		printf("是否重新游玩:");
    	}
    	return 0;
    }
    

定义几个二维数组?

  • 假设游戏区域为大小为N*N的正方形区域

  • 可以明确的是,我们需要用一个二维数组来存放这N*N个位置的信息,那我们还需不需要定义其他数组呢?

  • 根据上面的动画我们可以看到,游玩游戏时,系统展示给我们的是一个空白的未知区域,而没有将这片区域存放的数据(即地雷的位置)展示给我们,因此我们还需要定义一个二维数组用来对用户展示其排查的结果。

确定数组大小

  • ,二维数组的大小为多少合适呢?可能有小伙伴认为,定义一个长宽都为N的二维数组不就行了吗?其实这不是最优解,我们先将这个问题放一边,先来看看我们是如何记录一块区域周围雷的个数的:

    • 假设我们要排查的坐标是(X,Y),那么它周围的区域就是这八个坐标:(X,Y-1), (X,Y+1), (X+1,Y), (X+1,Y+1), (X+1,Y-1), (X-1,Y), (X-1,Y+1), (X-1,Y-1),如图所示

      在这里插入图片描述

    • 我们要查看这八个坐标区域是否是雷,并记录

  • 那么问题来了,如果我们排查的是如下图画线区域的坐标呢?假设我们定义的是长宽都为N的二维数组,那么当我们排查(0,0),(0,1),(1,0)……这些边界坐标时就会出现数组越界的情况,因此我们就有必要适当的扩大。

    在这里插入图片描述

  • 我们可以将二维数组的长和宽各增加两行/列(但有效区域仍然是大小为N*N的区域),这样当我们排查原本是边界坐标时就不会出现数组越界的情况了。

  • 为了保证操作的一致性,我们也将展示数组的大小定义为(N+2)*(N+2)

    /*
    	ROW,COL即真实的区域大小
    	ROWS,COLS即扩容后的大小
    */
    #define ROW 9
    #define COL 9
    #define ROWS ROW + 2
    #define COLS COL + 2
    
    void game()
    {
        //定义存放地雷的数组,和展示结果的数组
        //此时,[1,ROW]为横纵坐标的有效取值
        char mineBoard[ROWS][COLS];
        char showBoard[ROWS][COLS];
    }
    

初始化数组

  • 我们规定,两个数组都是字符数组

  • 用字符‘1’代表地雷,字符‘0’代表非地雷(具体原因会在后面讲到)

  • 将展示数组全部初始化为‘*’,代表未知

    /*
    	row,col即传入数组的行数和列数,这里row = ROWS,col = COLS
    	因为要将扩容的区域初始化为没有地雷的状态		
    	set即要初始化的字符
    */
    void BoardInit(char board[][COLS], int row, int col, char set)
    {
    	for (int i = 0; i < row; i++)
    		for (int j = 0; j < col; j++)
    			board[i][j] = set;
    }
    

布置地雷

  • 假设我们要随机布置MINENUMBER个地雷

  • 既然涉及到了“随机”这一概念,那么显然,我们就要用到随机数这一概念

  • 我们可以利用循环,在循环里随机生成地雷的坐标,只要这个坐标没被布置过地雷(即这个坐标代表的字符不是‘1’),那么就在这个坐标布置地雷

    /*
    	这里row = ROW,col = COL,因为地雷不会被布置在扩容区域
    	扩容区域只是为了方便记录边界区域周围的地雷个数
    */
    void MineSet(char board[][COLS], int row, int col)
    {
    	int count = 0;
    	while (count < MINENUMBER)
    	{
    		int x = rand() % row + 1;
    		int y = rand() % col + 1;
    		if (board[x][y] == '0')
    		{
    			board[x][y] = '1';
    			count++;
    		}
    	}
    }
    

打印展示数组

  • 布置完地雷后,我们就需要打印出展示数组,方便用户进行扫雷

  • 为了方便用户确定要排查区域的位置,我们可以顺便打印出展示数组的行数和列数,如图:

    在这里插入图片描述

    /*
    	这里row = ROW,col = COL,扩容区域不需要排查
    	扩容区域只是为了方便记录边界区域周围的地雷个数
    */
    void BoardDisplay(char board[][COLS], int row, int col)
    {
    	for (int i = 0; i <= col; i++)
    		printf("%d ", i);
    	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");
    	}
    }
    

排查地雷

  • 接下来就需要用户排查地雷了
  • 我们利用循环提醒用户输入要排查的坐标,有以下五种情况:
    • 如果排查的坐标是雷,那么直接退出game()函数,表示排雷失败(如果排雷失败,那么就要向用户展示存放雷的数组,让用户明白到底错在了哪)
    • 如果排查的坐标已经被检查过,那么提醒用户,重新输入
    • 如果排查的坐标非法,那么提醒用户,重新输入
    • 如果排查的坐标不是雷,并且还未排雷完毕,那么显示该区域周围有几个雷,继续循环
    • 如果排查的坐标不是雷,并且所有雷已经排查完毕,那么退出game()函数,表示排雷成功
  • 每排查完一次,都要重新打印一次展示数组

记录指定区域周围地雷的个数

  • 上面我们讲到,要确定一个指定坐标(X,Y)周围地雷的个数,只需要查看(X,Y-1), (X,Y+1), (X+1,Y), (X+1,Y+1), (X+1,Y-1), (X-1,Y), (X-1,Y+1), (X-1,Y-1)这八个坐标的值

  • 前面我们已经假设字符‘1’代表地雷,字符‘0’代表非地雷,由于字符‘0’的ASCII的值为48,字符‘1’的ASCII值为49(比字符‘0’大一),那么我们统计(X,Y)周围地雷的个数就只需要将周围八个坐标储存的值相加,然后减去8个字符‘0’即可

  • 这也是将字符‘1’设置为地雷,字符‘0’设置为非地雷的巧妙之处,可以在计算地雷个数的时候少去很多操作

    /*
    	这里row = ROWS,col = COLS
    	这样可以确保排查任意区域操作的一致性(数组不会越界)		
    */
    int MineNumber(char board[][COLS], int x, int y)
    {
    	return (board[x][y - 1] + board[x][y + 1]
    		+ board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1]
    		+ board[x + 1][y] + board[x + 1][y - 1] + board[x + 1][y + 1]
    		- 8 * '0');
    }
    

判断排雷成功

  • 如果被排查完的区域的总个数等于整块区域的个数减去地雷个数,那就说明排雷成功

    /*
    	传入的是展示数组showBoard
    	这里row = ROW,col = COL,扩容区域不需要排查
    	扩容区域只是为了方便记录边界区域周围的地雷个数
    */
    //如果排雷成功,就返回真,否则返回假
    bool MineFinish(char board[][COLS], int row, int col)
    {
    	int count = 0;	//count用来记录已经排查的区域的个数
    	for(int i = 1; i <= row; i++)
    		for (int j = 1; j <= col; j++)
    		{
    			if (board[i][j] != '*')
    			{
    				count++;
    				if (count == row * col - MINENUMBER)	//MINENUMBER就是地雷总个数
    					return true;
    			}
    		}
    	return false;
    }
    

排查地雷实现代码

void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x, y;
	while (1)
	{
		printf("请输入您要排查的坐标(用空格分隔):");
		scanf_s("%d %d", &x, &y);
        //如果排查的区域未被检查并且坐标合法
		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
            //如果踩到了雷
			if (mine[x][y] == '1')
			{
				printf("很遗憾,排雷失败\n");
				BoardDisplay(mine, row, col);
				return;
			}
            //否则继续排雷
			else
			{
				show[x][y] = MineNumber(mine, x, y) + '0';
                //
				if (MineFinish(show, row,col))
				{
					printf("恭喜你,排雷成功\n");
					BoardDisplay(mine, row, col);
					return;
				}
				BoardDisplay(show, row, col);
			}
		}
        //如果排查的区域合法但已经被检查
		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			printf("该位置已经被检查,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
		//如果排查的区域不合法	
		else
		{
			printf("坐标非法,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
	}
}

基本功能的实现代码和效果展示

  • 我们先来看看实现效果:

    在这里插入图片描述

  • 实现代码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    #include<stdbool.h>
    
    #define ROW 9
    #define COL 9
    #define ROWS ROW + 2
    #define COLS COL + 2
    #define MINENUMBER 10
    
    void BoardInit(char board[][COLS], int row, int col, char set);
    void BoardDisplay(char board[][COLS], int row, int col);
    void MineSet(char board[][COLS], int row, int col);
    int MineNumber(char board[][COLS], int x, int y);
    void MineFind(char mine[][COLS], char show[][COLS], int row, int col);
    bool MineFinish(char board[][COLS], int row, int col);
    
    void meau()
    {
    	printf("****************\n");
    	printf("*****1.Play*****\n");
    	printf("*****0.Exit*****\n");
    	printf("****************\n");
    }
    
    void game()
    {
    	//生成时间戳
    	srand((unsigned int)time(NULL));
    
    	//定义存放地雷的数组,和展示结果的数组
    	char mineBoard[ROWS][COLS];
    	char showBoard[ROWS][COLS];
    
    	//对两个数组进行初始化
    	BoardInit(mineBoard, ROWS, COLS, '0');
    	BoardInit(showBoard, ROWS, COLS, '*');
    
    	//设置地雷
    	MineSet(mineBoard, ROW, COL);
    	
        //打印展示数组
    	BoardDisplay(showBoard, ROW, COL);
    
    	//排查地雷
    	MineFind(mineBoard, showBoard, ROW, COL);
    }
    
    void BoardInit(char board[][COLS], int row, int col, char set)
    {
    	for (int i = 0; i < row; i++)
    		for (int j = 0; j < col; j++)
    			board[i][j] = set;
    }
    
    void BoardDisplay(char board[][COLS], int row, int col)
    {
    	for (int i = 0; i <= col; i++)
    		printf("%d ", i);
    	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");
    	}
    }
    
    void MineSet(char board[][COLS], int row, int col)
    {
    	int count = 0;
    	while (count < MINENUMBER)
    	{
    		int x = rand() % row + 1;
    		int y = rand() % col + 1;
    		if (board[x][y] == '0')
    		{
    			board[x][y] = '1';
    			count++;
    		}
    	}
    }
    
    void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
    {
    	int x, y;
    	while (1)
    	{
    		printf("请输入您要排查的坐标(用空格分隔):");
    		scanf_s("%d %d", &x, &y);
    		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			if (mine[x][y] == '1')
    			{
    				printf("很遗憾,排雷失败\n");
    				BoardDisplay(mine, row, col);
    				return;
    			}
    			else
    			{
    				show[x][y] = MineNumber(mine, x, y) + '0';
    				if (MineFinish(show, row,col))
    				{
    					printf("恭喜你,排雷成功\n");
    					BoardDisplay(mine, row, col);
    					return;
    				}
    				BoardDisplay(show, row, col);
    			}
    		}
    		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			printf("该位置已经被检查,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    			
    		else
    		{
    			printf("坐标非法,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    	}
    }
    
    int MineNumber(char board[][COLS], int x, int y)
    {
    	return (board[x][y - 1] + board[x][y + 1]
    		+ board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1]
    		+ board[x + 1][y] + board[x + 1][y - 1] + board[x + 1][y + 1]
    		- 8 * '0');
    }
    
    bool MineFinish(char board[][COLS], int row, int col)
    {
    	int count = 0;
    	for(int i = 1; i <= row; i++)
    		for (int j = 1; j <= col; j++)
    		{
    			if (board[i][j] != '*')
    			{
    				count++;
    				if (count == row * col - MINENUMBER)
    					return true;
    			}
    		}
    	return false;
    }
    
    int main()
    {
    	int input;
    	meau();	//展示选择菜单
    	printf("请输入您的选择:");
    	while (1)
    	{
    		scanf_s("%d", &input);
    		switch (input)
    		{
    		case 1:
    			game();
    			break;
    		case 0:
    			printf("退出游戏,感谢游玩\n");
    			break;
    		default :
    			printf("输入错误,请重新输入:");
    			break;
    		}
    		meau();
    		printf("是否重新游玩:");
    	}
    	return 0;
    }
    

拓展功能

简化游戏界面

  • 根据上面的展示我们可以看到,我们每排查一次,展示数组就要打印一次,这样就会使得游戏界面太过复杂

  • 我们可以使用system(“cls”)函数来清除已经不需要的打印结果

  • 注:要包含头文件<Windows.h>

  • 修改后的MineFind()函数:

    void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
    {
    	int x, y;
    	while (1)
    	{
    		printf("请输入您要排查的坐标(用空格分隔):");
    		scanf_s("%d %d", &x, &y);
            
            //就是多了这样一条语句
    		system("cls");
            
    		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			if (mine[x][y] == '1')
    			{
    				printf("很遗憾,排雷失败\n");
    				BoardDisplay(mine, row, col);
    				return;
    			}
    			else
    			{
    				show[x][y] = MineNumber(mine, x, y) + '0';
    				if (MineFinish(show, row,col))
    				{
    					printf("恭喜你,排雷成功\n");
    					BoardDisplay(mine, row, col);
    					return;
    				}
    				BoardDisplay(show, row, col);
    			}
    		}
    		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			printf("该位置已经被检查,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    			
    		else
    		{
    			printf("坐标非法,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    	}
    }
    
  • 实现效果:

    在这里插入图片描述

改变字体颜色

  • 为了将坐标数字和代表地雷个数的数字作出区分,我们可以改变这两种数字的颜色

  • 如果对如何改变字体颜色和背景色还不是太了解,可以先看看C语言——修改控制台背景色和字体颜色这篇博客

  • 直接上代码(只需要调整打印数组的函数BoardDisplay且在主函数最开始加上system(“color”)函数即可)

    int main()
    {
        system("color 17");
        ………………;
    }
    
    void BoardDisplay(char board[][COLS], int row, int col)
    {
    	for (int i = 0; i <= col; i++)
    	{
    		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
    		printf("%d ", i);
    	}
    
    	printf("\n");
    	for (int i = 1; i <= row; i++)
    	{
    		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
    		printf("%d ", i);
    		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | 16);
    		for (int j = 1; j <= col; j++)
    			printf("%c ", board[i][j]);
    		printf("\n");
    	}
    }
    
  • 看看效果:

    在这里插入图片描述

实现爆炸展开效果

  • 我们看到,网页版的扫雷中,会出现点击一个区域就自动展开一片区域的情况,如图:

    在这里插入图片描述

  • 要实现这一功能,那我们首先就需要知道什么条件下才会出现爆炸展开的情况。

  • 展开条件:如果排查的坐标(X,Y)周围地雷的总个数是0(既没有地雷),那么就自动排查它周围的八个区域(即坐标(X,Y-1), (X,Y+1), (X+1,Y), (X+1,Y+1), (X+1,Y-1), (X-1,Y), (X-1,Y+1), (X-1,Y-1)),对这八个坐标执行同样的操作,不断递归,直达每个区域的周围都至少有一个地雷(找到爆炸展开的边界)

  • 实现这个功能要注意以下几点:

    • 注意递归时数组不要越界
    • 注意递归时排查重复递归的情况
    • 如果排查区域周围地雷的个数为0,那么为了界面的简洁,就不再将0打印出,改为打印空格
  • Explode()的实现代码:

    //x,y即用户输入的排查坐标
    void Explode(char mine[][COLS], char show[][COLS], int x, int y)
    {
        //如果该位置已经配排查(重复递归)或坐标无效,那么退出这个函数(不是结束递归)
    	if (show[x][y] == ' ' || x < 1 || y < 1 || x > ROW || y > COL)
    		return;
        //如果该位置周围的地雷个数为0,那么符合递归条件,继续排查
    	if (MineNumber(mine, x, y) == 0)
    		show[x][y] = ' ';
        //如果坐标有效但周围的地雷个数不为零,那么退出这个函数(不是结束递归)
    	else
    	{
    		show[x][y] = MineNumber(mine, x, y) + '0';
    		return;
    	}
    	
        //自动排查(X,Y)周围的8个坐标
    	Explode(mine, show, x, y - 1);
    	Explode(mine, show, x, y + 1);
    	Explode(mine, show, x + 1, y + 1);
    	Explode(mine, show, x + 1, y);
    	Explode(mine, show, x + 1, y - 1);
    	Explode(mine, show, x - 1, y + 1);
    	Explode(mine, show, x - 1, y - 1);
    	Explode(mine, show, x - 1, y);
    }
    
  • 要对MineFind()函数做出相应的修改

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7sF9UEGD-1686494176819)(C:/Users/HUASHUO/Desktop/扫雷.5.gif)]void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
    {
    	int x, y;
    	while (1)
    	{
    		printf("请输入您要排查的坐标(用空格分隔):");
    		scanf_s("%d %d", &x, &y);
    		system("cls");
    		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			if (mine[x][y] == '1')
    			{
    				printf("很遗憾,排雷失败\n");
    				BoardDisplay(mine, row, col);
    				return;
    			}
    			else
    			{
    				Explode(mine, show, x, y);
    				if (MineFinish(show, row,col))
    				{
    					printf("恭喜你,排雷成功\n");
    					BoardDisplay(mine, row, col);
    					return;
    				}
    				BoardDisplay(show, row, col);
    			}
    		}
    		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
    		{
    			printf("该位置已经被检查,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    		else
    		{
    			printf("坐标非法,请重新输入:\n");
    			BoardDisplay(show, row, col);
    		}
    	}
    }
    

实现效果

在这里插入图片描述

插旗功能

  • 我们假设旗子的标志是字符:‘$’
  • 注:用户如果在一个区域插上旗子,就说明用户认为这个区域是雷,即插旗是为了方便用户排雷
  • 实现逻辑并不复杂,至少需要注意细节的处理

插旗

void Flag_In(char show[][COLS])
{
	int row = 0, col = 0;
	printf("您想在哪个位置插入旗子:\n");
	printf("注:用空格分隔,输入0 0结束插旗\n");
	while(1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '*')
			printf("该位置不能插旗\n");
		else
		{
			system("cls");
			count++;	//旗子数目加一
			show[row][col] = '$';
			BoardDisplay(show, ROW, COL);
		}
	}
	while (getchar() != '\n');	//清空缓冲区
}

取消所插旗子

void Flag_Out(char show[][COLS])
{
	int row, col;
	printf("您想取消哪个位置的旗子:\n");
	printf("注:用空格分隔,输入0 0结束\n");
	while (1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '$')
			printf("该位置不是旗子\n");
		else
		{
			system("cls");
			show[row][col] = '*';
			BoardDisplay(show, ROW, COL);
		}
	}
}

调整MineFind()函数

void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x, y;
	char ch;
	while (1)
	{
		printf("请输入您要排查的坐标(用空格分隔):");
		scanf_s("%d %d", &x, &y);
		system("cls");
		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,排雷失败\n");
				BoardDisplay(mine, row, col);
				return;
			}
			else
			{
				Explode(mine, show, x, y);

				if (MineFinish(show, row,col))
				{
					printf("恭喜你,排雷成功\n");
					BoardDisplay(mine, row, col);
					return;
				}

				BoardDisplay(show, row, col);

				getchar();

				printf("是否需要插旗(Y/N):\n");
				if((ch = getchar()) == 'Y')
					Flag_In(show);

				getchar();

				printf("是否需要取消所插的旗子(Y/N):\n");
				if ((ch = getchar()) == 'Y')
					Flag_Out(show);
			}
		}
		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			printf("该位置已经被检查,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
			
		else
		{
			printf("坐标非法,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
	}
}

实现效果

在这里插入图片描述

拓展功能实现代码

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

#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define MINENUMBER 10

void BoardInit(char board[][COLS], int row, int col, char set);
void BoardDisplay(char board[][COLS], int row, int col);
void MineSet(char board[][COLS], int row, int col);
int MineNumber(char board[][COLS], int x, int y);
void MineFind(char mine[][COLS], char show[][COLS], int row, int col);
bool MineFinish(char board[][COLS], int row, int col);
void Explode(char mine[][COLS], char show[][COLS], int x, int y);
void Flag_In(char show[][COLS]);
void Flag_Out(char show[][COLS]);

void meau()
{
	printf("****************\n");
	printf("*****1.Play*****\n");
	printf("*****0.Exit*****\n");
	printf("****************\n");
}

void game()
{
	//生成时间戳
	srand((unsigned int)time(NULL));

	//定义存放地雷的数组,和展示结果的数组
	char mineBoard[ROWS][COLS];
	char showBoard[ROWS][COLS];

	//对两个数组进行初始化
	BoardInit(mineBoard, ROWS, COLS, '0');
	BoardInit(showBoard, ROWS, COLS, '*');

	//设置地雷
	MineSet(mineBoard, ROW, COL);

	//BoardDisplay(mineBoard, ROW, COL);
	BoardDisplay(showBoard, ROW, COL);

	//排查地雷
	MineFind(mineBoard, showBoard, ROW, COL);
}

void BoardInit(char board[][COLS], int row, int col, char set)
{
	for (int i = 0; i < row; i++)
		for (int j = 0; j < col; j++)
			board[i][j] = set;
}

void BoardDisplay(char board[][COLS], int row, int col)
{
	for (int i = 0; i <= col; i++)
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
		printf("%d ", i);
	}

	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 4 | 16);
		printf("%d ", i);
		for (int j = 1; j <= col; j++)
		{
			SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | 16);
			if(board[i][j] == '$')
				SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 6 | 16);
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7 | 16);
}

void MineSet(char board[][COLS], int row, int col)
{
	int count = 0;
	while (count < MINENUMBER)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count++;
		}
	}
}

void MineFind(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x, y;
	char ch;
	while (1)
	{
		printf("请输入您要排查的坐标(用空格分隔):");
		scanf_s("%d %d", &x, &y);
		system("cls");
		if (show[x][y] == '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,排雷失败\n");
				BoardDisplay(mine, row, col);
				return;
			}
			else
			{
				Explode(mine, show, x, y);

				if (MineFinish(show, row,col))
				{
					printf("恭喜你,排雷成功\n");
					BoardDisplay(mine, row, col);
					return;
				}

				BoardDisplay(show, row, col);

				getchar();

				printf("是否需要插旗(Y/N):\n");
				if((ch = getchar()) == 'Y')
					Flag_In(show);

				getchar();

				printf("是否需要取消所插的旗子(Y/N):\n");
				if ((ch = getchar()) == 'Y')
					Flag_Out(show);
			}
		}
		else if (show[x][y] != '*' && x > 0 && x <= row && y > 0 && y <= col)
		{
			printf("该位置已经被检查,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
			
		else
		{
			printf("坐标非法,请重新输入:\n");
			BoardDisplay(show, row, col);
		}
	}
}

int MineNumber(char board[][COLS], int x, int y)
{
	return (board[x][y - 1] + board[x][y + 1]
		+ board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1]
		+ board[x + 1][y] + board[x + 1][y - 1] + board[x + 1][y + 1]
		- 8 * '0');
}

bool MineFinish(char board[][COLS], int row, int col)
{
	int count = 0;
	for(int i = 1; i <= row; i++)
		for (int j = 1; j <= col; j++)
		{
			if (board[i][j] != '*')
			{
				count++;
				if (count == row * col - MINENUMBER)
					return true;
			}
		}
	return false;
}

void Explode(char mine[][COLS], char show[][COLS], int x, int y)
{
	if (show[x][y] == ' ' || x < 1 || y < 1 || x > ROW || y > COL)
		return;
	if (MineNumber(mine, x, y) == 0)
		show[x][y] = ' ';
	else
	{
		show[x][y] = MineNumber(mine, x, y) + '0';
		return;
	}

	Explode(mine, show, x, y - 1);
	Explode(mine, show, x, y + 1);
	Explode(mine, show, x + 1, y + 1);
	Explode(mine, show, x + 1, y);
	Explode(mine, show, x + 1, y - 1);
	Explode(mine, show, x - 1, y + 1);
	Explode(mine, show, x - 1, y - 1);
	Explode(mine, show, x - 1, y);
}

void Flag_In(char show[][COLS])
{
	int row = 0, col = 0;
	printf("您想在哪个位置插入旗子:\n");
	printf("注:用空格分隔,输入0 0结束插旗\n");
	while(1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '*')
			printf("给位置不能插旗\n");
		else
		{
			system("cls");
			show[row][col] = '$';
			BoardDisplay(show, ROW, COL);
		}
	}
}

void Flag_Out(char show[][COLS])
{
	int row, col;
	printf("您想取消哪个位置的旗子:\n");
	printf("注:用空格分隔,输入0 0结束\n");
	while (1)
	{
		scanf_s("%d %d", &row, &col);
		if (row == 0 && col == 0)
			break;
		if (row < 1 || row > ROW || col < 1 || col > COL)
		{
			printf("坐标非法,重新输入:");
			continue;
		}
		else if (show[row][col] != '$')
			printf("该位置不是旗子\n");
		else
		{
			system("cls");
			show[row][col] = '*';
			BoardDisplay(show, ROW, COL);
		}
	}
}

int main()
{
	system("color 17");
	int input;
	meau();
	printf("请输入您的选择:");
	while (1)
	{
		scanf_s("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏,感谢游玩\n");
			break;
		default :
			printf("输入错误,请重新输入:");
			break;
		}
		meau();
		printf("是否重新游玩:");
	}
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[创业之路-73] :如何判断一个公司或团队是熵减:凝聚力强、上下一心,还是,熵增:一盘散沙、乌合之众?

目录 前言&#xff1a; 一盘散沙、乌合之众&#xff1a; 凝聚力强、上下一心&#xff1a; 一、股权结构与利益分配 一盘散沙、乌合之众 凝聚力强、上下一心 二、组织架构与岗位职责 一盘散沙、乌合之众 凝聚力强、上下一心 三、战略目标 一盘散沙、乌合之众 凝聚力…

碳排放预测模型 | Python实现基于MLP多层感知机的碳排放预测模型(预测未来发展趋势)

文章目录 效果一览文章概述研究内容环境准备源码设计学习总结参考资料效果一览 ![1](https://img-blog.csdnimg.cn/34c113bde2 文章概述 碳排放预测模型 | Python实现基于MLP多层感知机的碳排放预测模型(预测未来发展趋势) 研究内容 这是数据集的链接:https://github.com/…

【伏羲八卦图】(PythonMatlab实现)

目录 1 与达尔文对话 2 与老子对话 2.1 Python实现 2.2 Matlab实现 1 与达尔文对话 140年前&#xff0c;1858年7月1日&#xff0c;达尔文在英伦岛发表了自己有关自然选择的杰出论文。他提出&#xff0c;生物的发展规律是物竞天择。经过物竞&#xff0c;自然界选择并存留最具…

【CAD】【动态块】CAD设置动态块

文章目录 1 CAD的动态块及应用2 块的相关概念2.1 块的相关命令2.1.1 创建块BLOCK2.1.2 插入块INSERT2.1.3 编辑块BEDIT2.1.4 重命名块RENAME 2.2 CAD组&#xff08;group&#xff09;和块&#xff08;block&#xff09;的区别2.3 “块”&#xff08;block&#xff09;和“写块”…

CMake学习(6): 打印日志信息及宏定义

1. message 打印日志 介绍CMake中的调试打印命令Message, 可以为用户显示一条消息&#xff0c;并在终端上打印显示。假如&#xff0c;我们通过File命令对文件进行了搜索&#xff0c;但是不能确定搜索到的文件就是我们需要的文件。此时&#xff0c;可以利用message将搜索到的变…

PS 套索选区工具(2) 多边形套索工具 磁性套索工具使用技巧

上文PS 套索选区工具(1) 套索工具基础使用带大家了解了套索工具的基本操作 这边 我们右键套索工具 它还有个 多边形套索工具 多边形套索工具是用来画直线的 我们选中它 然后 我们鼠标点击一下 然后 移动到指定位置 从你点的位置开始 到结束的地方 就会形成一个直线的选区 点…

SpringBoot项目编译运行时提示“程序包xxx不存在,找不到符号”

今天想要在虚拟机上部署自己的前后端项目&#xff0c;在系统打包时碰到了这个问题&#xff0c;记录一下 1. 项目结构&#xff0c;启动程序再pub-oa-web项目中 2、编译异常报错分析 编译中报错是在其他子项目中都配置了如下代码&#xff0c;导致项目每个子项目都是可执行的&am…

第五章数组

我们之前学习&#xff0c;如果我们存储一个值&#xff08;或者说只有一个值在改变&#xff09;那么我们使用变量&#xff1b; 但是如果我们需要存储多个值&#xff0c;因该怎么办呢&#xff01; 1 数组 1.1 概念 数组Array&#xff0c;标志是[ ] ,用于储存多个相同类型数据的集…

JDBC 连接池 详解(通俗易懂)

目录 一、前言 二、传统连接方式的弊端分析 1.局限性 : 2.几个弊端 : 三、数据库连接池 1.基本介绍 : 2.示意图如下 : 3.连接池种类 : 四、C3P0连接池 0.准备工作 : 1.方式一 —— 程序中指定相关参数 : 2.方式二 —— 通过配置文件 : 五、Druid&#xff08;德鲁伊…

Vue中如何进行路由懒加载

Vue中如何进行路由懒加载 路由懒加载是一种优化Vue应用程序性能的技术。它可以延迟加载路由组件&#xff0c;从而减少应用程序的初始加载时间&#xff0c;提高应用程序的性能。本文将介绍Vue中如何进行路由懒加载&#xff0c;包括使用Vue异步组件和Webpack代码分割。 使用Vue异…

Docker本地私有仓库、harbor私有仓库部署与管理

目录 一、本地私有仓库1、本地私有仓库简介2、搭建本地私有仓库3、容器重启策略介绍 二、harbor私有仓库部署与管理1、什么是harbor2、Harbor的特性3、Harbor的构成4、harbor部署及配置①部署docker-compose②部署Harbor服务③登录创建项目④登录仓库并上传镜像 5、客户端测试①…

Python 基于 Django 的学生成绩管理系统,可视化界面

1简介 对于学生成绩管理系统&#xff0c;充分运用现代化的信息技术手段&#xff0c;对于学生成绩信息管理发展的趋势就是信息化&#xff0c;信息化时代下的信息管理&#xff0c;需要深化信息管理体制与手段的改革&#xff0c;充分运用信息化手段来全方位的进行学生成绩管理系统…

Vue中如何进行数据响应式更新?

Vue中如何进行数据响应式更新&#xff1f; Vue是一款流行的JavaScript框架&#xff0c;它提供了数据响应式更新的能力&#xff0c;可以让我们轻松地更新数据&#xff0c;并自动更新视图。本文将介绍Vue中如何进行数据响应式更新&#xff0c;包括使用Vue的响应式系统、使用计算…

nacos-sdk-rust binding for Python

广告时间 nacos-sdk-rust-binding-py : nacos-sdk-rust binding for Python with PyO3. Tip: nacos-sdk-python 仓库暂未提供 2.x gRPC 交互模式&#xff0c;为了能升级它&#xff0c;故而通过 ffi 方式调用 nacos-sdk-rust py 包 -> https://pypi.org/project/nacos-sdk-r…

【Java代码的运行过程】 ——每天一点小知识

&#x1f4a7; J a v a 代码的运行过程 \color{#FF1493}{Java代码的运行过程} Java代码的运行过程&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏…

Vue中如何处理日期与时间

Vue中如何处理日期与时间 日期和时间处理是Web应用中常见的需求&#xff0c;Vue作为一款流行的前端框架&#xff0c;提供了很多方便的工具和库&#xff0c;以满足不同业务场景下的需求。本文将介绍如何在Vue中处理日期和时间&#xff0c;包括使用原生JavaScript、Moment.js和D…

useMemo和useCallback

上述代码中&#xff0c;useSate 用于定义了三个状态&#xff1a;supNum、oppNum、x。在组件函数中&#xff0c;我们根据这些状态来计算投票的支持率&#xff0c;并将其渲染到视图中。但是&#xff0c;每次状态变化时&#xff0c;投票支持率的计算都会重新执行&#xff0c;即使其…

c++深搜1-迷宫类问题

目录 1.问题引入 2.知识讲解 3.例题解析 【例题1】迷宫的第一条出路。 题目描述 输入格式 输出格式 样例 输入数据#1 输出数据#1 输入数据#2 输出数据#2 【例题2】迷宫的所有路径。 【例题3】走出迷宫的最少步数。 1.问题引入 拓拓小时候玩迷宫游戏时&#xff…

小酷智慧地图3D导公众号小程序 v1.0.50+前端

&#x1f388; 限时活动领体验会员&#xff1a;可下载程序网创项目短视频素材 &#x1f388; &#x1f389; 有需要的朋友记得关赞评&#xff0c;阅读文章底部来交流&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 小酷智慧地图3D导览系统适用于城市或乡镇全…

性能测试之全链路压测实战理论详解

目录 前言 01为什么需要全链路压测&#xff1f; 02全链路压测解决了哪些问题&#xff1f; 03哪些业务场景适合做 04基础技术组件 05小结 前言 要说当下研发领域最热门的几个词&#xff0c;全链路压测 肯定跑不了。最近的几次大会上&#xff0c;也有不少关于全链路的议题。…