【C语言】实现扫雷游戏(初阶+进阶,有代码和图文讲解)

前言

扫雷游戏整体的代码架构和我上一篇写的n子棋的实现差不多,都是分为game.h文件,game.c文件和test.c文件来写的。这一篇主要讲在C语言中扫雷游戏的设计扫雷进阶的实现,不过多阐述语法知识。

一. 扫雷游戏介绍

扫雷游戏是一种单人益智游戏,玩家需要在一个方格矩阵中找出所有的地雷,同时避免触雷。游戏开始时,玩家需要选择难度级别,不同难度级别的方格矩阵大小和地雷数量不同。游戏中,玩家可以通过点击方格来揭开它们,如果揭开的方格上没有地雷,则会显示周围八个方格中地雷的数量,如果揭开的方格上有地雷,则游戏结束。玩家需要根据周围地雷的数量来推断哪些方格是安全的,哪些方格可能有地雷,最终找出所有的地雷并标记它们。游戏的目标是在最短的时间内找出所有的地雷。
Alt
Alt

二. C语言扫雷游戏的设计

  1. 在玩家开始扫雷之前就要把所有地雷的位置信息存储好,所以需要一个二维数组存储数据。把有雷的位置设为字符 ‘1’,没有雷的位置设为字符 ‘0’。
    Alt

  2. 玩家扫雷时,为了避免在同一个界面里显示周围八个方格中地雷的数量1地雷的位置1产生矛盾,我们使用两个二维数组,一个二维数组存放雷的位置信息,另一个二维数组存放排查雷的信息(周围雷的数目)。为了让两个数组的信息方便产生联系,可以把两个二维数组定义成一样的大小。
    Alt

  3. 在玩家排查到一个边缘的非雷的位置时,为了防止在统计周围雷的个数的时候越界,可以让玩家能操作的扫雷界面比整个存放排查雷的信息的二维数组小一圈。比如玩家能操作的扫雷界面是9乘9,那么二维数组的大小需要定义11乘11的大小。
    Alt
    Alt
    Alt

  4. 存放雷的位置信息的二维数组可以都初始化为字符 ‘0’,存放排查雷的信息可以都初始化为字符 ‘#’(这里都初始化为字符数组,初始化什么字符可以选比较适合的,为什么这里要初始化为字符‘0’到后面的代码会体现这样定义的到处),所以可以定义两个同样大小的字符数组

Alt
5. 我实现的扫雷玩家需要输入坐标来排查某个位置,为了缓解输入坐标前要一个一个数格子,希望代码运行时能让玩家看到界面旁边的横纵坐标。
Alt

有了上面的想法,就能写一个初级的C语言扫雷游戏了。

三. 扫雷初阶实现

扫雷游戏的初阶实现主要展现完整代码,如果代码本身有问题可以在评论区留言:
test.c文件(测试游戏的代码):

//test.c文件

#include"game.h"

void menu()
{
    
    
	printf("----------扫雷游戏----------\n");
	printf("############################\n");
	printf("###        1.Play        ###\n");
	printf("###        0.Exit        ###\n");
	printf("############################\n");

}

void game()
{
    
    
	//初始化两个字符数组
	//1.存放布置好的雷mine
	char mine[ROWS][COLS] = {
    
     0 };
	//2.存放排雷的信息show
	char show[ROWS][COLS] = {
    
     0 };

	//初始化雷的位置信息
	InitBoard(mine,ROWS,COLS,'0');
	//初始化方块矩阵
	InitBoard(show, ROWS, COLS, '#');
	//打印扫雷界面(只打印排雷的界面)
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	
	//排查雷
	FindMine(mine, show, ROW, COL,'#');
}

