C语言入门笔记 小游戏开发 【扫雷】

目录

 

一、游戏规则

二、项目结构

三、整体逻辑和局部函数分析

void game()

void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)

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

SetMine(mine, ROW, COL)

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)

int get_mine_count(char mine[ROWS][COLS], int x, int y)

void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)

int NoMineCount(char show[ROWS][COLS],int row,int col)

四、个人心得

五、项目源码分享


一、游戏规则

1、扫雷就是要把所有非地雷的格子揭开即胜利;踩到地雷格子就算失败。

2、游戏主区域由很多个方格组成。

选择一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷;如果点开的格子为空白格,即其周围有0颗雷,则其周围格子自动打开;
3、1代表1的上下左右及斜角合计有一颗雷,依次轮推,2则有2颗,3则有3颗。

4、胜利条件为打开除了雷以外的全部方框。

二、项目结构

和三子棋一样,一共有三个文件:

分别是

主文件:game.cpp//写函数的具体实现

头文件:game.h//放置define常量和函数声明

测试文件:test.cpp//放入对应头文件后,引用函数即可

三、整体逻辑和局部函数分析

游戏运行整体逻辑、test函数和菜单函数请参照:
C语言入门笔记 小游戏开发 【三子棋】

这里我们直接讲扫雷的逻辑函数——game()。

void game()

void game()
{
	//雷的信息存储
	//1.布置好的雷的信息
	char mine[ROWS][COLS] = { 0 };
	//2.排查出的雷的信息
	char show[ROWS][COLS] = { 0 };
	//初始化雷棋盘
	InitBoard(mine, ROWS, COLS, '0');
	//初始化玩家棋盘
	InitBoard(show, ROWS, COLS, '*');
	//打印棋盘
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	//查找雷,mine里找信息,然后传到show数组里
	FindMine(mine, show, ROW, COL);
}

 对于扫雷这个游戏,我们首先需要:

创建一个二维数组,布置好雷的信息。

然而, 如果只构造这一个数组,不管是存储,还是修改数据,都很不方便,

所以这里我们构造一个雷数组,一个玩家数组。

通过对雷数组的判定,再传递信息给玩家数组,最后展示玩家数组即可。

假如整个区域是9×9,

那么对于边界雷区的判定不太方便。

所以这里我们设置总的数组大小为11×11,

最外一圈全部设置为无雷,

最后展示给玩家的为9×9的数组即可。

创建好雷数组、玩家数组两个数组后,我们需要用到一个初始化雷棋盘的函数。

void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)

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

至于set,这是一个字符型变量,用于告诉我们将棋盘初始化成什么样子。

为了区分,

我们将雷棋盘一开始全放0,

将玩家棋盘一开始全放字符‘*’。

然后打印棋盘:

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

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; 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");
	}
}

因为我们只需要展示棋盘的内圈,

所以传入的参数是行列均为9,

for循环从1开始即可。

然后我们需要布置雷:

SetMine(mine, ROW, COL)

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int number = EASY_COUNT;
	while (number)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			number--;
		}
		
	}
}

这里还是用到了随机数函数,

随机将10个0置为1。

然后是本游戏的核心函数:扫雷函数。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	//合法性判断
	while (win<row*col - EASY_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//不是雷,统计雷
			{
				//计算x,y坐标周围有几个雷
				NoMine(mine, show, x, y);
				DisplayBoard(show, row, col);
				win = NoMineCount(show, row, col);
				printf("已打开区域:%d\n", win);
			}

		}
		else
		{
			printf("输入坐标非法,请重新输入!\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功!\n");
	}
	
}

首先进行合法性判断,

然后进入一个while循环。

我们设置一个win变量,用来记录已经扫过的安全区。

这里循环结束的条件是:

1、已扫安全区大于等于全部安全区;

2、扫到了雷。

对于不是雷的区域,

我们首先写一个函数来判断他周围有多少颗雷。

int get_mine_count(char mine[ROWS][COLS], int x, int y)

int get_mine_count(char mine[ROWS][COLS], int x, int y)//因为字符0和字符1的ASCII码之间相差1,所以可以加起来再减去8个0的ASCII码和即可。
{
	    return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] - 8 * '0';
}

这个函数很巧妙,避免了对每个格子的遍历,

直接返回周边八个格子的ASCII码减去0的ASCII码,因为数字的ASCII码是连续存放的,

举个例子,

该格子的上面不是雷,那么放的就是0,‘0’-‘0’=0;

如果上面是雷,那么放的就是1,‘1’-‘0’=1

巧妙地返回了整型。

接下来,我们写一个展开无雷区函数:

void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)

void NoMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
	int sum = get_mine_count(mine,x,y);
	if (sum == 0)
	{
		show[x][y] = ' ';
		if ((x - 1) > 0 && (y - 1) > 0 && (show[x - 1][y - 1] == '*'))
			NoMine(mine, show, x - 1, y - 1);
		if ((x - 1) > 0 && (y) > 0 && (show[x - 1][y] == '*'))
			NoMine(mine, show, x - 1, y);
		if ((x - 1) > 0 && (y + 1) > 0 && (show[x - 1][y + 1] == '*'))
			NoMine(mine, show, x - 1, y + 1);
		if ((x) > 0 && (y - 1) > 0 && (show[x][y - 1] == '*'))
			NoMine(mine, show, x, y - 1);
		if ((x) > 0 && (y + 1) > 0 && (show[x][y + 1] == '*'))
			NoMine(mine, show, x, y + 1);
		if ((x + 1) > 0 && (y - 1) > 0 && (show[x + 1][y - 1] == '*'))
			NoMine(mine, show, x + 1, y - 1);
		if ((x + 1) > 0 && (y) > 0 && (show[x + 1][y] == '*'))
			NoMine(mine, show, x + 1, y);
		if ((x + 1) > 0 && (y + 1) > 0 && (show[x + 1][y + 1] == '*'))
			NoMine(mine, show, x + 1, y + 1);
	}
	else
		show[x][y] = sum + '0';
}

这个函数用到了递归思想。

首先用返回雷数函数,判断一下自己周围是否有雷区。

如果有,那么就把自己的值设置为‘0’+雷数,

设置完以后跳出该函数,进行下一步;

如果没有雷区,那么首先将自己的值设置为‘ ’,也就是一个空格符,

然后依次递归他周围的八个格子,

直到成为了周边有雷区的格子为止。

遍历完成后,再次调用展示棋盘函数,并计算win的值。

这里win的值我用了依次遍历法。

int NoMineCount(char show[ROWS][COLS],int row,int col)

int NoMineCount(char show[ROWS][COLS],int row,int col)
{
	int i = 0;
	int count = 0;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] != '*')
			{
				count++;
			}
				
		}
	}
	return count;
}

这个办法挺笨的,就是依次判断展示棋盘的字符是否是‘*’。

不过目前没有想到更好的办法,暂时是最优解法,并且时间复杂度也不高。

接下来,会继续进入while循环,

直到游戏结束的两个标志中的一个成立。

最后给出程序运行截图:

四、个人心得

写的第二个游戏项目了,这个项目里有几个点我觉得非常精彩:

首先是用两个棋盘,一个雷区,一个玩家区,通过对雷区的判断修改玩家区的内容,解决了传值和改值不方便这两大问题。

第二是在我们需要的棋盘外再加一圈。这使边界数组元素和中间数组元素可以进行统一判雷。

第三是使用递归的思想来开辟无雷区。

这个项目真的很值得去写,你可以收获递归算法思想和一些关于设计游戏地图坐标的思想。

五、项目源码分享

git地址:https://github.com/kukeoo/Minesweeper.git

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页