【C语言】扫雷游戏的一步一步的实现

文章目录

  • 一、扫雷游戏分析和设计
    • 1.1 扫雷游戏的功能说明
    • 1.2 游戏的分析和设计
      • 1.2.1 数据结构的分析
      • 1.2.2 ⽂件结构设计
  • 二、扫雷游戏代码实现
  • 总结


一、扫雷游戏分析和设计

1.1 扫雷游戏的功能说明

• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦
• 默认随机布置10个雷
• 可以排查雷

◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束

游戏的界⾯:

【C语言】扫雷游戏的一步一步的实现_第1张图片
【C语言】扫雷游戏的一步一步的实现_第2张图片

初始游戏画面
【C语言】扫雷游戏的一步一步的实现_第3张图片
排雷画面
【C语言】扫雷游戏的一步一步的实现_第4张图片
排雷失败画面
【C语言】扫雷游戏的一步一步的实现_第5张图片

1.2 游戏的分析和设计

1.2.1 数据结构的分析

◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
【C语言】扫雷游戏的一步一步的实现_第6张图片

◦ 把除10个雷之外的所有雷都找出来,排雷成功,游戏结束
【C语言】扫雷游戏的一步一步的实现_第7张图片

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。
因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息。
【C语言】扫雷游戏的一步一步的实现_第8张图片

那如果这个位置布置雷,我们就存放1,没有雷我们就存放0
【C语言】扫雷游戏的一步一步的实现_第9张图片

假如排查的是(3,2)这个坐标,我们就访问绿色一圈里面,除黄色方格的八个方位的雷的个数,然后统计周围的雷的个数。
【C语言】扫雷游戏的一步一步的实现_第10张图片

但是问题来了,但当我们如果要排查(8,1)这个坐标的时候,当我们开始访问周围一圈8个位置,统计雷的个数时,最下面的三个位置由于没有开辟空间,会导致数组越界访问,越界访问,代码就执行不起来了呀?这怎么办。
【C语言】扫雷游戏的一步一步的实现_第11张图片
既然他会数组越界,那我们不让他越界就好了呀!既然99访问四周边缘的时候会多访问一层空间,那我们就再包裹一圈,也就是创建1111的数组,我们在外层里面不放雷,到时候访问坐标(8,1)的时候,直接就可以访问了,这样就解决了数组越界的问题。

【C语言】扫雷游戏的一步一步的实现_第12张图片


此时此刻我们的雷阵大小的设计是不是创建好了呢,雷是1,非雷是0,如果我们要排查一个某一位置,这个坐标是不是雷,这个坐标周围有一个雷,我们将统计周围的雷数量打印在棋盘上的时候,这个雷如果打印在棋盘上面,那到底是雷的个数信息,还是雷的信息,这就产生了混淆和打印上的困难,怎么办呢?

如果我们加多一个棋盘,玩家点击该棋盘上的信息对应到埋雷的棋盘上的信息。将雷的信息放在一个棋盘上,而雷的个数信息通过专门给玩家猜雷的棋盘,而玩家猜雷的棋盘统计雷的棋盘并打印在玩家的棋盘上,而我们的埋雷的棋盘上的信息并没有发生相应的改变。


读到这里,哎,你可能会想到,我一个棋盘也可以搞定呀!非雷用#,雷用&,然后用数字1,2,3... 来统计周围雷的数量就可以了,谁说过一定要用01来代表非雷与雷。没错,这种办法也可以,博主也期待你们去尝试一下,但是这样做棋盘上既有雷和非雷的信息,还有排查出雷的个数信息,就比较混杂,但不够方便!



1.2.2 ⽂件结构设计

当我们去写一个游戏,设计一个游戏时,我们可以采用分文件的形式来写:

1   test.c //⽂件中写游戏的测试逻辑
2   game.c //⽂件中写游戏中函数的实现等
3   game.h //⽂件中写游戏需要的数据类型和函数声明等

举个例子:
【C语言】扫雷游戏的一步一步的实现_第13张图片

【C语言】扫雷游戏的一步一步的实现_第14张图片
【C语言】扫雷游戏的一步一步的实现_第15张图片

当然这个代码也可以执行起来:
【C语言】扫雷游戏的一步一步的实现_第16张图片

二、扫雷游戏代码实现

通过上面的形式,我们把文件分成三个文件管理

 1. removal_of_mines.c
 2. game.c
 3. game.h

