扫雷游戏的C语言实现

1.设计思路

扫雷游戏要着重考虑以下几点:雷盘的初始化、埋雷、扫雷、输入一个坐标,旁边没有雷的地方全部变为空地。
我们设计两个二维数组,一个展示给玩家,一个则是程序员自己看到的真实埋雷情况。
而程序员自己的雷盘,以字符0和1区分,0表示此时没有雷,1表示次数为雷。
展示给玩家的雷盘,开始时全部都是以‘ * ’星号来展示,当玩家输入其中一个星号坐标时,若此时其周围有雷,则使用程序员的雷盘统计其周围雷的个数,若没有雷,则将其置为空,并且递归扩展空地。
为了便于控制游戏难度,我们使用宏来定义数组大小。扫雷时,为了统计一个区域周围雷的个数,我们需要遍历其周围8个区域,但是对于处于四个边的区域,我们其周围区域不足8个。比如玩家要玩一个9×9的雷盘,我们可以设计实际雷盘大小为11×11,打印时打印中间的9×9的部分。而在计算四边区域雷的个数时,在11×11的数组中,其四个边的雷均置为0,表示没有雷。这样在遍历时,9×9区域的四个边也可以按照遍历周围八个区域的方式来进行遍历。
因此,game.h文件主要包含以下内容

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

#define EASY_COUNT 10//盘上的雷的个数

#define ROW 9
#define COL 9

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

//游戏
void game();

//打印菜单
void menu();

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

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

//布置雷
void SetBoard(char board[ROWS][COLS], int row, int col,int count);//count为布置的雷的个数

//扫雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int count);

//统计一个区域周围雷的个数
int GetMineCount(char board[ROWS][COLS], int x, int y);

//递归实现扫雷的扩展
void Extend(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y);

//检验此时剩余的未检验区域的个数
int CheckWin(char show[ROWS][COLS], int row, int col, int count);

2.game()函数

void game()
{
    
    
	//创建对应的数组
	char mine[ROWS][COLS];//存放何处埋雷,何处不是雷
	char show[ROWS][COLS];//给用户展示的雷盘
	InitBoard(mine, ROWS, COLS,'0');//初始化时,所有的区域都不埋雷,不埋雷表示0,埋雷表示1
	InitBoard(show, ROWS, COLS, '*');//用户最开始看到的雷盘均为 *,随着扫雷过程,* 减少

	//打印雷盘
	DisplayBoard(show, ROW, COL);//只打印中间的部分

	//埋雷
	SetBoard(mine, ROW, COL, EASY_COUNT);

	//排雷
	FindMine(mine, show, ROW, COL,EASY_COUNT);

}

3.二维数组的初始化

由于有两个二维数组需要初始化,因此我们不必用两个函数来进行初始化,而是使用一个函数。


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;
		}
	}
}

为了便于对不同数组的初始化,传参时设置一个 char set参数,表示传进去的字符。因此们对于两个数组的初始化分别如下:

InitBoard(mine, ROWS, COLS,'0');//初始化时,所有的区域都不埋雷,不埋雷表示0,埋雷表示1
	InitBoard(show, ROWS, COLS, '*');//用户最开始看到的雷盘均为 *,随着扫雷过程,* 减少

4.雷盘的打印

我们希望对于雷盘的打印效果如下图所示:
在这里插入图片描述
为了便于扫雷时玩家能够很好区分横纵坐标,我们需要标注好行号与列号,其代码如下

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    
    
    printf("----------------扫雷游戏----------------\n");
	printf("\n");
	printf("    ");
	for (int i = 1; i <= row; i++)
	{
    
    
		printf("  %d ", i);
	}
	printf("\n\n");
	for (int i = 1; i <= row; i++)
	{
    
    
		printf(" %d   ", i);
		for (int j = 1; j <= col; j++)
		{
    
    
			printf(" %c ", board[i][j]);
			if (j <= col - 1)
			{
    
    
				printf("|");  //最后一行不打印 |
			}
		}
		printf("\n");
		printf("     ");
		for (int j = 1; j <= col; j++)
		{
    
    
			if (i <= col - 1)
			{
    
    
				printf("---"); //最下面一行不打印---
			}
			if (i <= col - 1 && j <= col - 1)
			{
    
    
				printf("|"); //最后一行和最后一列不打印 |
			}
		}
		printf("\n");
	}
}

5.埋雷

埋雷需要每一盘随机进行,因此我们将横纵坐标都置为随机数。为了防止开始时每一盘的雷一样,我们需要在进入main函数前加入如下一句代码

srand((unsigned int)time(NULL));

从而保证埋雷是随机的。
该模块代码如下:

