基于C语言 --- 自己写一个扫雷小游戏

C语言程序设计笔记---020

初阶扫雷小游戏(开源)

前言:

游戏规则:

(1)、在打印的数组棋盘里,输入坐标,排雷。

(2)、坐标格式:x(空格) y

(3)、x横坐标,y竖坐标

(4)、当布置的雷,被排除完,则判定胜利
如图所示

采用模块化编写

arr_game2.c执行主要逻辑程序

arr_game2.h存放头文件或函数声明等程序

arr_main2.c放主函数逻辑程序

1、arr_main2.c程序大纲

首先,从以往玩游戏的经验来谈,我们需要为游戏写一个游戏开始菜单,由玩家选择是否开始游戏。

这里可以借助所学的menu( )自定义函数,设计一个简易的菜单

c 复制代码
void menu()
{
	printf("****************************************\n");
	printf("************** 1.play game *************\n");
	printf("************** 0.game over *************\n");
	printf("****************************************\n");
}

当我们选择1,则开始游戏;当选择0,则退出游戏。

那么就得思考,如何对玩家得选择进行判定

1.利用 scanf( ) 函数获取输入值,将获取的值,借用 do while 循环语句和switch( )选择语句 ,进行下一步。

2.当选择1,开始游戏则执行game()自定义,游戏主逻辑执行程序

3.当选择0,switch中 进入case 0 : 的入口,执行退出游戏,并且 do while( ),判定为0,则退出程序

4.当玩家误选择非法数值,则default : 提示玩家输入错误

c 复制代码
int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("\n*********** 三子棋游戏开始 *************\n\n");
			game();//游戏执行逻辑函数
			break;
		case 0:
			printf("\n退出游戏\n");
			break;
		default:
			printf("\n选择错误请重新选择\n\n");
			break;
		}

	} while (input);

接下来,主要阐述game( )函数内容,游戏的执行逻辑

1.当玩家选择1,开始游戏后,会显示整个以号展现的9 9棋盘,并且显示出行号与列号,方便玩家输入坐标。

如何实现9*9棋盘的显示呢?

(1)、因为棋盘是一个平面,平面由一条条线组成,线又由一个个点组成,利用所学的数组知识可以联想到,棋盘不过是由一个个字符拼接而成。
(2)、所以首先得定义和初识化两个大小相等,且类型相同的数组(mine和show),mine[ ][ ]数组负责布置雷和排查雷,show[ ][ ]负责将玩家在mine[ ][ ]数组所排查的雷显示出来,即显示排查情况。
(3)、自定义初识化函数 InitBoard( ) ,自定义显示棋盘函数 DisPlayBoard( ) ;

(4)、函数的参数,可想而知,需要数组名(board) --- 首元素的地址指定需操作的数组,需要行(ROW)和列(COL)指定操作的元素或坐标或地址;