首先,我们一上来就要打印菜单给玩家看,我们可以选择do...while循环,而菜单通过函数封装,用menu()打印菜单,然后玩家输入,switch语句来辨认玩家的选择,玩与不玩,接下来,巧妙的地方来了,我们把玩家输入的选择作为do....while(intput)的循环条件,因为这样可以让玩家选择继续玩,或者退出!点击(猜数字小游戏的一步一步实现)的步骤一样

# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("******************************************\n");
	printf("******************************************\n");
	printf("***********   1. 扫雷游戏启动!!  *******\n");
	printf("************  0. 不玩了,启动不了 ********\n");
	printf("******************************************\n");
	printf("******************************************\n");
}


int main()
{
	int input = 0;
	do               //因为我们一上来就是要打印菜单,因此我们选择do.....while语句
	{
		menu();//通过函数封装,用menu()打印菜单
		printf("请选择--->");
		scanf("%d", &input);//然后玩家输入
		switch (input)      //用switch分支选择选择玩与不玩
		{
		case 1:
			printf("扫雷游戏启动!!\n");
			game();
			break;
		case 0:
			printf("不玩了,启动不了!\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处
	return 0;
}

接下来,我们要实现game(),我们刚才分析得出,创建两个数组,一个埋雷的棋盘,一个用来给用户看和猜的棋盘;

埋雷的棋盘:char mine[][]={0};
猜雷的棋盘:char player[][]={0};

到这里,我们是不是只要把9放进去就好了呢?但是这样不利于后面代码的维护,如果要改为16*16的棋盘呢?就要把整个程序的char mine[9][9]里的9改为16,一个一个的改,也可以,不过有点费力。如果代码很长要改的数量很多,有几个漏改的,代码也就会出错,因此为了以后代码的修改和长远的考虑,我们可以#define定义ROW(行)和COL(列),
也就是

#define ROW 9
#define COL 9
char mine[ROW][COL]={0};
char palyer[ROWS][COLS]={0};

但是这样九行就列真的可以了吗?哎,显然还不行,因为,前面我们分析了数组会越界,因此这里我们要定义11*11数组,那我们是可以加2:

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

因此数组创建为:

char mine[ROWS][COLS]={0};
char player[ROW]S[COLS]={0}

这个时候,数组的大小创建好了,接下来就是对数组初始化了,也就是给我们两个棋盘放我们的初始值,那我们就创建一个初始化函数吧!

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

这样写也可以,但是我们要放字符0*,这样我们就要调用两个相同的函数,这样的话,就有点太繁琐了,如果我们调用一个函数,能同时打印;那就更好了,让代码来:

void InitBoard(char board[ROWS][COLS], int rows, int cols, int ret)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = ret;
		}
	}
	
}
InitBoard(mine, ROWS, COLS, '0');
InitBoard(player, ROWS, COLS, '*');

0*一起传过去字符‘0’代表该位置没有雷,字符*代表隐藏,让玩家来猜,初始化完了,我们得看看我们放的值有没有实现我们想要的效果,那我们来打印一下吧,打印我们得要printf函数吧!

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("******************************************\n");

	int i = 0;
	for (i = 0; i <= col; i++)
	{
		printf(" %d ", i);
	}
	printf("\n");
	for (i = 1; i <= col; i++)
	{
		printf(" %d ", i);
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf(" %c ", board[i][j]);
		}
		printf("\n");
	}
	printf("******************************************\n");

}

【C语言】扫雷游戏的一步一步的实现_第17张图片
接下来就是埋地雷了;使用rand()函数(猜数字小游戏的一步一步实现有讲解),随机生成坐标,但是我们的坐标要合法,x 的范围必须在row的范围内,y也同样如此,为了避免重复埋雷,我们要加上if判断语句;