void SetBoard(char board[ROWS][COLS], int row, int col, int count)
{
    
    
	while (count) //当count等于0时跳出循环,表示此时埋雷完毕
	{
    
    
		int x = rand() % row +1;
		int y = rand() % col +1;
		if (board[x][y] == '0')//若此处未埋雷,则布置,否则重新布置
		{
    
    
			board[x][y] = '1';
			count--; //每埋一个雷,count--
		}
	}
}

**

6.统计某个区域周围的个数

**
遍历周边八个区域,代码如下:

int GetMineCount(char board[ROWS][COLS], int x, int y)
{
    
    
	int count = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
    
    
		for (int j = y - 1; j <= y + 1; j++)
		{
    
    
			if (board[i][j] == '1')
				count++;
		}
	}
	return count;
}

7.空地的扩展

空地的扩展为游戏设计的难点,但当我们了解游戏规则后,不难想出用递归来实现。
当输入坐标不是雷,且其周围八个区域均不是雷的时候,需要向此地置为空格,并向外扩展。此时需要检查其周围的区域有没有被访问国,若访问过则跳过,若没有访问则进行访问。其代码如下:

//递归实现扫雷的扩展
void Extend(char show[ROWS][COLS], char mine[ROWS][COLS],int x, int y)
{
    
    
	if (!GetMineCount(mine, x, y))//如果周围的个数为0,则对其周围8个区域进行检查
	{
    
    
		show[x][y] = ' '; //将周围雷的个数为0的区域置为空
		for (int i = x - 1; i <= x + 1; i++)
		{
    
    
			for (int j = y - 1; j <= y + 1; j++)
			{
    
    
				//两个for循环对该区域周围8个区域进行检查
				if (mine[i][j] == '0' && show[i][j]=='*' && (i >= 1 && i <= ROW) && (j >= 1 && j <= COL))//如果没有被遍历,且此时检查的区域不是雷,则进入递归
				{
    
    
							Extend(show, mine, i, j);
				}
			}
		}
	}
	else
	{
    
    
		show[x][y] = GetMineCount(mine, x, y) + '0';//若周围雷的个数不为0,则标出该区域周围雷的个数
	}
}

8.排雷

排雷是本游戏设计的核心。
如果所输入坐标刚好是雷,则告知玩家被炸死,跳出游戏,并向玩家展示埋雷的真实情况。
如果不是雷,则展示该区域周围的雷的个数。当该区域周围的雷的个数为0时,则置为空格,并进行空地的扩展。知道周围不是0为止。
当剩余的‘*’星号刚好等于埋雷的个数时,证明玩家已经排雷成功,向玩家告知并跳出游戏
其代码如下

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int count)
{
    
    
	int x = 0;
	int y = 0;
	int win = row*col;//win为此时未检查的区域的个数,初始时为row * col
	while (win > count) //若未检查的个数大于雷的个数,则进入循环
	{
    
    
		printf("请输入要排查的坐标:>");
		scanf("%d%d", &x, &y);
		if ((x >= 1 && x <= row) && (y >= 1 && y <= col))//检查坐标合法性,若合法,则进入排查
		{
    
    
			if (mine[x][y] == '1')
			{
    
    
				//是雷
				printf("很遗憾,你被炸死了!\n");
				DisplayBoard(mine, row, col);
				break;
			}
			else if(mine[x][y] == '0')
			{
    
    
				//如果此时不是雷,则递归计算
				Extend(show, mine, x, y);
				DisplayBoard(show, row, col);
				win = CheckWin(show, row, col, count);//计算未检查个数,此数决定了是否进入循环
			}
		}
		else
		{
    
    
			printf("坐标非法,请重新输入"); //若坐标不合法,则重新输入
		}
	}
	if (win == count) //雷的个数等于未排查区域的个数,代表已经排查完毕,未排查区域即为雷
	{
    
    
		printf("恭喜你!排雷成功!\n");
	}
}

9.test()函数

最后的完善就是给游戏设计一个简单的菜单,玩家能根据需求来进行进入游戏或者退出游戏。此部分较为简单,菜单设计如代码如下:

void menu()
{
    
    
	printf("----------------------------------------\n");
	printf("-----------------1.play-----------------\n");
	printf("-----------------2.exit-----------------\n");
	printf("----------------------------------------\n");
}

test()函数代码如下:


int main()
{
    
    
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
    
    
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
    
    
			case 0:
				printf("退出游戏\n");
				break;
			case 1:
				game();//扫雷游戏的实现
				break;
			default:
				printf("选择错误,请重新选择!\n");
				break;
		}


	} while (input);

	return 0;
}

10.小结

至此,扫雷游戏设计完成。此设计依然可以进行进一步改进,如第一次所点区域不能是雷,感兴趣的可以自行尝试。
扫雷游戏是对二维数组的一次应用,需要掌握对二维数组的遍历。同时,其中空地的扩展,也涉及到了递归条件的判断,需要掌握递归的方法。

猜你喜欢

转载自blog.csdn.net/Birdyxh/article/details/113338164