C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程

文章目录

    • @[TOC]
    • :video_game:前言
        • 一、游戏效果展示
        • 二、代码基本思路讲解
    • :video_game:具体实现
        • 一、菜单栏函数(test、menu)的编写(test.c中)
        • 二、游戏函数(game)的编写(test.c中)
        • 三、随机布置雷区(SetMine)
        • 四、计算该位置周围雷的个数(get_mine_count)
        • 五、递归实现该区域的展开(Unfold)
        • 六、标记/取消标记(sign_mine)
        • 七、玩家找雷(FindMine)+判断输赢
    • :video_game:代码模块化展示
        • 一、test.c
        • 二、game.h
        • 三、game.c

前言

一、游戏效果展示

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第1张图片

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第2张图片

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第3张图片

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第4张图片

二、代码基本思路讲解

我们如果想要实现扫雷游戏,首先要打印出来一个棋盘。在此棋盘中,玩家看不到雷(mine)的分布,玩家可以通过输入坐标的方式来进行排雷。对于我们程序员来说,也要同时布置一个棋盘,与玩家不同的是我们可以清晰的看到雷区的分布,以此来判断玩家是否踩雷。于是我们就有了目标,想要初始化两个棋盘。先创建两个二位数组,然后初始化的时候我们可以将雷视为字符’1’,将非雷视为字符’0’。当然了想要随机设置雷就要编写代码来具体实现,文章后面会有详细讲解。其打印结果如下:

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第5张图片

接下来就是玩家开始找雷,当玩家输入二维数组的坐标时,我们来判断此位置是否为雷’1’,如果非雷’0’则玩家继续排查,若踩到雷则玩家失败。想必看到这篇文章的同学都是玩过扫雷的,但是到现在为止,我们还是没有真正的感受到扫雷的乐趣,我们的代码到底差在哪里呢?代码还可以如何优化呢?欲知后事如何,请看下文分析。


具体实现

在讲解之前首先要明确一个模块化编写代码的思路,就是通过源文件、头文件相结合的方式来实现的。

这里有test.c和game.c文件,以及game.h头文件

一、菜单栏函数(test、menu)的编写(test.c中)

1.为了整体代码的功能和美观我们可以先编写一个菜单提示栏(menu)

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

2.编写扫雷游戏前要写出菜单栏信息,让玩家可以实现基本的游戏/退出游戏的操作,此操作可以放在test函数中。如果玩家选择游戏则调用game函数开始游戏,反之则退出游戏。

void test()
{
	int temp = 0;
    srand((unsigned)time(NULL));      //后文会讲到,定位到 三、随机布置周围雷的个数
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&temp);
		switch (temp)
		{
		case 1:
			game();
			break;
		case 2:
			printf("游戏退出\n");
			break;
		default:
			printf("选择错误!请重新输入!\n");
		}
	}while(temp);
}
二、游戏函数(game)的编写(test.c中)

game中实现扫雷的基本功能,其中包括两个棋盘的初始化(InitBoard)、打印(DisplayBoard)、随机设置雷(SetMine)、玩家找雷(FindMine)函数。这是扫雷游戏的基本流程。

❓ 什么是初始化? 答:向二位数组中添加内容,这里是加字符’0’或’1’。

❓ 什么是打印棋盘? 答:用printf()来输出二位数组里的内容。

void game()
{
	char mine[ROWS][COLS] = { 0 };//布置好的雷的信息
	char show[ROWS][COLS] = { 0 };//排查出雷的信息
	printf("游戏开始\n");

	//初始化两个棋盘
	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);

}

这里的ROWS和COLS是在game.h头文件中设定的值12,ROW和COL同样也是在game.h头文件中设定好的值9;

❓ 这里有人会问到为什么我们打印 9 *9 棋盘但是却要初始化 11 *11 的棋盘呢???不要慌,带着疑问看下去可能你的理解会更加深刻(答案在 :四、计算该位置周围雷的个数)

三、随机布置雷区(SetMine)

设置x和y来接收随机函数(rand)的数值。如果想要接收 1~9 之间任意一个数,需要先把rand函数取余(%)9这样会得到 0~9之间的数,再加上1就会得出 1~9之间的数。

❓什么是srand()??,怎么用呢???

答: 为了得到随机数,在此之前我们需要先初始化随机种子srand().不过为了防止随机数每次重复,常常使用系统时间来初始化 即: srand((unsigned int) time(NULL));如果想在一个程序中生成随机数序列,需要至多在生成随机数之前设置一次随机种子。即:只需在主程序开始处调用srand((unsigned)time(NULL)); 后面直接用rand就可以了。不要在for等循环放置srand((unsigned)time(NULL));

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int n = count;
	while (n)
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;
		if (board[x][y]=='0')
		{
			board[x][y] = '1';
			n--;
		}
	}
}
四、计算该位置周围雷的个数(get_mine_count)

以图中所打印的二位数组为例,若我们想计算该位置周围雷的个数,就需要对周围8个元素逐个判断。

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第6张图片

到这里我们不难想到可以把8个字符加起来,再转换成数字就可以判断该位置周围雷的个数。代码如下:

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

