C语言实现扫雷游戏

经过前两篇文章的学习,我们已经知道了C语言中的分支语句与循环语句,应该也能熟练的运用这些语句了。但对于这些语句分别认识并且熟练运用,并不代表就能够做到清晰明了的嵌套使用,为了复习近期学习的知识和用新的题型来突破自我,让我们一起来尝试使用C语言中的各种语句来创造出一个扫雷游戏吧!!!

一、扫雷游戏的思路

扫雷游戏是一个很多人都了解并且耳熟能详的游戏,开始游戏后,会显示出一个棋盘,整体是一个规则的正方形,里面有规律的排列着各种格子,而这些格子中有一些是雷,有一些不是雷,对于它是不是雷,需要我们一个个格子去探索,而获得游戏胜利的条件就是:

找出棋盘中所有不是雷的格子。

由于是第一次尝试编写扫雷游戏,我们先做一个规格比较简易的扫雷游戏吧!!!

1.游戏菜单

创造一个游戏菜单,拥有选择开始游戏退出游戏的功能。

2.创造棋盘

创造一个规则正方形的9*9规格的棋盘。

3.布置雷

在9*9的棋盘中随机布置10个雷。

4.实现扫雷功能

可以实现扫雷的游戏功能:选择棋盘中的格子时,如果不是雷,则显示出周围格子中的雷的个数,

如果是雷,则游戏结束,如果找出所有不是雷的格子,则游戏获胜。

二、实现游戏的多个文件 

要创造多个功能的文件,首先我们需要知道如果创造文件,和这些分文件的作用到底是什么。

创造扫雷游戏就需要我们创造出三个文件,它们分别是头文件game.h,和源文件game.ctest.c创建文件的步骤是,点击对应的文件,选择添加,选择新建项。

然后对新建项命名

这三个文件的都是创造扫雷游戏所不可或缺的,它们每一个文件的功能都非常重要。

game.h:作为头文件,它用来存放创造游戏所需要的各种类型的全局变量,并且也用来实现编写代码使所必需的函数说明

game.c: 存放各种实现游戏功能所需要的函数。

test.c:作为编译的主程序,存放主函数,编写实现游戏功能的主要思想。

三、实现游戏的多个函数

首先我们需要做的事情是将创造游戏所需的库函数放入我们的头文件game.h中,这样我们就省去了每一个文件上都要加上四五个库函数的步骤,只需要一个"game.h"就可以取而代之。

1.创造菜单

创造出扫雷游戏,第一步需要我们创造出一个游戏菜单,而游戏菜单只需要用printf()函数就足够,并不需要返回值,所以定义菜单函数时只需要使用void函数就好,因为void函数没有返回值。

构造函数之后,我们需要将函数的名称以及需要向该函数传递的参数放在源文件game.h中,这样做的目的是实现函数的声明,存放在game.h中之后,在test.c加入"game.h",就可以直接调用函数并且不需要再对函数进行声明。

2.创造游戏棋盘

首先我们知道扫雷游戏的棋盘是一个9*9规格的棋盘,得到思路:创建数组来存放棋盘是比较合适便捷并且通俗易懂的。因此我们需要创建一个二维数组来创造棋盘,正常来说一个9*9规格的棋盘,我们只需要创建一个arr[9][9]就足够了,但是我们需要对问题进行多方面的思考,比如我们创建完棋盘之后,我们还需要对方格周围的区域进行扫描,但有些格子在边边角角,就会比较麻烦。

如图所示,我们虽然可以使用一些方法实现格子对周围一圈进行扫描,但是对于处理图中边角处的格子,周围只有三格或五格,这样就会使扫描周围格子时出现访问越界的情况,我给出的解决方法是使将二维数组定义成[11][11]的范围,使整体周围多一圈格子,然后只显示[1][1]到[9][9]的范围,这样仍然是9*9的棋盘并且不会出现访问越界的情况。

如果我们用'0'代表无雷,'1'代表有雷,那么我们在进行扫描的时候就会发生歧义(扫描雷个数时也是用数字定义),所以我们可以定义两个棋盘,一个专门用来存放雷,用'0'代表无雷,'1'代表有雷。为了不让玩家看到雷,这个棋盘我们不会展示出来,所以这个数组叫做mine。在创造一个给玩家看的棋盘,我们用'*'来初始化它,这个数组叫做show

为了使数据使用方便,我们可以在头文件中定义几个全局变量,分别代表行和列。

这样定义数组时只需要像这样。

char mine[ROWS][COLS];
char show[ROWS][COLS];

对数组进行初始化,使用for循环是较为简便的方法:

char InitBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = //'0'?
                          //'*'?
		}
	}
}

但此时又出现了问题,传输实参后我们并不能确定这个棋盘是mine还是show,也就无法确定初始化的字符。但是没关系!开动我们的脑筋,其实只需要再加一个字符就好了。

//棋盘的初始化
char InitBoard(char board[ROWS][COLS], int row, int col, char zifu)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = zifu;
		}
	}
}

