扫雷游戏升级版 含递归链式展开(一次展开一片区域) 代码详细解读 C语言

1、前言:

我看了CSDN有很多小伙伴也写了扫雷小游戏 但是大部分写的代码都是一次输入坐标只能展开一个位置并没有还原我们小时候最初始的游戏玩法 可玩性很低  我在这用函数递归链式展开一片还原最初始游戏 提高可玩性

 先放效果图↓

38a17b7f911f45a6b002eb656f09d876.png

2、建议:

一个程序代码的实现并不是只靠看能看会的  而是要落实到敲代码 可以先靠自己画导图 根据导图敲出代码  敲代码的过程中难免会有些错误 解决它 这都将成为你宝贵的知识  *在看的过程也可以拿出稿纸和笔来画出导图

3 什么是扫雷?

《扫雷》中玩家的目标是在最短时间内识别出所有非雷区的格子,同时避免触碰到地雷。游戏区域由多个隐藏格子组成,每个格子可能隐藏着地雷或数字,数字表示周围八个格子中地雷的数量。玩家通过点击格子揭示内容,使用逻辑推理逐步排除雷区。游戏考验玩家的观察力和推理能力,任何一次误点地雷都会导致游戏失败。

4 须知:

本篇代码中 '0'(字符0)代表安全位置   '1'(字符1)代表雷  至于为何是这样设定后面会提到

5 正文:

首先我们需要三个文件(意义:代码没有那么冗杂 提高代码的可读性 方便修改)两个源文件test.c(代码的主体)game.c(游戏运行的本质)和一个头文件game.h(头文件的包含 函数的声明)

接下来 我们一一来写代码↓

先写出test.c的主函数 注:test.c与game.c都要引用头文件game.h 而game.h的内容后面会讲到

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

跟三子棋游戏类似 主函数都特别简单 挑战还是在test函数里面的

再写出test.c文件里的test函数

void test()
{
	srand((unsigned int)time(NULL));
	//时间戳生成随机数字
	//后面布雷随机坐标的生成会用到 与rand连用
	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏");
			break;
		default:
			printf("选择错误 请重新选择\n");
			break;
		}


	} while (input);
}

可以看到test函数里面又包含了menu函数和game函数 在这里menu是用来打印菜单 game才是游戏运行的本质

menu函数

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

game函数

void game()
{
	char mine[Rows][Cols] = { 0 };//布置好的雷的信息
	char show[Rows][Cols] = { 0 };//排查出的雷的信息
	//排查坐标时 为了防止坐标的越界 我们需要给数组加两行两列
	//初始化棋盘
	init_board(mine, Rows, Cols, '0');
	init_board(show, Rows, Cols, '*');
	//打印棋盘
	print_board(show, Row, Col);
	//布置雷
	set_mine(mine, Row, Col);
	//print_board(mine, Row, Col);//写代码中途可以借用看布雷情况 方便后续写作
	//排查雷
	find_mine(mine, show, Row, Col);
}

*重点理解:game函数的运行逻辑 先初始化布雷和展示面板 打印出来要展示的面板 在布雷面板布置雷 在展示面板输入坐标排雷 显示排雷后的面板

据上代码可见game函数里面又包含了许多函数

init_board()初始化面板

print_board()打印面板

set_mine()布置雷

find_mine()排查雷

这些函数以及Row Rows 和Col Cols的声明 和库函数头文件的引用都可以放在头文件game.h里面

下面是game.h文件代码↓

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>//rand srand
#include<time.h>//time
#define easy_mode 10//布置雷的个数(类似于模式难度)
#define Row 9//9*9宫格
#define Col 9
#define Rows Row+2//上下左右各多一维 防止数组访问越界
#define Cols Col+2
//初始化棋盘
void init_board(char board[Rows][Cols], int ros, int cols, char set);
//打印棋盘
void print_board(char board[Rows][Cols], int row, int col);
//布置雷
void set_mine(char mine[Rows][Cols], int row, int col);
//排查雷
void find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

game函数里面的一系列函数是什么实现的 具体内容是什么? 这就要用到game.c文件 也是本游戏代码逻辑书写的最困难的地方  但是只要基础扎实都会迎刃而解
接下来我们一一来写↓

(1)初始化面板init_board()

void init_board(char board[Rows][Cols], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

(2)打印面板print_board()

void print_board(char board[Rows][Cols], int row, int col)
{
	int i = 0;
	printf("-------扫雷--------\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------扫雷--------\n");
}

(3)布置雷set_mine()

void set_mine(char mine[Rows][Cols], int row, int col)
{
	int count = easy_mode;
	while (count)
	{ 
		//生成随机下标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

(4)排查雷find_mine()

void find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	printf("请输入坐标");
	while (win < (row * col - easy_mode))
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= col && y >= 1 && y <= row)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标被排查过了 请重新输入");
				continue;
			}
			if (mine[x][y] == '1')
			{
				printf("很遗憾 你被炸死了\n");
				print_board(mine, row, col);
				break;
			}
			else
			{
				chain_reflect(mine, show,row , col, x, y);
				print_board(show, row, col);
				win++;
			}
		}
		else
		{
			printf("坐标输入非法 请重新输入\n");
		}
	}
	if (win == (row * col - easy_mode))
	{
		printf("恭喜你雷全部排查完 游戏胜利\n");
	}
}

我们又又又看到find_mine函数里有chain_reflect函数 通过函数名字我们也可以知道其作用 就是开头讲的 用递归链式展开一片区域  书写思路1 判断坐标是否越界 2 是否被排查过 3 周围八个方格里没有雷 三个条件成立该坐标和旁边八个坐标就一起递归 否则就返回该坐标周围雷的个数