❓ 那么问题来了,我要是判断如下位置的元素应该怎么办呢?

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第7张图片

还记得上文我创建的 11 *11 的二维数组吗,这里只是显示的是 9 *9,实际上也是11 *11。由于判断如上元素周围雷的个数不好判断,所以我们才创建了11 *11的二维数组,这时候我们就可以其轻而易举的算出周围雷的个数。

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第8张图片

五、递归实现该区域的展开(Unfold)

⚙️ 根据我们玩扫雷的多年经验可知,我们点下任意位置,若该位置周围没有雷则它就会向周围展开一圈,若果该位置的周围有雷它则会停止并显示周围雷的个数。我们可以这样理解,电脑会一直重复这个展开操作,直到不能展开(周围位置有雷)为止。如下图所示展开的位置都没有雷,但是在边界的周围有雷。

C语言实现:扫雷小游戏(递归展开+标记/取消标记)详细版教程_第9张图片

再来对代码进行想象,这段代码会是一段循环,循环体内会有判断。判断条件就应该是:该位置周围如果没有雷就直接展开,反之则显示该位置周围雷的个数。这里我们需要借助get_mine_count函数的思想,对该位置周围8个进行判断,但与之不同的是,这里需要不断循环的往下递归,不断的对新位置周围8个元素进行判断吗,所以这里还需要调用到get_mine_count函数。

具体代码的编写思想出来了,剩下的就是添加限制条件:1.如果该位置被判断过则停止递归 2.若果该位置超过9 *9二位数组的边界则递归停止。由此我们就可以写代码喽。

//递归展开
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)
{
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)
	{
		return ;
	}
	//判断是否已经被排除
	if (show[x][y] != '*')
	{
		return ;
	}
	int n = get_mine_count(mine, x, y);   //周围有几个炸弹
	if (n>0)                     //周围如果没有炸弹,则展开
	{
		show[x][y] = n+'0';
	}
	else if (n==0)
	{
		show[x][y] = ' ';
		for (int i=-1;i<=1;i++)
		{
			for (int j=-1;j<=1;j++)
			{
			  Unfold(mine, show, x+i, y+j);
			}
		}
	}
}
六、标记/取消标记(sign_mine)

这里我们用到mine和show数组,这里的代码编写起来比较简单,大多数用到的就是循环条件和判断条件。玩家标记了一个未被判断过的位置,若此位置为炸弹则炸弹总数量减1,反之则炸弹总数量不变。相信读者大大仔细阅读下列代码都会明白。

//标记或取消标记
int sign_mine(char mine[ROWS][COLS],char show[ROWS][COLS])
{
	int n = 0,a=0,b=0;
	int win = 0;
	while (1)
	{
		//美化界面
		printf("\n****************\n");
		printf(" 1.mark_mine(标记)\n 2.cancel_mine(取消标记)\n 3.continue(跳过)\n");
		printf("****************\n请选择:>");
		scanf("%d", &n);
		if (n == 1)
		{
			printf("请输入要标记的坐标:>");
			scanf("%d%d", &a, &b);
			if (show[a][b] == '*')
			{
				show[a][b] = 'Y';
				win += 1;
				DisplayBoard(show, ROW, COL);
				continue;
			}
			else
			{
				printf("标记错误,请重新标记!!\n");
				continue;
			}
		}
		else if (n == 2)
		{
			printf("请输入要取消标记的坐标:>");
			scanf("%d%d", &a, &b);
			if (show[a][b]=='Y')
			{
				show[a][b] = '*';
				DisplayBoard(show, ROW, COL);
				win -= 1;
				continue;
			}
			else
			{
				printf("操作错误,请重新输入!!\n");
				continue;
			}
		}
		else
			return win;
	}
}
七、玩家找雷(FindMine)+判断输赢

这个模块就是玩家输入坐标查询,1.若该位置被查询过则请玩家重新输入 2.若该位置为雷则游戏失败 3.若该位置非雷则调用(Unfold)函数展开同时打印棋盘 4.若该位置超过边界则输入错误。

最后就是判断输赢,在标记雷(sign_mine)函数中,若标记正确则雷的总数减1,直到总数为0时则排雷成功。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = count;
	while (win>0)
	{
		printf("\n请输入想要排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标被排查过了\n");
				continue;
			}
			if (mine[x][y] == '1')
			{
				printf("抱歉!你已经被炸死了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			if (mine[x][y] == '0')
			{
				Unfold(mine, show, x, y);           //利用递归展开非雷区域
				printf("\n");
				DisplayBoard(show, ROW, COL);      //打印棋盘
			    int n=sign_mine(mine, show);      //标记雷或取消标记雷
				win = win - n;
				printf("\n");
				printf("还剩%d个雷\n",win);
			}
		}
		else
			printf("坐标非法!\n");
	}

	if (win==0)
	{
		printf("恭喜你,排雷成功!!!!!\n");
		//DisplayBoard(mine, ROW, COL);
		Distribution_mine(mine,ROW,COL);
	}
}

代码模块化展示

一、test.c
#define _CRT_SECURE_NO_WARNINGS 1;

#include "game.h"

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