像这样,传输时多传输一个'0'或者'*'就能弄清楚哪个是mine哪个是show啦。同样将它在头文件中进行声明,我们的棋盘数组初始化就完成了,而初始化结束了,我们还需要将它们打印出来,这就很简单了,只需要for()循环就足够了。(前面的一个for和后面嵌套中外层循环的printf是为了打印出行列数,看着更清晰)

//棋盘的打印
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= row; 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");
	}
}

这样在test.c的主函数调用输出样例就是这样的,也就创造成功啦!

3.放置棋盘中的雷

放置棋盘中的雷,需要将mine中的'0'修改成'1',并且棋盘中的雷是随机生成的,因此我们需要用到上一期"猜数字"中的知识点srandrand,在test.c的主函数中输入

srand((unsigned int)time(NULL));

并且在头文件game.h中加入库函数#include <time.h>即可创造种子时刻改变的随机数。

因为我们需要随机生成10个雷,所以我们定义一个整型变量num=10;在每次创造一个雷的时候,让num--;直到第10个雷生成,同时也就结束了放置雷的工作。并且我们需要创建两个随机数rand,分别代表随机的行和列。

int i = rand() % row + 1;         int j = rand() % col + 1;

表示二维数组的行和列范围在1~row或1~col。(二维数组最外围只起到防止访问越界的情况,不能在最外围生成雷)

//放置雷
void GenBoard(char board[ROWS][COLS], int row, int col)
{
	int num = 10;
	while (num)
	{
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (board[i][j] == '0')
		{
			board[i][j] = '1';
			num--;
		}
	}
}

我们来调试一下,看看是不是随机生成了10个雷。

第一次:

第二次:

由图可见,成功生成了随种子变化的10个随机的雷。

4.排查棋盘中的雷

首先我们理清思路一下,排查出棋盘中的雷需要做到什么。

第一我们需要做到,在玩家输入坐标后,要知道相应的格子是否有雷,如果为'0'则没事发生,如果为'1'那么雷爆炸,游戏结束。

第二我们需要做到,在打开非雷格子的时候,告知玩家格子周围有几个雷。

好了,思路理清了,那么我们开始动手实践吧!!!

首先我们定义一个FindMine函数,由于我们需要对两个棋盘同时影响,所以这次需要传输两个二维数组进去。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)

 因为要输入坐标,我们设置两个整型变量。并且要知道周围有几个雷是一个比较繁琐的事情,所以我们再次创建一个函数,GetMineCount,用于查看选定坐标周围的雷数。

int GetMineCount(char mine[ROWS][COLS], int a, int b);

由图可知,假定选定坐标为[a][b],那么周围需要排查的区域分别为[a-1][b-1],[a-1][b],[a-1][b+1],[a][b-1],[a][b+1],[a+1][b-1],[a+1][b],[a+1][b+1]。而查询雷数,则需要我们把这些雷的格子统计到一起并且返回雷总数的数字。

注意!!!这里你可能认为,返回总数只要全部相加再返回就好了呀。这样想那你就错了!别忘了存放在棋盘中的不是真正的数字0,而是字符'0'!!!如果我们要返回真正的数字,需要像这样

int GetMineCount(char mine[ROWS][COLS],int a,int b)
{
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = a - 1; i <= a + 1; i++)
	{
		for (j = b - 1; j <= b + 1; j++)
		{
			num += (mine[i][j] - '0');
		}
	}
	return num;
}

这样返回的就是真正的数字啦。

接下来我们再回到FindMine函数中来,实现了排查雷数量的功能,那么我们还需要让非雷运行,雷爆炸。此时可以使用if()语句来对格子是否为雷进行判断,不是雷就显示周围雷数并且循环继续输入坐标,游戏继续,如果为雷则提示游戏结束,并且打印出存放雷的mine棋盘让玩家心服口服。

if (a >= 1 && a <= 9 && b >= 1 && b <= 9)
{
	if (mine[a][b] == '0')
	{
		int num = GetMineCount(mine, a, b);
		show[a][b] = num + '0';//展示雷数时要把数字转换成字符型
		PrintBoard(show, ROW, COL);
		win++;//记录成功排查非雷格的数量
	}
	else
	{
		printf("你运气真不好,雷炸了\n");
		PrintBoard(mine, ROW, COL);
	}
}

因为每次成功排雷,游戏都会继续,此时继续输入坐标就需要用循环来进行帮助,此处我们选择用while(因为此函数比较复杂,相较于for函数,while能让维持循环运行的自增式子放在循环中,更加灵活和多变,而do...while操作不当或许会使最后只剩下雷的时候还要让玩家再猜一次,那可就死翘翘了)而如果使用while,该怎么控制循环结束呢?

此时上一条代码中出现的win就起到决定性作用了,游戏胜利的条件是排除所有非雷的格子,而非雷的格子总数就是row*col-总雷数,我们将游戏总雷数创建成一个全局变量放在头文件game.h中,那么控制while循环的代码就可以像这样while (row * col - EASYCOUNT > win)而循环结束时(雷不炸为前提)正好两式相等,游戏胜利。

