C语言实现扫雷小游戏(下)(用递归实现了展开)

  穷且益坚,不坠青云之志。
  上篇博客实现了鸡肋的扫雷小游戏,不能实现雷盘(场)的展开,体验不是很好,这篇博客对其进行优化了,实现了展开功能。



一、新的头文件

  增加了一些新的函数声明。

#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 5

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

void test(void);
void menu(void);
void game(void);

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetCount(char mine[ROWS][COLS], int x, int y);
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
int Win(char show[ROWS][COLS], int row, int col);

二、游戏框架

  游戏基本实现框架没有任何改动。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"

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

//游戏执行测试框架
void test (void)
{
	int input = 0;

	do {
		menu();//系统打印游戏菜单,调用menu函数

		printf("请选择:>>>");
		scanf("%d", &input);

		switch (input)//switch开关语句,作用显而易见
		{
		case 1:
			game();//调用game函数
			break;
		case 2:
			printf("退出游戏\n");
			input = 0;
			break;
		default:
			printf("输入值无效\n");
			break;
		}

	} while (input);
}

//菜单打印函数
void menu(void)
{
	printf("************************************\n");
	printf("*****1.开始游戏     0.退出游戏******\n");
	printf("************************************\n");
}

//游戏主函数
void game(void)
{
	//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//第二步,初始化
	InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。数组一开始需要有能分辨的初始化元素。
	InitBoard(show, ROWS, COLS, '*');
	//第三步,布置雷
	SetMine(mine, ROW, COL);
	DisplayBoard(mine, ROW, COL);//测试需要,玩家操作时注释掉
	DisplayBoard(show, ROW, COL);
	//第四步,扫雷
	FindMine(mine, show, ROW, COL);

}

三、功能函数

  功能函数在保留了上篇博客中提到的三个函数InitBoardDisplayBoardSetMine外,又增加了三个新的函数GetCountOpenMineWin,并对FindMine函数做出了更改。

  先把三个未更改的函数放在这里,再介绍FindMine函数的更改,从而引出三个新增函数吧。


1、未更改的部分

#include "game.h"

//初始化函数
void InitBoard(char board[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++)
		{
			board[i][j] = set;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
		}
	}
}

//打印雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;

	printf("\n");//打印个换行符分割下,不是必要

	//打印列号
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	//打印行号及雷盘
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);

		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");//打印个换行符分割下,不是必要
}

//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//雷的个数

	srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

	while (count)//控制雷个数
	{
		int x = rand() % row + 1;//x会在1-9之间随机生成一个值
		int y = rand() % col + 1;//y会在1-9之间随机生成一个值

		if (board[x][y] == '0')//避免重复放雷
		{
			board[x][y] = '1';//雷的放置用字符'1'表示
			count--;
		}
	}
}

2、更改后的FindMine函数

  这个主要的更改逻辑是这样,目的不是要实现当玩家所选坐标周围的八个坐标不存在雷也就是周围八个坐标的个数和为0的时候对雷盘进行展开吗?

  我们要把计算玩家周围雷的总数的这一个算法单独封装成一个单独的函数。从而引出

if(玩家周围雷的总数为0)

对雷盘进行展开,展开的逻辑是这样,对周围的八个坐标进行判断是否满足其所在周围的八个坐标雷的总数为0,如果是的,再次进行展开,如果不是就让此处坐标格子显示其周围雷的个数。有点绕,不知道讲明白没。

else if(玩家周围雷的总数不为0)

那么就进入不了递归展开函数的判断条件,直接让此处的坐标格子显示周围雷的个数。

  胜负判断,如果玩家棋盘上未揭开的’*'的个数是雷的总数,那么玩家胜利,退出循环。