void game()
{
	char mine[ROWS][COLS] = { 0 };//布置好的雷的信息
	char show[ROWS][COLS] = { 0 };//排查出雷的信息
	printf("游戏开始\n");

	//初始化两个棋盘
	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);
}

void test()
{
	int temp = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&temp);
		switch (temp)
		{
		case 1:
			game();
			break;
		case 2:
			printf("游戏退出\n");
			break;
		default:
			printf("选择错误!请重新输入!\n");
		}
	}while(temp);
}

int main()
{
	test();
	return 0;
}
二、game.h
#pragma once

#include
#include
#include

#define ROW 9
#define COL 9

#define ROWS 11
#define COLS 11

#define count 5     //雷的数量

//初始换棋盘
void InitBoard(char board[ROWS][COLS],int rows, int cols,char ret);

//打印棋盘
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);
三、game.c
#define _CRT_SECURE_NO_WARNINGS 1;

#include "game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = ret;
		}
	}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("\n--------扫雷--------\n");
	for (int i=0;i<=row;i++)
	{
		printf("%d ",i);
	}
	printf("\n");
	for (int i=1;i<=row;i++)
	{
		printf("%d ",i);
		for (int 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 n = count;
	while (n)
	{
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;
		if (board[x][y]=='0')
		{
			board[x][y] = '1';
			n--;
		}
	}
}
//周围雷的个数
int get_mine_count(char mine[ROWS][COLS],int x,int y)
{
	int tmp = 0;
	for (int i=-1;i<=1;i++)
	{
		for (int j=-1;j<=1;j++)
		{
			tmp+= mine[x + i][y + j]-'0';
		}
	}
	return tmp;
}

//递归展开
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int x,int y)
{
	if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)
	{
		return ;
	}
	//判断是否已经被排除
	if (show[x][y] != '*')
	{
		return ;
	}
	int n = get_mine_count(mine, x, y);   //周围有几个炸弹
	if (n>0)                     //周围如果没有炸弹,则展开
	{
		show[x][y] = n+'0';
	}
	else if (n==0)
	{
		show[x][y] = ' ';
		for (int i=-1;i<=1;i++)
		{
			for (int j=-1;j<=1;j++)
			{
			  Unfold(mine, show, x+i, y+j);
			}
		}
	}
}
//标记或取消标记
int sign_mine(char mine[ROWS][COLS],char show[ROWS][COLS])
{
	int n = 0,a=0,b=0;
	int win = 0;
	while (1)
	{
		//美化界面
		printf("\n****************\n");
		printf(" 1.mark_mine(标记)\n 2.cancel_mine(取消标记)\n 3.continue(跳过)\n");
		printf("****************\n请选择:>");
		scanf("%d", &n);
		if (n == 1)
		{
			printf("请输入要标记的坐标:>");
			scanf("%d%d", &a, &b);
			if (show[a][b] == '*')
			{
				show[a][b] = 'Y';
				win += 1;
				DisplayBoard(show, ROW, COL);
				continue;
			}
			else
			{
				printf("标记错误,请重新标记!!\n");
				continue;
			}
		}
		else if (n == 2)
		{
			printf("请输入要取消标记的坐标:>");
			scanf("%d%d", &a, &b);
			if (show[a][b]=='Y')
			{
				show[a][b] = '*';
				DisplayBoard(show, ROW, COL);
				win -= 1;
				continue;
			}
			else
			{
				printf("操作错误,请重新输入!!\n");
				continue;
			}
		}
		else
			return win;
	}
}

//成功后打印最终雷区分布
void Distribution_mine(char mine[ROWS][COLS],int row,int col)
{
	for (int i=0;i<=col;i++)
	{
		printf("%d ",i);
	}
	printf("\n");
	for (int i=1;i<=row;i++)
	{
		printf("%d ", i);
		for (int j=1;j<=col;j++)
		{

			if (mine[i][j]=='1')
			{
				mine[i][j] = 'Y';
				printf("%c ",mine[i][j]);
			}
			else if (mine[i][j]=='0')
			{
				mine[i][j] = ' ';
				printf("%c ",mine[i][j]);
			}
		}
		printf("\n");
	}
	printf("\n");
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = count;
	while (win>0)
	{
		printf("\n请输入想要排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] != '*')
			{
				printf("该坐标被排查过了\n");
				continue;
			}
			if (mine[x][y] == '1')
			{
				printf("抱歉!你已经被炸死了!\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			if (mine[x][y] == '0')
			{
				Unfold(mine, show, x, y);           //利用递归展开非雷区域
				printf("\n");
				DisplayBoard(show, ROW, COL);      //打印棋盘
			    int n=sign_mine(mine, show);      //标记雷或取消标记雷
				win = win - n;
				printf("\n");
				printf("还剩%d个雷\n",win);
			}
		}
		else
			printf("坐标非法!\n");
	}
	if (win==0)
	{
		printf("恭喜你,排雷成功!!!!!\n");
		//DisplayBoard(mine, ROW, COL);
		Distribution_mine(mine,ROW,COL);
	}
}

你可能感兴趣的:(c语言,游戏程序)