void SetMine(char board[ROWS][COLS], int row, int col)
{
	//布置10个雷
	//⽣成随机的坐标,布置雷
	int count = EASY_COUNT;//EASY_COUNT是设置雷的个数
	while (count)
	{
		int x = rand() % row + 1;//1到9
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

打印雷盘看看是否埋好雷了:
【C语言】扫雷游戏的一步一步的实现_第18张图片

此时此刻,我们的雷布置好了,接下来我们要排查雷了;排查雷我们要排查的位置是里面9*9的位置,也就是(x >= 1 && x <= row && y >= 1 && y <= col)
在这些合法的位置排查雷,

【C语言】扫雷游戏的一步一步的实现_第19张图片
(一共10个雷,没排完了71个位置,继续循环,排完了71个位置,也排赢了)
如果mine[x][y] == '1',也就是点中雷了,游戏结束,然后打印雷的地图给玩家看。
如果该位置不是雷,那么就统计该坐标周围的雷,也就是八个方位。我们用
GetMineCount来统计周围雷的个数

void FindMine(char mine[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < row * col - EASY_COUNT)//一共10个雷,没排完了71个位置,继续循环
	{
		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
			{
				//该位置不是雷,就统计这个坐标周围有⼏个雷
				int count = GetMineCount(mine, x, y);
				player[x][y] = count + '0';
				DisplayBoard(player, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("坐标⾮法,重新输⼊\n");
		}
	}
	if (win == row * col - EASY_COUNT)//一共10个雷,排完了71个位置,也排赢了
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
}

利用GetMineCount统计雷的个数,怎么统计呢?由于我们用的是字符‘0’和‘1’来存储,怎么把字符统计起来,转换成整型数字呢?
小知识来啦:
字符:‘1’ -1==‘0’‘2’-1==‘1’‘3’-2==’1‘

字符1的ASCII是49,字符0的ASCII是48,两相减就是数字1了,在C语言中,当字符‘0’以%c字符的形式打印,打印出来的字符0和数字0一样的,以整形的形式打印出来的是48,也就是他的ASCII值。
【C语言】扫雷游戏的一步一步的实现_第20张图片
我们将八个方位的字符统计起来再减去字符’0‘,算出来的值就是我们的雷的个数。

int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y] + mine[x - 1][y - 1] + 
		mine[x - 1][y + 1] + mine[x + 1][y] + 
		mine[x + 1][y - 1] + mine[x + 1][y + 1]+ 
		mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
}

【C语言】扫雷游戏的一步一步的实现_第21张图片

源码如下:

removal_of_mines.c

# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void game()
{
	char mine[ROWS][COLS];
	char player[ROWS][COLS];

	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(player, ROWS, COLS, '*');

	SetMine(mine, ROW, COL);

	DisplayBoard(player, ROW, COL);

	FindMine(mine, player, ROW, COL);
}

void menu()
{
	printf("******************************************\n");
	printf("******************************************\n");
	printf("***********   1. 扫雷游戏启动!!  *******\n");
	printf("************  0. 不玩了,启动不了 ********\n");
	printf("******************************************\n");
	printf("******************************************\n");
}


int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do               //因为我们一上来就是要打印菜单,因此我们选择do.....while语句
	{
		menu();//通过函数封装,用menu()打印菜单
		printf("请选择--->");
		scanf("%d", &input);//然后玩家输入
		switch (input)      //用switch分支选择选择玩与不玩
		{
		case 1:
			printf("扫雷游戏启动!!\n");
			game();
			break;
		case 0:
			printf("不玩了,启动不了!\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);//选择1继续循环,0退出,这也是采用do.....while语句的好处
	return 0;

game.c

# define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

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

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("******************************************\n");

	int i = 0;
	for (i = 0; i <= col; i++)
	{
		printf(" %d ", i);
	}
	printf("\n");
	for (i = 1; i <= col; i++)
	{
		printf(" %d ", i);
		int j = 0;
		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)
{
	//布置10个雷
	//⽣成随机的坐标,布置雷
	int count = EASY_COUNT;//EASY_COUNT是设置雷的个数
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return (mine[x - 1][y] + mine[x - 1][y - 1] + 
		mine[x - 1][y + 1] + mine[x + 1][y] + 
		mine[x + 1][y - 1] + mine[x + 1][y + 1]+ 
		mine[x][y - 1] + mine[x][y + 1] - 8 * '0');
}

void FindMine(char mine[ROWS][COLS], char player[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
			{
				//该位置不是雷,就统计这个坐标周围有⼏个雷
				int count = GetMineCount(mine, x, y);
				player[x][y] = count + '0';
				DisplayBoard(player, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("坐标⾮法,重新输⼊\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
}

game.h

# define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 


#define EASY_COUNT 10

#define ROW 9 
#define COL 9

#define ROWS ROW+9
#define COLS COL+9

//初始化棋盘
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 player[ROWS][COLS], int row, int col);

总结

好啦!扫雷简易版的小游戏到这里就完了,注:永远不要害怕每一次失败,要勇于尝试,看过千遍万遍,不过于自己动手做几遍!
感谢您的观看,如果喜欢的话,请点个赞。如果文章有错误,我不胜感激,你可以指出,我不胜感激,让我们共同学习和交流!

你可能感兴趣的:(c语言,游戏,c#,算法,c++,开发语言)