c 复制代码
void game()
{
	//定义两个大小相等,类型相同的数组棋盘
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初识化棋盘
	char set = 0;
	InitBoard(mine, ROWS, COLS, '0');//初识化布置雷棋盘
	InitBoard(show, ROWS, COLS, '*');//初识化排查雷棋盘
	//显示棋盘
	//DisPlayBoard(mine,ROW,COL);//注意:显示只需显示玩家看的9*9棋盘,参数用 ROW 和 COL
	DisPlayBoard(show, ROW, COL);

2.当我们完成棋盘的初识化和显示后,我们需要将雷布置进去,当雷布置好后,玩家才可以排雷;

所以就得写一个自定义布置雷和自定义排查雷的函数,直到玩家踩雷或排查完雷结束。
(1)、自定义布置雷函数 SetMine( ) ;
(2)、自定义排查雷函数 FindMine( ) ;
3.如何判断游戏结束?

那么很容易思考到,玩家可不断的进行排雷,就是反复的调用玩家排雷函数和显示函数,同时不停的判断每一次排雷是否踩中地雷或排完地雷。所以需要一个循环来实现,这里就用while循环,实现不停的下棋。

但是,不停的下棋,始终在死循环,那么就思考利用,break跳出循环,那么跳出的while函数的条件是什么呢?我们可以想到,如99的棋盘,不断的排雷,棋盘可存放的总数一共才9 9个数,还放了10个雷,那么我们只需要判断,我们排雷的次数,是否等于总数减去我们放的雷,当相等时说明,雷被排完了,游戏结束。

c 复制代码
void game()
{
	//定义两个大小相等,类型相同的数组棋盘
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初识化棋盘
	char set = 0;
	InitBoard(mine, ROWS, COLS, '0');//初识化布置雷棋盘
	InitBoard(show, ROWS, COLS, '*');//初识化排查雷棋盘
	//显示棋盘
	//DisPlayBoard(mine,ROW,COL);//注意:显示只需显示玩家看的9*9棋盘,参数用 ROW 和 COL
	DisPlayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine ,ROW,COL);//雷只需要布置在9*9棋盘
	//排查雷
	FindMine(mine,show, ROW, COL);//首先,需要mine数组中排查,布置的雷,然后将排查出的雷,放置在show数组中显示出来,且雷的布置和显示棋盘均在9*9棋盘

arr_main.c程序大纲展示

c 复制代码
#include "arr_game2.h"

//显示游戏菜单
void menu()
{
	printf("*****************************************\n");
	printf("************* 1.play game ***************\n");
	printf("************* 0.game over ***************\n");
	printf("*****************************************\n");
}

void game()
{
	//定义两个大小相等,类型相同的数组棋盘
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//初识化棋盘
	char set = 0;
	InitBoard(mine, ROWS, COLS, '0');//初识化布置雷棋盘
	InitBoard(show, ROWS, COLS, '*');//初识化排查雷棋盘
	//显示棋盘
	//DisPlayBoard(mine,ROW,COL);//注意:显示只需显示玩家看的9*9棋盘,参数用 ROW 和 COL
	DisPlayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine ,ROW,COL);//雷只需要布置在9*9棋盘
	//排查雷
	FindMine(mine,show, ROW, COL);//首先,需要mine数组中排查,布置的雷,然后将排查出的雷,放置在show数组中显示出来,且雷的布置和显示棋盘均在9*9棋盘

}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();//显示游戏菜单
		printf("请输入;>");
		scanf("%d",&input);
		switch (input)
		{
		case 1:
			printf("\n************ 扫雷游戏开始 **************\n\n");
			game();//游戏主逻辑函数
			break;
		case 0:
			printf("\n退出游戏\n\n");
			break;
		default:
			printf("\n输入错误,请重新输入\n\n");
			break;
		}
	} while (input);
	return 0;
}

2、arr_game2.h

用于存放所自定义的函数和头文件等声明的程序

通俗易懂,就不多赘述,详见代码注释的说明。

c 复制代码
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

//定义显示的棋盘show大小
#define ROW 9
#define COL 9

//定义外围的棋盘mine大小
#define ROWS ROW+2
#define COLS COL+2

//宏定义雷的个数
#define ESAY_MINE 10

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

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);

3、arr_game2.c

用于存放对 arr_main.c 程序大纲做提到的函数进行封装,实现具体的功能的程序
说明 :基于arr_main2.c 程序大纲逻辑对代码进行讲解
注意 :这里均以99的棋盘为例哦,但是我们需要考虑到边界的元素,当我们输入的坐标在边缘时,如何计算周围的雷的数量呢?
所以我们在arr_game2.h中,宏定义的数组大小可以知道,通过引用比9
9数组大一圈的数组覆盖9*9的棋盘即可。所以我们将引用两个大小相等,类型相同的数组。

为了方便理解,简单画个图:

3.1、 自定义初化函数 InitBoard( ) 和 自定义显示函数 DisPlayBoard( )

首先,根据需求我们需要一个棋盘才可以正常的扫雷.

如何让棋盘初识化和显示呢?

1.根据所学的二维数组知识,便可以知道,当我们遍历二维数组的每一个元素,使得填充为需要的字符,便可以由InitBoard( ) 达到效果。然后我们将mine数组放置字符 ' 0 ',将show数组放置字符 ' * ' 。
补充:我们以mine数组填充字符0表示不是雷,以字符1表示为雷。以show字符填充字符 ✳号隐藏雷的位置,由玩家输入坐标进行逐步排雷。
注意因为我们使用的是两个大小相等,类型相同的数组,所以增加一个字符型变量,以传参为我们需要的字符

c 复制代码
//初识化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)//下标0~10
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] =  set;//由set参数决定显示的棋盘
		}
	}
}
c 复制代码
//显示棋盘
void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("\n************** 扫雷 *************\n");
	//打印行号、列号,方便输入坐标
	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");
	}
	printf("\n************** 扫雷 *************\n\n");
}

如图所示

3.2、 自定义布置雷函数 SetMine( )

我们定义坐标变量,并使用随机值rand函数约束坐标范围,将雷的坐标以while循环的方式存放进mine数组中,当布置一个雷,雷则少一个,所以直接以雷的数量作为循环判断条件即可。

c 复制代码
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = ESAY_MINE;
	while (count)//由雷的数量,布置一个少一个,作为判定的条件
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')//是否被占用
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

如图所示

3.4、 自定义排查雷函数 FindMine( )

那么接下来,我们如何进行排查雷呢?

