yo!这里是扫雷小游戏

目录

前言

模块介绍

测试模块

1.测试文件

功能实现模块

1.头文件

2.函数实现文件

完整测试

后记


前言

       《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。---百度百科

       当我们学完c语言的基础入门知识,包括数组,循环,递归等,可以尝试写代码实现扫雷这款家喻户晓的小游戏,如果不太理解游戏的玩法,可自行百度以及寻找此游戏熟悉一下,这里就不教大家玩法了,学会的小伙伴们,快来跟着我实现它吧。

模块介绍

  • 测试模块

1.测试文件

       在整个编写开始前,建议可以先写好测试文件,写完一个功能函数测试一个函数,保证思路的顺利展开,否则,若一下全写完再测试,如果出现问题,就不清楚错从何处来,会影响到自己的思路。

       主函数使用了一个do while循环以及嵌套一个switch语句,可以保证游戏可以在ta的选择下一直进行。

代码:

#include "game_sl.h"

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();   //菜单
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();   //博弈
			break;
		case 0:
			printf("退出成功\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);

	return 0;
}

  • 功能实现模块

1.头文件

       在测试文件中,我们可以看到,主函数主要包括菜单,博弈两部分,博弈中包括初始化雷盘、打印雷盘、布置雷、统计某坐标周围雷数等功能模块。

       通过两个预处理命令#define ROW 9、#define COL 9来决定雷盘的大小,此外,还可以看到两个预处理#define ROWS ROW+2、#define COLS COL+2,必要时会将雷盘的合法坐标多设置两行两列(相当于多了一圈格子),以减少判断坐标不合法的代码或者在访问元素时会越界,而MINE_COUNT则是一局游戏中雷的数目,以此来设置游戏的难度。

代码:

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

#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE_COUNT 10

//菜单
void menu();
//初始化雷盘
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char ch);
//打印雷盘
void PrintBoard(char arr[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char arr[ROWS][COLS], int row, int col);
//统计某坐标周围雷数
int AroundMine_count(char mine[ROWS][COLS], int x, int y);
//四周扩散
void SpreadAround(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col);
//玩家是否避雷成功
int is_Win(char check[ROWS][COLS], int row, int col);
//玩家排雷
void PlayerMove(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col);
//博弈
void game();

2.函数实现文件

1)菜单

       菜单的制作依旧很简单,是读者展示自己个性的地方,可以根据自己对游戏的理解装点菜单界面,让ta可以对游戏的功能一目了然。

代码: 

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

测试:

2)初始化雷盘

       初始化雷盘是每局游戏开始前必须要经过的处理,很明显使用二维数组去实现,但是一开始把它初始化成什么样呢?

       如果像三子棋那样用空格符去初始化,ta选择一个坐标就用其他符号去替换空格符,可是这样又怎么判断坐标是否会有雷呢;一方面要初始化所有的盘格是同一个字符,一方面又要隐藏特殊雷子符号在其中,很明显一个二维数组存不下;所以说扫雷小游戏中雷盘我们需要两个二维数组实现,这一点是比较难以想到的地方。

       将雷盘分为两个部分,一个mine盘,使用字符1表示此坐标有雷,字符0表示无雷,另一个是check盘,使用字符'*'表示雷盘的原始状态,使用数字字符表示此坐标周围的雷数,可以将两个棋盘想象成上下两个平行的盘,mine盘是下面的隐藏盘,check盘是上面展示给ta的盘,两个一重叠,可以完美的模拟出扫雷游戏中的雷盘。

       这样一说,对如何实现扫雷是不是有了进一步的认识,笔者在下文的介绍中将mine盘的元素初始化为'0',而check盘元素初始化为'*',InitBoard函数可以满足两个数组的初始化,因为初始化的元素可以随参数传进去。

代码:

void InitBoard(char arr[ROWS][COLS], int rows, int cols,char ch)
{
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = ch;
		}
	}
}

3)打印雷盘

       雷盘的打印思路可以参考上一篇文章---三子棋小游戏中棋盘的制作,不过这次笔者在原来的基础上加上了边框,也建议为每行每列加上序号,以便于ta对坐标的选择,读者也可以仿照如图制作更美观的雷盘。

代码:

void PrintBoard(char arr[ROWS][COLS], int row, int col)
{
	int i = 0, j = 0;

	printf("\n  |");
	for (i = 1; i <= row; i++)  //列坐标
		printf(" %d  ", i);
	printf("\n—|");
	for (i = 1; i <= row; i++)  //上边框
		printf("——", i);
	printf("\n");

	//表格的打印
	for (i = 1; i <= row; i++)
	{
		printf("%d |",i);
		for (j = 1; j <= col; j++)
		{
			printf(" %c |",arr[i][j]);
		}
		printf("\n");

		printf("  |");
		for (j = 1; j <= col; j++)
		{
			printf("---|");
		}
		printf("\n");
	}
	printf("\n");
}

测试(左为mine盘,右为check盘):

   

4)布置雷

       布置雷也是每局游戏开始前的准备之一,不过需要电脑进行随机的选取坐标安放雷,这里显然是对mine盘处理,依旧用到rand函数,熟悉此函数的小伙伴应该都知道使用此函数需要在主函数中加上sand函数以及头文件(不知道的小伙伴可以参考猜数字小游戏的文章),解释一下x = rand() % row + 1; y = rand() % col + 1;,比如rand函数会得到一个随机的数,将其对9(假如行列均为9)取模,可以得到0-8的一个随机数,再加一就得到1-9中的数字,对应雷盘中的坐标。

代码:

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

测试(布置之后打印mine盘如图):

5)统计某坐标周围雷数

       统计某坐标周围的雷数,这对大家来说应该是个很简单的函数了,是在mine盘中进行,这里介绍两种方法:

①如左图,也如笔者的参考代码,我使用嵌套循环的方式依次去访问每一个格子

②举个栗子,如右图,在‘1’表示有雷,‘0’表示无雷的前提下,此坐标周围的雷数就是,先将所有字符加起来,再减去8*'0'('1'+'0'+'0'+'0'+'1'+'1'+'1'+'0'-8*'0'=4)。                                   

      

代码:

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

6)四周扩散

       先来说一下什么是四周扩散功能,类似一种被动技能,当玩家所选格子的周围都没有雷,就会同时都被展开,而且周围的每一个格子也会被触发这种技能,向外展开,很明显是在check盘中操作,如图所示。

       很明显,可以看出适合使用递归去实现,主函数传给函数一个周围无雷的坐标,在函数内,先将此坐标赋值空格符,再去遍历每个格子,如果格子周围也无雷,就会递归传入这个坐标重复上述,递归停止条件是此坐标是空格符,也是防止变成死递归。

 --->  

代码:

void SpreadAround(char mine[ROWS][COLS], char check[ROWS][COLS], int x, int y)
{
	int count = 0;
	int i = 0, j = 0;
	if (check[x][y] != ' ')   //递归停止条件
	{
		check[x][y] = ' ';
		for (i = -1; i <= 1; i++)   //遍历所选坐标周围的每一个格子
		{
			for (j = -1; j <= 1; j++)
			{
				count = AroundMine_count(mine, x + i, y + j);   //判断当前格子是否满足触发四周扩散功能
				if (count == 0 && check[x + i][y + j] == '*') 
                {  
                //注意此处加上check[x + i][y + j] == '*'
	            //否则会扫描到check[x][y]进入死循环,因为此时check[x][y]==‘ ’,
                //会再次进入SpreadAround函数
					SpreadAround(mine, check, x + i, y + j);
                }
				else if (count != 0)
					check[x + i][y + j] = '0' + count;
			}
		}
	}
}

测试:

7)玩家排雷

      玩家排雷是扫雷小游戏编写过程的一个重要步骤之一,需要mine盘和check盘一起操作,

①玩家根据check盘上的数字选择坐标打开盘格,也可以给怀疑是雷的格子作出标记(这里是在坐标紧跟着输入一个空格,读者也可设置其他符号),

②设置一个永久循环(while(1)),读者在输入坐标非法时可重复输入,所选坐标若是雷,则直接break游戏结束,若不是雷,则统计此坐标周围的雷数,看是否触发四周扩散功能,若没触发(即周围有雷),则会赋值雷子的个数,

