c语言实现扫雷(递归实现自动展开)

要想实现扫雷的游戏我们要知道扫雷的规则是什么:

在一个99(初级),1616(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷。

我们这以9*9的矩阵为例,要想实现这个游戏我选择如果写在一个文件里面会显得十分混乱,所以我们分为三个文件来完成:
c语言实现扫雷(递归实现自动展开)

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"game.h"
#include<stdlib.h>
#include<time.h>
void game()
{
	char showboard[ROWS][COLS] = { 0 };
	char botboard[ROWS][COLS] = { 0 };
	initboard(showboard, COLS, ROWS, '*');
	initboard(botboard, COLS, ROWS, '0');
	setMine(botboard, COL, ROW);
	char botboard2[ROWS][COLS];
	copy(botboard2, botboard, COL, ROW);
	//displayboard(botboard, COL, ROW);     //这个显示随机地雷埋在哪个位置,调试的是候可以使用
	int x, y;
	int  n = 0;
	while (1)
	{
		displayboard(showboard, COL, ROW);
		printf("请输入坐标值:");
		scanf("%d%d", &x, &y);

		if (x > 0 && x <=COL  && y > 0 && y <= COL)
		{
			n++;
			int ret = findMine(botboard, x, y);
			if (ret == 10)
			{
				showboard[x][y] = '#';
				printf("你被炸死了!!\n");
				break;
			}
			if (n == COL * ROW - 10)
			{
				printf("你获得了胜利\n");
				break;
			}
			if (ret == 0)
			{
				fun(botboard2, showboard, botboard, x, y);
			}
			else
			{
				showboard[x][y] = ret + '0';
				continue;
			}
		}
		printf("坐标错误!\n");
	}
	displayboard(showboard, COL, ROW);
	//displayboard(botboard, COL, ROW);

}
void manu()                       //打印菜单
{
	printf("***************************\n");
	printf("***************************\n");
	printf("*******1 . play************\n");
	printf("*******2 . excite**********\n");
	printf("***************************\n");
	printf("***************************\n");
}
int main()
{
	int n;
	srand((unsigned)time(NULL));
	do
	{
		manu();
		printf("玩家请选择:");
		scanf("%d", &n);
		switch (n)
		{
		case 1:
			game();
			break;
		case 2:
			n = 0;
			break;
		default:
			n = 0;
			break;
		}

	} while (n);
}

在game()函数里设置了两个二维字符数组showboard()用来打印给玩家看到显示数组 和 botboard()用来储存雷的位置的数组,实际上我们下面函数在进行运算的时候使用的是botboard()来获得棋盘的信息。这里把数组设置成1111的数组但是我们只显示其中99的部分,原因下面分析findmine函数时会解析。

game.c:

#pragma once
#define COL 9
#define ROW 9
#define COLS 11
#define ROWS 11
#include<stdio.h>
#include<stdlib.h>
#include<time.h>


//初始化棋盘
void initboard(char board[ROWS][COLS], int col, int row, char set);

//展示棋盘
void displayboard(char board[ROWS][COLS], int col, int row);


//设置地雷
void setMine(char board[ROWS][COLS], int col, int row);

//排地雷
int findMine(char board[ROWS][COLS], int col, int row);

//快速排除周围没有一个地雷的点
void fun(char board2[ROWS][COLS], char board1[ROWS][COLS], char board[ROWS][COLS], int x, int y);

void copy(char board[ROWS][COLS], char board1[ROWS][COLS], int col, int row);

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"game.h"
void initboard(char board[ROWS][COLS], int col, int row, char set)
{
	int i = 0, j = 0;
	for (i = 0; i < col; i++)
	{
		for (j = 0; j < row; j++)
		{
			board[j][i] = set;
		}
	}

}

void displayboard(char board[ROWS][COLS], int col, int row)
{
	int i = 0, j = 0;
	for (i = 0; i <=col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <=col; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= row; j++)
		{
			printf("%c ", board[j][i]);
		}
		printf("\n");
	}
}


void setMine(char board[ROWS][COLS], int col, int row)
{
	int x, y;
	int n = 1;
	while (n<=10)
	{
		x = rand() % (col - 1) + 1;
		y = rand() % (row - 1) + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			n++;

		}
	}
}
int findMine(char board[ROWS][COLS], int x, int y)
{
	while (1)
	{
		if (board[x][y] == '1')
		{
			return 10;
		}
		else
		{
			return board[x - 1][y - 1] +
				board[x][y - 1] +
				board[x + 1][y - 1] +
				board[x - 1][y] +
				board[x + 1][y] +
				board[x - 1][y + 1] +
				board[x][y + 1] +
				board[x + 1][y + 1] - 8 * board[x][y];
		}
	}

}