首先,我们由玩家循环输入将排查的坐标(x,y),然后判断输入的坐标是否超出9*9扫雷棋盘的范围,若超出,可打印提示语句,重新输入。当符合范围,就判断是否踩中了雷,若踩中雷,break跳出循环,游戏结束。当玩家没有踩中雷,则显示输入坐标处(x,y),周围雷的数量情况。
所以思考怎么计算坐标处周围雷的数量呢 ?

当然不难想到,当我们知道了一个数组的元素坐标时,可通过简单的加减运算,即可得知周围坐标。
如图所示

当我们知道了输入坐标周围的元素坐标,那么就可以通过该对应的坐标提取该元素的值,为字符0还是为字符1,联系前文中提到的0/1(非雷/雷),就可知我们mine数组填充0/1来作为是否为雷的巧妙之处了,因为我们计算周围的雷的数量只需要将该对应的元素值加起来就得到了数量情况了。
但是值得注意的是:我们自始自终mine和show数组都是字符型数组,所以在进行加减运算时,要区分字符与十进制数值的关系。

众所周知:

字符 '0' - '0' = 0

'5' - '0' = 5

'9' - '0' = 9

所以我们将周围元素加一起,再减去8* '0'即可。

c 复制代码
//返回雷的数量
int  GetMine(char mine[ROWS][COLS],int x,int y)
{
	return (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] + mine[x-1][y]) - 8 * '0';
}

最后通过GetMine( )获取的雷的数量,ret再将它加上一个字符' 0 ',赋值给shou数组,然后作为DisPlayBoard( )的参数,打印出来即可。

c 复制代码
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	//如何判断输赢?
	while (1)
	{
		printf("请输入排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <=  col)
		{
			if (mine[x][y] == '1')
			{
				printf("\n很遗憾,您踩中雷了\n\n");
				DisPlayBoard(mine,ROW,COL);
				break;
			}
			else//没猜中雷,返回雷的数量
			{
				int ret = GetMine(mine, x, y);//返回雷的数量
				show[x][y] = ret + '0';//放置再在show数组显示
				DisPlayBoard(show, ROW, COL);
			}
		}
		else
		{
			printf("\n输入非法,请重新输入\n");
		}
	}
	if (win == row * col - ESAY_MINE)
	{
		printf("恭喜,您已经成功排完雷\n");
		DisPlayBoard(mine, ROW, COL);

	}
}

但是我们调试发现:

当玩家没有踩中雷时,一直进行排雷,那么如何判断输赢游戏结束呢?

在上文arr_main2.c大纲中提到,当棋盘所有的雷被排查完游戏结束。
即理解为:当棋盘中可以输入排查的坐标位置数量win,当win 小于了row*col(棋盘可下棋坐标棋子的总数量) 减去 地雷数量就跳出循环结束游戏,判定胜利
所以优化代码:

c 复制代码
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	//如何判断输赢?
	//当棋盘所有的雷被排查完
	//即理解为:当棋盘的可排查的棋子坐标位置,win 小于了row*col(棋盘可下棋坐标棋子的总数量) - 地雷数量就跳出循环结束游戏,判定胜利
	while (win <row*col - ESAY_MINE)
	{
		printf("请输入排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <=  col)
		{
			if (mine[x][y] == '1')
			{
				printf("\n很遗憾,您踩中雷了\n\n");
				DisPlayBoard(mine,ROW,COL);
				break;
			}
			else//没猜中雷,返回雷的数量
			{
				int ret = GetMine(mine, x, y);//返回雷的数量
				show[x][y] = ret + '0';//放置再在show数组显示
				DisPlayBoard(show, ROW, COL);
			}
		}
		else
		{
			printf("\n输入非法,请重新输入\n");
		}
	}
	if (win == row * col - ESAY_MINE)
	{
		printf("恭喜,您已经成功排完雷\n");
		DisPlayBoard(mine, ROW, COL);

	}
}

最后我们将arr_game2.h中宏定义的雷(ESAY_MINE)的数量改为80,来证明while跳出的条件是正确的。
如图所示

4、结束语

相信通过这样一个扫雷的小游戏,更具掌握了对数组的操作以及对自定义函数的深刻认识;
如果觉着文章对您有所帮助,请不要吝啬的一赞三连哦,谢谢阅读,不足之处还请多多指教。

扫雷源码获取链接 : 扫雷小游戏初阶

相关推荐
Theodore_102238 分钟前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
网易独家音乐人Mike Zhou1 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
冰帝海岸2 小时前
01-spring security认证笔记
java·笔记·spring
----云烟----3 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024063 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
小二·3 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic3 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it3 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康3 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神4 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式