③玩家每一次选择之后,就会判断雷盘的格子是否已被打开完(下面介绍的is_win函数),若已经被打开完,则游戏结束,玩家胜利。

代码:

void PlayerMove(char mine[ROWS][COLS], char check[ROWS][COLS], int row, int col)
{
	char flag = 0;
	int i = 0, j = 0;
	int x = 0, y = 0;
	while (1)
	{
		//PrintBoard(mine, ROW, COL);
		printf("请输入坐标:(若是标记雷,坐标后紧接加上空格)\n");
		scanf("%d %d%c", &x, &y, &flag);
		if (flag == ' ')   //flag判断坐标后面有无紧跟一个空格符
		{
            //当所有雷排完,直接结束游戏
			check[x][y] = 'v';
			PrintBoard(check, ROW, COL);
			if (is_Win(check, ROW, COL) == 1)
			{
				printf("排雷成功,游戏胜利\n");
				break;
			}
            //未排完所有雷,就继续while语句选择坐标
			continue;
		}

		if (x >= 1 && x <= row&&y >= 1 && y <= col && (check[x][y] == '*' || check[x][y] == 'v'))
		{
			if (mine[x][y] == '1')
			{
				printf("踩到雷了,游戏结束\n");
				printf("雷子坐标为%d %d\n", x, y);
				PrintBoard(check, ROW, COL);
				break;
			}
			else
			{
				//统计所选坐标周围的雷数
				int count = AroundMine_count(mine, x, y);
				
				//如果count=0,递归进行四周扩散
				if (count == 0)
				{
					SpreadAround(mine, check, x, y);
				}

				//count!=0,则记录雷数
				else
				{
					check[x][y] = '0' + count;   //记录雷数
				}

				PrintBoard(check, ROW, COL);
			}
		}
		else
			printf("输入错误,请重新输入:\n");

		if (is_Win(check, ROW, COL) == 1)   //不仅上面玩家自主去选择坐标标记雷,而且此处在每一次玩家选择完坐标处理之后,也要判断雷盘是否还有未选择的格子
		{
			printf("排雷成功,游戏胜利\n");
			PrintBoard(check, ROW, COL);
			break;
		}
	}
}

测试:

   

8)玩家是否避雷成功

        玩家是否避雷成功也就决定了游戏是否结束的标志,即玩家在没有碰过电脑设置的雷子所在的坐标前提下,走完雷盘(check盘)所有的格子,即胜利。本函数就是在每一次玩家选择坐标且没有碰到雷之后判断check盘是否还有未选择的格子,但在这样的前提下,整个小游戏的时间会大大加长,如果有更好的方法,可以写在评论区。

代码:

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

测试(左成功,右失败):

   

9)博弈

       博弈部分就是将以上所述的函数组合在一起,形成一局完整的游戏(如测试图所示),并且在主函数的调用之下,实现 由玩家选择是否继续进行下一句游戏 的效果,直呼内行。

代码:

void game()
{
	//将扫雷界面分为两层,一层为mine,一层为check
	//mine每个元素记录是雷('1')、非雷('0')
	char mine[ROWS][COLS] = { 0 };
	//每个元素记录是未核查('*')、核查之后周围雷的个数('1、2....8')
	char check[ROWS][COLS] = { 0 };
	//初始化
	InitBoard(mine, ROWS, COLS,'0');
	InitBoard(check, ROWS, COLS,'*');
	//打印
	//PrintBoard(mine, ROW, COL);
	PrintBoard(check, ROW, COL);
	//布置雷
	SetMine(mine, ROW, COL);
	//玩家排雷
	PlayerMove(mine, check, ROW, COL);

}

测试(考虑截图长度,仅布置3个雷): 

     

完整测试

       

后记

       据我看来,扫雷小游戏还是会比三子棋小游戏的实现难一点,一方面需要设计两个数组实现雷盘,另一方面,需要实现的函数也更多,其中也涉及到了递归这个较难的知识点,我想我应该说的比较清楚了,有什么不懂的和想要源码的小伙伴可以私我或者在评论区告诉我,大家一起讨论,或者是想出更加优化的方案,当然,文章中若出现技术上的错误,也可以发出来,供大家排错,谢谢。

猜你喜欢

转载自blog.csdn.net/phangx/article/details/130375896
今日推荐