代码如下↓

void chain_reflect(char mine[Rows][Cols], char show[Rows][Cols], int row, int col, int x, int y)
{
	int i = 0;
	int j = 0;
	if (x >= 1 && x <= col && y >= 1 && y <= row)//防止坐标越界
	{
		if (show[x][y] == '*')//判断是否被排查过
		{
			if (get_mine_count(mine, x, y) == 0)
			{
				show[x][y] = ' ';
				for (i = x - 1; i <= x + 1; i++)
				{
					for (j = y - 1; j <= y + 1; j++)
					{
						chain_reflect(mine, show, row, col, i, j);//递归传递
					}
				}
			}
			else
			{
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';//记下该坐标雷的个数
			}
		}
	}
	return;
}

看到这里问题来了 上面get_mine_count函数是怎么统计雷的个数的呢 ?这里就要巧妙的用到'1'   前面所说的为何雷设置为'1' 安全位置为何设置为'0'  这就需要知道'0'的ASCII 值是48   '1'ASCII值是 49 

所以我们计算雷的个数就可以等同为计算该坐标周围八个坐标字符相加 再 减去 8 * '0' 返回的int 值 就是雷的个数  举个例子:如果周围有两个雷 那么计算就为2 * '1' + 6 * '0' - 8 * '0'  返回的int 值就是2  注意:字符运算的本质就是ASCII值的运算   上面的show[x][y] = n + '0' 就是 '2'   面板上展示的都是字符 因为我们定义的就是char board[]

下面就是get_mine_count函数的代码↓

int get_mine_count(char mine[Rows][Cols], int x, int y)
{
	return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
		mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
		mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');

}

这样全部的代码就写完了

下面我们来看完整的代码↓

test.c源文件代码

//扫雷游戏
#include"test(game)50.h"
void game()
{
	char mine[Rows][Cols] = { 0 };//布置好的雷的信息
	char show[Rows][Cols] = { 0 };//排查出的雷的信息
	//排查坐标时 为了防止坐标的越界 我们需要给数组加两行两列
	//初始化棋盘
	init_board(mine, Rows, Cols, '0');
	init_board(show, Rows, Cols, '*');
	//打印棋盘
	print_board(show, Row, Col);
	//布置雷
	set_mine(mine, Row, Col);
	//print_board(mine, Row, Col);//写代码中途可以借用看布雷情况 方便后续写作
	//排查雷
	find_mine(mine, show, Row, Col);
}
void menu()
{
	printf("***************************\n");
	printf("******     1.play   *******\n");
	printf("******     0.exit   *******\n");
	printf("***************************\n");
}
void test()
{
	srand((unsigned int)time(NULL));
	//时间戳生成随机数字
	//后面布雷随机坐标的生成会用到 与rand连用
	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏");
			break;
		default:
			printf("选择错误 请重新选择\n");
			break;
		}


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

game.h头文件代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>//rand srand
#include<time.h>//time
#define easy_mode 10//布置雷的个数(类似于模式难度)
#define Row 9//9*9宫格
#define Col 9
#define Rows Row+2//上下左右各多一维 防止数组访问越界
#define Cols Col+2
//初始化棋盘
void init_board(char board[Rows][Cols], int ros, int cols, char set);
//打印棋盘
void print_board(char board[Rows][Cols], int row, int col);
//布置雷
void set_mine(char mine[Rows][Cols], int row, int col);
//排查雷
void find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col);

game.c源文件代码

#define _CRT_SECURE_NO_WARNINGS 1
#include"test(game)50.h"
void init_board(char board[Rows][Cols], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}
void print_board(char board[Rows][Cols], int row, int col)
{
	int i = 0;
	printf("-------扫雷--------\n");
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-------扫雷--------\n");
}
void set_mine(char mine[Rows][Cols], int row, int col)
{
	int count = easy_mode;
	while (count)
	{ 
		//生成随机下标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}
int get_mine_count(char mine[Rows][Cols], int x, int y)
{
	return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
		mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
		mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');

}
void chain_reflect(char mine[Rows][Cols], char show[Rows][Cols], int row, int col, int x, int y)
{
	int i = 0;
	int j = 0;
	if (x >= 1 && x <= col && y >= 1 && y <= row)//防止坐标越界
	{
		if (show[x][y] == '*')//判断是否被排查过
		{
			if (get_mine_count(mine, x, y) == 0)
			{
				show[x][y] = ' ';
				for (i = x - 1; i <= x + 1; i++)
				{
					for (j = y - 1; j <= y + 1; j++)
					{
						chain_reflect(mine, show, row, col, i, j);//递归传递
					}
				}
			}
			else
			{
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';//记下该坐标雷的个数
			}
		}
	}
	return;
}
void find_mine(char mine[Rows][Cols], char show[Rows][Cols], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	printf("请输入坐标");
	while (win < (row * col - easy_mode))
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= col && y >= 1 && y <= row)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标被排查过了 请重新输入");
				continue;
			}
			if (mine[x][y] == '1')
			{
				printf("很遗憾 你被炸死了\n");
				print_board(mine, row, col);
				break;
			}
			else
			{
				chain_reflect(mine, show,row , col, x, y);
				print_board(show, row, col);
				win++;
			}
		}
		else
		{
			printf("坐标输入非法 请重新输入\n");
		}
	}
	if (win == (row * col - easy_mode))
	{
		printf("恭喜你雷全部排查完 游戏胜利\n");
	}
}

接下来看看运行结果↓

308f7990b08f42f1b1dab85f91a8f9d2.png

  看看踩雷结束情况

66ef13ff7cb64759bbc7aded7538acf4.png

 

 

 

 

 

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值