void fun(char board2[ROWS][COLS],char board1[ROWS][COLS],char board[ROWS][COLS], int x, int y)                 //fun(botboard, showboard, botboard, x, y);
{
	int ret = findMine(board, x, y);
	if (ret == 0 && x > 0 && y > 0 && board2[x][y] != ' ')
	{
		board2[x][y] = ' ';
		fun(board2, board1, board, x - 1, y);
		fun(board2, board1, board, x + 1, y);
		fun(board2, board1, board, x, y + 1);
		fun(board2, board1,board, x, y - 1);
	}
	board1[x][y] = ret + '0';
}

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

game.c函数的解析:
initboard 和 displayboard
两个函数 一个是用来初始化字符数组 一个是将字符数组打印在屏幕上,玩家输入的是坐标,而我们要输出的是该坐标周围有几个雷。用来实际计算的数组botboard我们将所有元素设为’0’,地雷设为0这样我们在后面计算地雷数时会方便不少,showboard全部设为’*’。

setMine
用来设置地雷数,我们这里是藏十个地雷,调用函数rand()生成随机数来随机取横纵坐标,随机坐标的值赋值为‘1’,但只在9*9的范围里赋值。

findMine
这里是一个有返回值的函数,这个函数的目的是先进行判断如果输入的那点值正好为’1’就返回一个特定的值,如果不是就返回周围’1’的个数也就是炸弹数。由于字符在内存中是以ASCII码的形式存储所以炸弹数等于周围八个坐标对应的值减去8*‘0’。这里还可以解释为什么要将数组设成1111的大小,如果输入的坐标在最后一行/列或第一行/列那么他周围只有3或5个坐标,如果分情况讨论会过于麻烦,不如设成1111多出两行两列,最边缘的点周围也有八个点,而且由于setmine只在9*9的范围里埋地雷顾多出来的行和列不影响结果。

递归函数fun
程序到上面其实就可以正常运行了,但在玩的时候你会发现如果你输入的点得到0即周围八个点没有地雷,那你还要再把周围八个点的坐标一个一个输入游戏才能继续,如果输出是零能自动展开周围的点该多好呀!

void fun(char board2[ROWS][COLS],char board1[ROWS][COLS],char board[ROWS][COLS], int x, int y)                 
{
	int ret = findMine(board, x, y);
	if (ret == 0 && x > 0 && y > 0 && board2[x][y] != ' ')
	{
		board2[x][y] = ' ';
		fun(board2, board1, board, x - 1, y);
		fun(board2, board1, board, x + 1, y);
		fun(board2, board1, board, x, y + 1);
		fun(board2, board1,board, x, y - 1);
	}
	board1[x][y] = ret + '0';
}
void copy(char board[ROWS][COLS], char board1[ROWS][COLS],int col,int row)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = board1[i][j];
		}
	}
}
fun(botboard2, showboard, botboard, x, y);
char botboard2[ROWS][COLS];
	copy(botboard2, botboard, COL, ROW);

这里我们采用递归的方法这边我传进去了三个数组botboard2,botboard,showboard,由后两段代码我们可以知道其实botboard2和botboard是两个完全一样的数组但是地址不一样。
每一次递归我们先通过findmine函数的返回值得到ret,然后我们判断ret是否为0(即周围八个点是否都没有地雷) x > 0 && y > 0是判断是否到达边界防止递归越过9*9边界,board2[x][y] != ’ '判断字符串是否为我们已经检查过的点不在进入递归以防死循环(比如由于我们是向一个点的四个方向递归,第一个点右边的点进入下一次递归,第二层递归四个方向 向左边 的递归实际上又回到了第一层递归,但是board2[x][y] != ’ ‘的判断条件可以免除这种情况发生)。进入判断的第一件事就是将我们检查的点设成’ ‘,然后向该店的左右上下四个方向递归。
botboard2 作用就是将递归的点设成’ '防止死循环
botboard 作用是由于botboard2递归过的点值被修改会导致ret=finmine无法判断周围八个点是否为炸弹,故需要一个botboard2一模一样的数组来专门输入findmine函数
这就是递归函数的逻辑,这个函数是我自己想出来的,可能不是非常好但完成了目标,欢迎大家提出改进意见和自己的想法

上一篇:扫雷


下一篇:C语言扫雷简单版