//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//row和col传递过来的是ROW和COL,这里用x,y来控制数组下标。
	int x = 0;
	int y = 0;

	while (1)
	{
		printf("请输入排查雷的坐标:>>>");
		scanf("%d %d", &x, &y);
		//玩家输入坐标后,面临两种情况,第一种,坐标合法,第二种坐标非法。
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//玩家输入的坐标埋放了雷,那么对不起,打印雷分布情况,游戏结束
			if (mine[x][y] == '1')
			{
				printf("\n很不幸,您被炸死了!!!\n\n雷的分布情况如下:\n");
				DisplayBoard(mine, row, col);
				printf("-----------游戏结束----------\n\n");
				break;
			}
			//如果玩家所选坐标安全,那么面临有两种情况。
			//情况一:玩家周围有雷,直接显示周围雷的个数。
			//情况二:玩家周围八个坐标均没雷,那么进行递归展开,向四面八方展开,直至遇到情况一。
			else if (mine[x][y] == '0')
			{
				show[x][y] = GetCount(mine, x, y) + '0';//先将此处的赋值为此处坐标周围雷的个数,如果是0进入展开。
                //其实这个放到后面也可以,但是会有一些差别,放在后面雷盘上会出现周围有0个雷的字样,放在前面,0会被下一层的空格顶掉。
				OpenMine(mine, show, x, y);//情况一周围雷的个数是0展开直到每个坐标都遇到了周围8个坐标雷的个数不再是0。
                                           //情况二周围雷的个数不是0,不满足展开函数条件跳过此函数。
				DisplayBoard(show, row, col);//更新此时的雷盘。

				if (Win(show, ROW, COL) == EASY_COUNT)//判断此时雷盘剩余的*数目,如果等于雷数,则玩家排雷成功。
				{
					printf("恭喜你成功排雷,太棒了,太棒了,送你一朵小红花!!!\n");
                    printf("雷的分布情况如下:\n");
					DisplayBoard(mine, row, col);
					break;
				}
			}
		}
		//坐标非法
		else
		{
			printf("非法输入!!!\n");
		}
	}
}

3、GetCount

//计算周围八个格子中雷的个数
int GetCount(char mine[ROWS][COLS], int x, int y)
{
	int count= 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';

	return count;
}

4、OpenMine

//递归展开(套娃行为,心累哇...)
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (GetCount(mine, x, y) == 0)
	{
		show[x][y] = ' ';//先将周围的8个格子全变成空格,在进行每个格子的判断,递归,好好理解下
		if ((x - 1) > 0 && (y - 1) > 0 && (show[x - 1][y - 1] == '*'))
			OpenMine(mine, show, x - 1, y - 1);
		if ((x - 1) > 0 && (y) > 0 && (show[x - 1][y] == '*'))
			OpenMine(mine, show, x - 1, y);
		if ((x - 1) > 0 && (y + 1) > 0 && (show[x - 1][y + 1] == '*'))
			OpenMine(mine, show, x - 1, y + 1);
		if ((x) > 0 && (y - 1) > 0 && (show[x][y - 1] == '*'))
			OpenMine(mine, show, x, y - 1);
		if ((x) > 0 && (y + 1) > 0 && (show[x][y + 1] == '*'))
			OpenMine(mine, show, x, y + 1);
		if ((x + 1) > 0 && (y - 1) > 0 && (show[x + 1][y - 1] == '*'))
			OpenMine(mine, show, x + 1, y - 1);
		if ((x + 1) > 0 && (y) > 0 && (show[x + 1][y] == '*'))
			OpenMine(mine, show, x + 1, y);
		if ((x + 1) > 0 && (y + 1) > 0 && (show[x + 1][y + 1] == '*'))
			OpenMine(mine, show, x + 1, y + 1);
	}
	else //如果周围的8个格子雷的个数不再为0,就显示周围雷的个数。
	{
		show[x][y] = GetCount(mine, x, y) + '0';
	}
}

5、Win