如果玩家输入的坐标正确则进行一系列排查,如果输入错误则提示并重新输入,思路清晰了,那么代码如下

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int a = 0;
	int b = 0;
	int win = 0;
	while ((row * col - EASYCOUNT) > win)
	{
		printf("请输入排雷的坐标:");
		scanf("%d %d", &a, &b);
		if (a >= 1 && a <= row && b >= 1 && b <= col)
		{
			if (mine[a][b] == '0')
			{
				int num = GetMineCount(mine, a, b);
				show[a][b] = num + '0';
				PrintBoard(show, ROW, COL);
				win++;
			}
			else
			{
				printf("你运气真不好,雷炸了\n");
				PrintBoard(mine, ROW, COL);
			}
		}
		else
		{
			printf("输入错误,请重新输入:\n");
		}
		if (win == (row * col - EASYCOUNT))
		{
			printf("游戏胜利!!!\n");
			PrintBoard(mine, ROW, COL);
		}
	}
}

此时已经基本完成了所有所需工作,如果你能将所有函数编写并灵活的运用,那么扫雷游戏对你来说就不在话下啦!

游戏已经成功运行啦,并且每次游戏结束都可以重新选择是否游玩,每次游玩时雷所在的位置也都有所不同!(time函数的返回值作为srand函数的参数,而time函数的返回值时刻变化,也就是生成数的种子随机变化,使炸弹的坐标成为了真正的随机数)

四、游戏完整代码

1.头文件game.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASYCOUNT 10
//函数的声明

//打印游戏界面
void GameInte();

//数组初始化
char InitBoard(char board[ROWS][COLS], int row, int col, char zifu);

//打印扫雷棋盘
void PrintBoard(char board[ROWS][COLS], int row, int col);

//放置棋盘中的雷
void GenBoard(char board[ROWS][COLS], int row, int col);

//查找选定坐标周围的雷数
int GetMineCount(char mine[ROWS][COLS], int a, int b);

//排查棋盘中的雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
2.源文件game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void GameInte()
{
	printf("----- 扫 雷 游 戏 -----\n");
	printf("**********************\n");
	printf("*****   1.play   *****\n");
	printf("*****   0.quit   *****\n");
	printf("**********************\n");
	printf("是否游玩?请输入>:");
}

char InitBoard(char board[ROWS][COLS], int row, int col, char zifu)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = zifu;
		}
	}
}

void PrintBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= row; 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");
	}
}

void GenBoard(char board[ROWS][COLS], int row, int col)
{
	int num = 10;
	while (num)
	{
		again:
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (board[i][j] == '0')
		{
			board[i][j] = '1';
			num--;
		}
	}
}

int GetMineCount(char mine[ROWS][COLS],int a,int b)
{
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = a - 1; i <= a + 1; i++)
	{
		for (j = b - 1; j <= b + 1; j++)
		{
			num += (mine[i][j] - '0');
		}
	}
	return num;
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int a = 0;
	int b = 0;
	int win = 0;
	while ((row * col - EASYCOUNT) > win)
	{
		printf("请输入排雷的坐标:");
		scanf("%d %d", &a, &b);
		if (a >= 1 && a <= row && b >= 1 && b <= col)
		{
			if (mine[a][b] == '0')
			{
				int num = GetMineCount(mine, a, b);
				show[a][b] = num + '0';
				PrintBoard(show, ROW, COL);
				win++;
			}
			else
			{
				printf("你运气真不好,雷炸了\n");
				PrintBoard(mine, ROW, COL);
				GameInte();
				break;
			}
		}
		else
		{
			printf("输入错误,请重新输入:\n");
		}
	}
	if (win == (row * col - EASYCOUNT))
	{
		printf("游戏胜利!!!\n");
		PrintBoard(mine, ROW, COL);
		GameInte();
	}
}
3.源文件test.c
#include"game.h"
void game()
{
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	//数组初始化
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印扫雷棋盘
	printf("\n");
	PrintBoard(show, ROW, COL);
	//放置棋盘中的雷
	GenBoard(mine, ROW, COL);
	//PrintBoard(mine, ROW, COL);
	FindMine(mine, show, ROW, COL);
	return 0;
}
int main()
{
	int a = 0;
	//打印游戏界面
	srand((unsigned int)time(NULL));
	GameInte();
	do
	{
		scanf("%d", &a);
		switch(a)
		{
		case 1:
		{
			game();
			break;
		}
		case 0:
			printf("退出游戏");
			break;
		default:
			printf("输入错误,请重新输入:>");
		}
	} while (a);
	return 0;
}

那今天对于使用C语言实现扫雷游戏的思路,过程以及最后测试,就给大家分享到这里啦,我会尽量将自己的学习经验分享给大家的(虽然说还是个小白),我们下次再见!!!

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值