int main()
{
    
    
	int mode = 0;
	srand((unsigned int)time(NULL));

	do
	{
    
    
		menu();
		printf("请输入模式(1/0):");
		scanf("%d", &mode);

		switch (mode)
		{
    
    
		case 1:
			printf("开始游戏\n");
			game();
			printf("\n");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (mode);

	return 0;
}

game.h文件(包含库函数,由#define定义的常量,游戏函数的声明):

//game.h文件

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

//布置雷的数目
#define MINE 80

//扫雷界面的大小,(1<=ROW<=999),(1<=COL<=51,列数太多一个屏幕放不下会出现bug)
#define ROW 9
#define COL 9

//整个二维数组的大小
#define ROWS ROW+2
#define COLS COL+2

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 show[ROWS][COLS],int row, int col,char set);

game.c文件(游戏函数的实现):

//game.c文件

#include"game.h"

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

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;

	//纵坐标显示
	printf("  0|");
	for (i = 1; i <= col; i++)
	{
    
    
		printf("%2d ", i);
	}
	printf("\n---|");
	for (i = 1; i <= col; i++)
	{
    
    
		printf("---");
	}
	printf("\n");


	for (i = 1; i <= row; i++)
	{
    
    
		//横坐标显示
		printf("%3d|", 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)
{
    
    
	int x = 0;
	int y = 0;
	int count = MINE;
	
	int i = 0;
	while (count > 0)
	{
    
    
		//随机获取扫雷界面范围内的一个坐标埋一个雷
		x = rand() % row + 1;
		y = rand() % col + 1;
		//如果该坐标没有雷,就在该位置设为字符 '1'
		if (board[x][y] == '0')
		{
    
    
			board[x][y] = '1';
			count--;
		}
	}
}

int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    
    
	//把该坐标周围8个字符相加再减八个字符'0'得到雷的个数
	return (mine[x-1][y]+ mine[x - 1][y-1]+ mine[x][y-1]+ mine[x + 1][y-1]+ mine[x + 1][y]+ 
		mine[x + 1][y+1]+ mine[x][y+1]+ mine[x - 1][y+1]-'0'*8);
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,char set)
{
    
    
	int x = 0;
	int y = 0;
	//记录玩家排雷的方格的个数,排一个方格,count-1
	int count = row * col - MINE;
	while (count>0)
	{
    
    
		//让玩家输入想揭开方格的坐标
		printf("请输入想揭开方格的坐标(格式:横坐标 纵坐标):");
		scanf("%d %d", &x, &y);

		//判断玩家输入坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]==set)
		{
    
    
			//坐标合法,继续判断是否踩到雷了
			if (mine[x][y] == '1')
			{
    
    
				printf("更遗憾,您踩到了地雷,被炸死了...\n");
				printf("以下是所有地雷的分布位置,地雷的位置标为1。\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
    
    
				//如果没有踩到雷,要给玩家显示该坐标周围的雷的个数
				//注意雷的个数最后以字符的形式存储到show数组中
				//GetMineCount(mine,x,y)得到的是雷的数目,是个整形,转换为字符类型的数字只需加'0'即可
				
				show[x][y] = GetMineCount(mine, x, y) + '0';
				DisplayBoard(show, ROW, COL);
				count--;

			}
		}
		else
			printf("您输入的坐标已经超出方格矩阵的范围,或者该坐标已经排查过了,请重新输入...\n");
	}
	if (count == 0)
	{
    
    
		printf("恭喜您排查了所有地雷!!!\n");
	}
}

测试(设MINE为3,ROW为5,COL为4):
Alt
Alt

通过本次测试可以发现:右下角明知道都没有雷却要一个一个输好几个坐标,这还是5乘4界面的情况下,太麻烦了,能否像下图真正的扫雷游戏一样,排查了一个非雷的坐标,就能够打开一大片?进阶实现就讲它。

Alt

四. 扫雷进阶实现

主要攻克排查了一个非雷的坐标就能够打开一大片的情况。
这里用递归实现。首先先分析能打开一大片的前提条件是什么?

  1. 玩家排查的这个坐标不是雷
  2. 这个坐标的周围也没有雷。如果这个坐标的周围有雷,就停止扩展

举个例子:比如我排查了下图中用红框框起来的地方,它满足上面两个前提条件,向外扩展到周围8个坐标。
Alt

但是在周围8个坐标中的任意一个,排查周围是否有雷,一定会再次排查到最开始的坐标(黑色圆圈框住的坐标),如果一直排查最开始的坐标会进入死递归。所以还要再加一个条件:

3.该坐标还没有被排查过

Alt
通过上面的三个条件,就能用递归实现这个功能了。
对于解决这个问题的函数,我取名为Extend函数,我的代码实现如下:

//game.c文件
#include"game.h"
void Extend(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,char set)
{
    
    
	//如果该坐标的周围有雷,直接在该坐标存储周围雷的个数
	if (GetMineCount(mine, x, y) !=0)
	{
    
    
		show[x][y] = GetMineCount(mine, x, y) + '0';
	}
	//如果周围没有雷,该坐标还没有排查过,并且不在二维数组的边缘,就在该坐标存储空格,然后向四面八方扩展
	else if ((show[x][y] == set) && (x!=0 && x!=ROWS-1 && y != 0 && y!=COLS-1))
	{
    
    
		show[x][y] = ' ';
		Extend(mine, show, x - 1, y, set);
		Extend(mine, show, x - 1, y - 1, set);
		Extend(mine, show, x, y - 1, set);
		Extend(mine, show, x + 1, y - 1, set);
		Extend(mine, show, x + 1, y, set);
		Extend(mine, show, x + 1, y + 1, set);
		Extend(mine, show, x, y + 1, set);
		Extend(mine, show, x - 1, y + 1, set);
	}
	//如果以上情况都不满足,说明该坐标的周围没有雷,或者雷已经排查过了,或者已经排查到了玩家看不到的二维数组的边缘
}

既然已经不是一个一个排雷了,那FindMine函数里不能直接记录玩家排雷的方格的个数,我再写了一个Judge函数判断玩家排雷排的是否只剩雷了。

//game.c文件

#include"game.h"
int Judge(char board[ROWS][COLS],int row,int col,char set)
{
    
    
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = 1; i <= row; i++)
	{
    
    
		for (j = 1; j <= col; j++)
		{
    
    
			if (board[i][j] == set)
			{
    
    
				num++;
			}
		}
	}
	if (num == MINE)
	{
    
    
		return 1;
	}
	return 0;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,char set)
{
    
    
	int x = 0;
	int y = 0;

	while (1)
	{
    
    
		//让玩家输入想揭开方格的坐标
		printf("请输入想揭开方格的坐标(格式:横坐标 纵坐标):");
		scanf("%d %d", &x, &y);

		//判断玩家输入坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]==set)
		{
    
    
			//坐标合法,继续判断是否踩到雷了
			if (mine[x][y] == '1')
			{
    
    
				printf("更遗憾,您踩到了地雷,被炸死了...\n");
				printf("以下是所有地雷的分布位置,地雷的位置标为1。\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
    
    
				Extend(mine, show, x, y, set);
				DisplayBoard(show, ROW, COL);
				if (Judge(show, row, col, set))
				{
    
    
					printf("恭喜您排查了所有地雷!!!\n");
					break;
				}
			}
		}
		else
			printf("您输入的坐标已经超出方格矩阵的范围,或者该坐标已经排查过了,请重新输入...\n");
	}
}

其他地方的代码与扫雷初级实现一样。

代码测试(设MINE为5,设ROW和COL为12):
Alt

Alt

五.完整扫雷进阶代码

test.c文件

//test.c文件

#include"game.h"

void menu()
{
    
    
	printf("----------扫雷游戏----------\n");
	printf("############################\n");
	printf("###        1.Play        ###\n");
	printf("###        0.Exit        ###\n");
	printf("############################\n");

}

void game()
{
    
    
	//初始化两个字符数组
	//1.存放布置好的雷mine
	char mine[ROWS][COLS] = {
    
     0 };
	//2.存放排雷的信息show
	char show[ROWS][COLS] = {
    
     0 };

	//初始化雷的位置信息
	InitBoard(mine,ROWS,COLS,'0');
	//初始化方块矩阵
	InitBoard(show, ROWS, COLS, '#');
	//打印扫雷界面(只打印排雷的界面)
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	
	//排查雷
	FindMine(mine, show, ROW, COL,'#');
}

int main()
{
    
    
	int mode = 0;
	srand((unsigned int)time(NULL));

	do
	{
    
    
		menu();
		printf("请输入模式(1/0):");
		scanf("%d", &mode);

		switch (mode)
		{
    
    
		case 1:
			printf("开始游戏\n");
			game();
			printf("\n");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (mode);

	return 0;
}

game.h文件

//game.h文件

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

//布置雷的数目
#define MINE 5

//扫雷界面的大小,(1<=ROW<=999),(1<=COL<=51,列数太多一个屏幕放不下会出现bug)
#define ROW 12
#define COL 12

//整个二维数组的大小
#define ROWS ROW+2
#define COLS COL+2

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 show[ROWS][COLS],int row, int col,char set);

game.c文件

//game.c文件

#include"game.h"

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

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;

	//纵坐标显示
	printf("  0|");
	for (i = 1; i <= col; i++)
	{
    
    
		printf("%2d ", i);
	}
	printf("\n---|");
	for (i = 1; i <= col; i++)
	{
    
    
		printf("---");
	}
	printf("\n");


	for (i = 1; i <= row; i++)
	{
    
    
		//横坐标显示
		printf("%3d|", 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)
{
    
    
	int x = 0;
	int y = 0;
	int count = MINE;
	
	int i = 0;
	while (count > 0)
	{
    
    
		//随机获取扫雷界面范围内的一个坐标埋一个雷
		x = rand() % row + 1;
		y = rand() % col + 1;
		//如果该坐标没有雷,就在该位置设为字符 '1'
		if (board[x][y] == '0')
		{
    
    
			board[x][y] = '1';
			count--;
		}
	}
}

int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    
    
	//把该坐标周围8个字符相加再减八个字符'0'得到雷的个数
	return (mine[x-1][y]+ mine[x - 1][y-1]+ mine[x][y-1]+ mine[x + 1][y-1]+ mine[x + 1][y]+ 
		mine[x + 1][y+1]+ mine[x][y+1]+ mine[x - 1][y+1]-'0'*8);
}

void Extend(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,char set)
{
    
    
	//如果该坐标的周围有雷,直接在该坐标存储周围雷的个数
	if (GetMineCount(mine, x, y) !=0)
	{
    
    
		show[x][y] = GetMineCount(mine, x, y) + '0';
	}
	//如果周围没有雷,该坐标还没有排查过,并且不在二维数组的边缘,就在该坐标存储空格,然后向四面八方扩展
	else if ((show[x][y] == set) && (x!=0 && x!=ROWS-1 && y != 0 && y!=COLS-1))
	{
    
    
		show[x][y] = ' ';
		Extend(mine, show, x - 1, y, set);
		Extend(mine, show, x - 1, y - 1, set);
		Extend(mine, show, x, y - 1, set);
		Extend(mine, show, x + 1, y - 1, set);
		Extend(mine, show, x + 1, y, set);
		Extend(mine, show, x + 1, y + 1, set);
		Extend(mine, show, x, y + 1, set);
		Extend(mine, show, x - 1, y + 1, set);
	}
	//如果以上情况都不满足,说明该坐标的周围没有雷,或者雷已经排查过了,或者已经排查到了玩家看不到的二维数组的边缘
}

int Judge(char board[ROWS][COLS],int row,int col,char set)
{
    
    
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = 1; i <= row; i++)
	{
    
    
		for (j = 1; j <= col; j++)
		{
    
    
			if (board[i][j] == set)
			{
    
    
				num++;
			}
		}
	}
	if (num == MINE)
	{
    
    
		return 1;
	}
	return 0;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,char set)
{
    
    
	int x = 0;
	int y = 0;

	while (1)
	{
    
    
		//让玩家输入想揭开方格的坐标
		printf("请输入想揭开方格的坐标(格式:横坐标 纵坐标):");
		scanf("%d %d", &x, &y);

		//判断玩家输入坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]==set)
		{
    
    
			//坐标合法,继续判断是否踩到雷了
			if (mine[x][y] == '1')
			{
    
    
				printf("更遗憾,您踩到了地雷,被炸死了...\n");
				printf("以下是所有地雷的分布位置,地雷的位置标为1。\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else
			{
    
    
				Extend(mine, show, x, y, set);
				DisplayBoard(show, ROW, COL);
				if (Judge(show, row, col, set))
				{
    
    
					printf("恭喜您排查了所有地雷!!!\n");
					break;
				}
			}
		}
		else
			printf("您输入的坐标已经超出方格矩阵的范围,或者该坐标已经排查过了,请重新输入...\n");
	}
}

结语

非常感谢您能够阅读完这篇文章,如果有任何建议或纠正欢迎在评论区留言。如果您认为这篇文章对您有所收获,点一个小小的赞就是我创作的巨大动力,谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_73276255/article/details/131410919
今日推荐