//胜利计算
//玩家棋盘上符号*个数为雷数就扫雷成功了
int Win(char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0, count = 0;
	for (i = 1; i <= row; i++)//注意!注意!注意!这个地方i和j需要从第二行第二列开始记录到ROW和COL停止
	{
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}

四、完整程序

#define _CRT_SECURE_NO_WARNINGS 1

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 5

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

void test(void);
void menu(void);
void game(void);

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetCount(char mine[ROWS][COLS], int x, int y);
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
int Win(char show[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include "game.h"

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

//游戏执行测试框架
void test (void)
{
	int input = 0;

	do {
		menu();//系统打印游戏菜单,调用menu函数

		printf("请选择:>>>");
		scanf("%d", &input);

		switch (input)//switch开关语句,作用显而易见
		{
		case 1:
			game();//调用game函数
			break;
		case 2:
			printf("退出游戏\n");
			input = 0;
			break;
		default:
			printf("输入值无效\n");
			break;
		}

	} while (input);
}

//菜单打印函数
void menu(void)
{
	printf("************************************\n");
	printf("*****1.开始游戏     0.退出游戏******\n");
	printf("************************************\n");
}

//游戏主函数
void game(void)
{
	//第一步,创建两个数组,一个是布盘数组,一个是用户数组,两个数组大小相同,属于叠加的双层结构。
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//第二步,初始化
	InitBoard(mine, ROWS, COLS, '0');//初始化两个数组。数组一开始需要有能分辨的初始化元素。
	InitBoard(show, ROWS, COLS, '*');
	//第三步,布置雷
	SetMine(mine, ROW, COL);
	DisplayBoard(mine, ROW, COL);//测试需要,玩家操作时注释掉
	DisplayBoard(show, ROW, COL);
	//第四步,扫雷
	FindMine(mine, show, ROW, COL);
}
#include "game.h"

//初始化函数
void InitBoard(char board[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++)
		{
			board[i][j] = set;//让布雷数组全部元素为字符'0',玩家数组全部为字符'*'
		}
	}
}

//打印雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;

	printf("\n");//打印个换行符分割下,不是必要

	//打印列号
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	//打印行号及雷盘
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);

		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");//打印个换行符分割下,不是必要
}

//布置雷盘
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//雷的个数

	srand((unsigned int)time(NULL));//使用系统时间作为随机值的种子

	while (count)//控制雷个数
	{
		int x = rand() % row + 1;//x会在1-9之间随机生成一个值
		int y = rand() % col + 1;//y会在1-9之间随机生成一个值

		if (board[x][y] == '0')//避免重复放雷
		{
			board[x][y] = '1';//雷的放置用字符'1'表示
			count--;
		}
	}
}

//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//row和col传递过来的是ROW和COL,这里用x,y来控制数组下标。
	int x = 0;
	int y = 0;

	while (1)
	{
		printf("请输入排查雷的坐标:>>>");
		scanf("%d %d", &x, &y);
		//玩家输入坐标后,面临两种情况,第一种,坐标合法,第二种坐标非法。
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//玩家输入的坐标埋放了雷,那么对不起,打印雷分布情况,游戏结束
			if (mine[x][y] == '1')
			{
				printf("\n很不幸,您被炸死了!!!\n\n雷的分布情况如下:\n");
				DisplayBoard(mine, row, col);
				printf("-----------游戏结束----------\n\n");
				break;
			}
			//如果玩家所选坐标安全,那么面临有两种情况。
			//情况一:玩家周围有雷,直接显示周围雷的个数。
			//情况二:玩家周围八个坐标均没雷,那么进行递归展开,向四面八方展开,直至遇到情况一。
			else if (mine[x][y] == '0')
			{
				show[x][y] = GetCount(mine, x, y) + '0';
				OpenMine(mine, show, x, y);//情况一不满足展开函数条件跳过此函数,情况二展开直到每个坐标都遇到了情况一。
				DisplayBoard(show, row, col);//更新此时的雷盘。
				if (Win(show, ROW, COL) == EASY_COUNT)//判断此时雷盘剩余的*数目,如果等于雷数,则玩家排雷成功。
				{
					printf("恭喜你成功排雷,太棒了,太棒了,送你一朵小红花!!!\n");
                    printf("雷的分布情况如下:\n");
					DisplayBoard(mine, row, col);
					break;
				}
			}
		}
		//坐标非法
		else
		{
			printf("非法输入!!!\n");
		}
	}
}
//计算周围八个格子中雷的个数
int GetCount(char mine[ROWS][COLS], int x, int y)
{
	int count= 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';

	return count;
}
//递归展开(套娃行为,心累哇...)
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	if (GetCount(mine, x, y) == 0)
	{
		show[x][y] = ' ';
		if ((x - 1) > 0 && (y - 1) > 0 && (show[x - 1][y - 1] == '*'))
			OpenMine(mine, show, x - 1, y - 1);
		if ((x - 1) > 0 && (y) > 0 && (show[x - 1][y] == '*'))
			OpenMine(mine, show, x - 1, y);
		if ((x - 1) > 0 && (y + 1) > 0 && (show[x - 1][y + 1] == '*'))
			OpenMine(mine, show, x - 1, y + 1);
		if ((x) > 0 && (y - 1) > 0 && (show[x][y - 1] == '*'))
			OpenMine(mine, show, x, y - 1);
		if ((x) > 0 && (y + 1) > 0 && (show[x][y + 1] == '*'))
			OpenMine(mine, show, x, y + 1);
		if ((x + 1) > 0 && (y - 1) > 0 && (show[x + 1][y - 1] == '*'))
			OpenMine(mine, show, x + 1, y - 1);
		if ((x + 1) > 0 && (y) > 0 && (show[x + 1][y] == '*'))
			OpenMine(mine, show, x + 1, y);
		if ((x + 1) > 0 && (y + 1) > 0 && (show[x + 1][y + 1] == '*'))
			OpenMine(mine, show, x + 1, y + 1);
	}
	else 
	{
		show[x][y] = GetCount(mine, x, y) + '0';
	}
}
//胜利计算
//玩家棋盘上符号*个数为雷数就扫雷成功了
int Win(char show[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0, count = 0;
	for (i = 1; i <= row; i++)//注意!注意!注意!这个地方i和j需要从第二行第二列开始记录到ROW和COL停止
	{
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*')
			{
				count++;
			}
		}
	}
	return count;
}

五、游戏测试

  将EASY_COUNT改成5进行测试。

************************************
*****1.开始游戏        0.退出游戏******
************************************
请选择:>>>1

0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 1 0
4 0 0 0 0 0 0 0 1 0
5 0 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 0
8 0 0 0 1 0 0 1 0 0
9 0 0 0 0 0 1 0 0 0


0 1 2 3 4 5 6 7 8 9
1 * * * * * * * * *
2 * * * * * * * * *
3 * * * * * * * * *
4 * * * * * * * * *
5 * * * * * * * * *
6 * * * * * * * * *
7 * * * * * * * * *
8 * * * * * * * * *
9 * * * * * * * * *

请输入排查雷的坐标:>>>2 2

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * * * * 1
9     1 * * * * 1

请输入排查雷的坐标:>>>9 4

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * * * * 1
9     1 1 * * * 1

请输入排查雷的坐标:>>>9 5

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * * * * 1
9     1 1 2 * * 1

请输入排查雷的坐标:>>>8 5

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * 2 * * 1
9     1 1 2 * * 1

请输入排查雷的坐标:>>>8 6

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * 2 2 * 1
9     1 1 2 * * 1

请输入排查雷的坐标:>>>9 7

0 1 2 3 4 5 6 7 8 9
1
2             1 1 1
3             2 * 2
4             2 * 2
5             1 1 1
6
7     1 1 1 1 1 1
8     1 * 2 2 * 1
9     1 1 2 * 2 1

恭喜你成功排雷,太棒了,太棒了,送你一朵小红花!!!

0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 1 0
4 0 0 0 0 0 0 0 1 0
5 0 0 0 0 0 0 0 0 0
6 0 0 0 0 0 0 0 0 0
7 0 0 0 0 0 0 0 0 0
8 0 0 0 1 0 0 1 0 0
9 0 0 0 0 0 1 0 0 0

************************************
*****1.开始游戏        0.退出游戏******
************************************
请选择:>>>1

0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 1 0 0
6 0 0 1 0 1 0 0 0 0
7 0 0 1 0 0 0 1 0 0
8 0 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 0 0


0 1 2 3 4 5 6 7 8 9
1 * * * * * * * * *
2 * * * * * * * * *
3 * * * * * * * * *
4 * * * * * * * * *
5 * * * * * * * * *
6 * * * * * * * * *
7 * * * * * * * * *
8 * * * * * * * * *
9 * * * * * * * * *

请输入排查雷的坐标:>>>6 3

很不幸,您被炸死了!!!

雷的分布情况如下:

0 1 2 3 4 5 6 7 8 9
1 0 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0
3 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 1 0 0
6 0 0 1 0 1 0 0 0 0
7 0 0 1 0 0 0 1 0 0
8 0 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 0 0

-----------游戏结束----------

************************************
*****1.开始游戏        0.退出游戏******
************************************
请选择:>>>

六、写在后面

  emmmmm,整体来说,扫雷小游戏游戏算是基本实现了吧,写了好久,参考了好些资料,对于我这个新手来说太难了,艰难的写完所有程序,测试了应该没问题,一些算法很细节,我也是调试了好久。。。。

  敬请各位大佬检阅、批评和指正,希望可以奖励我个一键三连以此鼓励,非常感谢!!!非常感谢!!!非常感谢!!!

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小孙同学Coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值