C语言模拟实现扫雷游戏(可展开)


游戏介绍

扫雷是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输
在这里插入图片描述


由于代码量比较多,我们采用多文件的方式来模拟实现扫雷方便后期修改维护,并将每个功能都封装成一个函数实现。

首先我们得先创建两个源文件和一个头文件

  1. test.c ------------------整体逻辑框架
  2. game.c----------------游戏过程实现
  3. game.h----------------存放函数声明

代码实现

game.c

先创建两个二维数组用来模拟扫雷的棋盘,创建数组的时候我们要注意,如果想在9*9的棋盘上进行扫雷游戏的话,为了防止在边上排雷的时候越界访问,我们创建真实数组的时候行和列就应该比打印出来的行列多两行
在这里插入图片描述

#define ROW 9	//打印的行
#define COL 9	//打印的列
#define ROWS ROW+2	//真实的行
#define COLS COL+2	//真实的列

mine数组用来雷的信息,show数组用来展示给玩家看的信息

	//储存雷的棋盘
	char mine[ROWS][COLS];
	//展示的棋盘
	char show[ROWS][COLS];

游戏开始先初始化棋盘,为了美观,我将mine的棋盘初始化成空格,show棋盘初始化为’*’。

	//初始化棋盘
	Initboard(mine, ROWS, COLS, ' ');
	Initboard(show, ROWS, COLS, '*');

代码如下

Initboard()

//初始化棋盘
void Initboard(char board[ROWS][COLS], int rows, int cols, char ret)
{
    
    
	//遍历数组初始化棋盘
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
    
    
		for (j = 0; j < cols; j++)
		{
    
    
			board[i][j] = ret;
		}
	}
}

初始化棋盘后,我们需要将用于展示的棋盘打印出以下的形状,为了方便确认坐标,我加上了横竖序号。
在这里插入图片描述
以下是打印棋盘的函数

	//打印棋盘
	Displayboard(show, ROW, COL);

Displayboard()

//打印棋盘
void Displayboard(char board[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;
	int j = 0;
	for (i = 0; i <= col; i++)
	{
    
    
		if (i == 0)
			printf("   ");
		else
		{
    
    
			printf(" %d  ", i);//打印坐标列号
		}
	}
	printf("\n");
	for (i = 0; i <= col; i++)
	{
    
    
		if (0 == i)
		{
    
    
			printf("   ");
		}
		else
		{
    
    
			printf("--- ");
		}
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
    
    
		printf("%d ", i);//打印坐标行号
		for (j = 1; j <= col; j++)
		{
    
    
			printf("|");
			printf(" %c ", board[i][j]);
		}
		printf("|");
		printf("\n");
		if (i <row)
		{
    
    
			printf("  ");	
			for (j = 1; j <=row; j++)
			{
    
    
				printf("|");
				printf("---");
			}
			printf("|");
			printf("\n");
		}
	}
	for (i = 0; i <= col; i++)
	{
    
    
		if (0 == i)
		{
    
    
			printf("   ");
		}
		else
		{
    
    
			printf("--- ");
		}
	}
	printf("\n");
}

开始布置雷,先定义一个宏表示雷的个数,然后我们在mine棋盘里面布置雷,这里我们用字符’1’来代表雷,这里需要注意的是,为了防止少雷的情况,我们传参传的是用于展示的行和列。

#define EASY_COUNT 10//雷
	//布置雷
	Mentmine(mine, ROW, COL, EASY_COUNT);

Mentmine()

布置雷的过程是随机的,所以我们用了rand这个库函数来实现生成随机数,不过rand这个库函数有个弊端,它生成的数并不是完全随机的,所以在此之前,我们需要在前面利用srand使用系统时间作为rand随机数的种子。

srand((unsigned int)time(NULL));//利用系统时间作为随机数的种子
//布置雷
void Mentmine(char board[ROWS][COLS], int row, int col, int count)
{
    
    
	while (count)
	{
    
    
		int x = rand() % row + 1;//生成1-row的坐标
		int y = rand() % col + 1;//生成1-row的坐标
		if (board[x][y] == ' ')
		{
    
    
			board[x][y] = '1';
			count--;
		}
	}
}

Finemine()

雷布置完后,就开始排雷了,由于技术有限,这里我就用坐标来代替鼠标操作,排雷的过程是个循环,直到排雷成功或者踩雷才会退出循环,在每次输入坐标的时候都要判断坐标是否合法,坐标合法的情况下才能进入扫雷的过程。

//排雷
Finemine(mine, show, ROW, COL);
//排雷
void Finemine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    
    
	int flag = 1;
	while (1)
	{
    
    
		char ch = 0;
		int x = 0;
		int y = 0;
		printf("请输入要排查的坐标:");
		scanf("%d%d", &x, &y);
		while (ch != '\n')//清空缓存区
		{
    
    
			ch = getchar();
		}
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
    
    
			
			if (Inspcoor(show, x, y))//检查坐标是否已经排过雷的函数。
			{
    
    
				printf("当前坐标已经排过雷,请重新输入!!!\n");
				continue;
			}
			if (mine[x][y] == '1')
			{
    
    
				system("cls");
				printf("非常抱歉,你踩到雷了\n");
				Displayboard(mine, ROW, COL);//打印雷的信息
				break;
			}
			else
			{
    
    
				int count = GetMinecont(mine, x, y);//统计坐标周围八个坐标有几个雷
				if (count != 0)
				{
    
    
					show[x][y] = count + '0';//存放周围有多少个雷的字符
				}
				else
				{
    
    
					show[x][y] = ' ';
					Openboard(mine, show, x, y);//没踩到雷则进行展开操作
				}
				system("cls");
				Displayboard(show, ROW, COL);
				if (Iswin(show, row, col) == EASY_COUNT)//判断输赢
				{
    
    
					system("cls");
					printf("恭喜你,排雷成功!!\n");
					Displayboard(mine, ROW, COL);//排雷成功后,把雷的布置打印在屏幕上
					break;
				}
			}
		}
		else
		{
    
    
			printf("坐标不合法,请重新输入!!!\n");
		}
	}
}

上面我们看到,在排雷的过程中我们是需要检查输入的坐标有没有被排过雷,这个比较简单,只需要判断那个坐标对应的是不是字符’*'就可以。

Inspcoor()

//检查输入的坐标是否排过雷。
Inspcoor(show, x, y);
//检查坐标是否已经排过雷
//返回1则排过
//返回0则没有排过
int Inspcoor(char show [ROWS][COLS], int x, int y)
{
    
    
	if (show[x][y] != '*')
	{
    
    
		return 1;
	}
	return 0;
}

GetMinecont()

在扫雷的过程中,我们需要知道这个坐标周围的八个当中有几个雷,或者没有雷。先定义一个计数器,然后遍历mine数组的周围八个坐标,看有没有雷,如果有计数器自增一次,最后返回计数器的个数,返回的个数加上字符’0’就能得到对应的字符数字。

//统计坐标周围八个坐标有几个雷
int GetMinecont(char board[ROWS][COLS], int x, int y)
{
    
    
	int count = 0;//计数器
	if (board[x - 1][y - 1] == '1')
		count++;
	if (board[x - 1][y] == '1')
		count++;
	if (board[x - 1][y + 1]=='1')
		count++;
	if (board[x][y - 1]=='1')
		count++;
	if (board[x][y + 1]=='1')
		count++;
	if (board[x + 1][y - 1]=='1')
		count++;
	if (board[x + 1][y]=='1')
		count++;
	if (board[x + 1][y + 1]=='1')
		count++;
	return count;
}

int count = GetMinecont(mine, x, y);//统计坐标周围八个坐标有几个雷
if (count != 0)
{
    
    
	show[x][y] = count + '0';//存放周围有多少个雷的字符
}
else
{
    
    
	show[x][y] = ' ';
	Openboard(mine, show, x, y);//没踩到雷则进行展开操作
}

如果返回的数是0,就代表当前坐标周围没有雷,则进行我们的递归展开操作

Openboard()

Openboard(mine, show, x, y);//没踩到雷则进行展开操作
//展开操作
void Openboard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
    
    
	if (GetMinecont(mine, x, y)==0)//判断周围是否有雷
	{
    
    
		show[x][y] = ' ';//把当前坐标设为空格
		//遍历当前坐标周围八个位置
		if (x > 0 && x <= ROW && y - 1 > 0 && y - 1 <= COL && show[x][y-1] == '*')
		{
    
    
			Openboard(mine, show, x, y - 1);
		}
		if (x - 1 > 0 && x - 1 <= ROW && y > 0 && y <= COL && show[x-1][y] == '*')
		{
    
    
			Openboard(mine, show, x-1, y);
		}
		if (x - 1 > 0 && x - 1 <= ROW && y + 1 > 0 && y + 1 <= COL && show[x-1][y + 1] == '*')
		{
    
    
			Openboard(mine, show, x - 1, y + 1);
		}
		if (x > 0 && x <= ROW && y - 1 > 0 && y - 1 <= COL && show[x][y - 1] == '*')
		{
    
    
			Openboard(mine, show, x, y - 1);
		}
		if (x > 0 && x <= ROW && y + 1 > 0 && y + 1 <= COL && show[x][y + 1] == '*')
		{
    
    
			Openboard(mine, show, x, y + 1);
		}
		if (x + 1 > 0 && x + 1 <= ROW && y - 1 > 0 && y - 1 <= COL && show[x+1][y - 1] == '*')
		{
    
    
			Openboard(mine, show, x+1, y - 1);
		}
		if (x + 1 > 0 && x + 1 <= ROW && y > 0 && y <= COL && show[x+1][y] == '*')
		{
    
    
			Openboard(mine, show, x+1, y);
		}
		if (x+1 > 0 && x+1 <= ROW && y + 1 > 0 && y+1 <= COL &&show[x+1][y+1] == '*')
		{
    
    
			Openboard(mine, show, x + 1, y+1);
		}
	}
	else//周围有雷的情况下//递归的出口
	{
    
    
		show[x][y] = GetMinecont(mine, x, y) + '0';//存放周围有多少个雷的字符
	}
}

最后就是判断游戏赢的条件

ISwin()

遍历当前的棋盘,如果返回的’*'号个数等于雷数,说明全部非雷区已经排完,则获得胜利,跳出游戏循环

if (Iswin(show, row, col) == EASY_COUNT)//判断输赢
{
    
    
		system("cls");
		printf("恭喜你,排雷成功!!\n");
		Displayboard(mine, ROW, COL);//排雷成功后,把雷的布置打印在屏幕上
		break;
}
//判断输赢
int Iswin(char show[ROWS][COLS], int row, int col)
{
    
    
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = 1; i <= row; i++)
	{
    
    
		for (j = 1; j <= col; j++)
		{
    
    
			if (show[i][j] == '*')
			{
    
    
				count++;
			}
		}
	}
	return count;
}

另外,为了有游戏体验感,防止出现玩家在第一次排雷的时候就被炸死的情况,所以设计了一个防止第一次被炸死的函数

Onemine()

//防止第一次踩到雷
Onemine(mine,show, ROW, COL);
//防止第一次踩到雷
void Onemine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    
    
	char ch = 0;
	int x = 0;
	int y = 0;
	while (1)
	{
    
    
		printf("请输入要排查的坐标:>");
		scanf("%d%d", &x, &y);
		while (ch != '\n')//清空缓存区
		{
    
    
			ch = getchar();
		}
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性
		{
    
    
			if (mine[x][y] == '1')
			{
    
    
				mine[x][y] = ' ';//补救第一次踩到雷
				Mentmine(mine, row, col, 1);//调用布置雷的函数重新布置一个
				int count = GetMinecont(mine, x, y);//统计坐标周围八个坐标有几个雷
				if (count != 0)
				{
    
    
					show[x][y] = count + '0';//存放周围有多少个雷的字符
				}
				else
				{
    
    
					show[x][y] = ' ';
				}
				system("cls");
				Displayboard(show, ROW, COL);
				break;//跳出循环
			}
			else//没踩到雷的情况下
			{
    
    
				int count = GetMinecont(mine, x, y);//统计坐标周围八个坐标有几个雷
				if (count != 0)
				{
    
    
					show[x][y] = count + '0';//存放周围有多少个雷的字符
				}
				else
				{
    
    
					show[x][y] = ' ';
					Openboard(mine, show, x, y);//没踩到雷则进行展开操作
				}
				system("cls");
				Displayboard(show, ROW, COL);
				break;//跳出循环
			}
		}
		else
		{
    
    
			printf("坐标不合法,请重新输入\n");
		}
	}
}

最后讲到这里就把我们整个游戏的逻辑给讲完了,把这些逻辑整合起来就是整个游戏框架。

test.c

#include"game.h"

void game()
{
    
    
	//储存雷的棋盘
	char mine[ROWS][COLS];
	//展示的棋盘
	char show[ROWS][COLS];
	//初始化棋盘
	Initboard(mine, ROWS, COLS, ' ');
	Initboard(show, ROWS, COLS, '*');
	//打印棋盘
	Displayboard(show, ROW, COL);
	//布置雷
	Mentmine(mine, ROW, COL, EASY_COUNT);
	//Displayboard(mine, ROW, COL);//查看布置好雷的布局
	//Displayboard(mine, ROW, COL);//查看第一次踩到雷的布局
	Onemine(mine,show, ROW, COL);//防止第一次踩到雷
	//Displayboard(mine, ROW, COL);//查看第一次踩到雷后,雷布置去哪的布局
	//排雷
	Finemine(mine, show, ROW, COL);
	system("pause");
}

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

int main()
{
    
    
	char ch = 0;
	int input = 0;
	srand((unsigned int)time(NULL));//利用系统时间作为随机数的种子
	do
	{
    
    
		menu();//菜单
		printf("请选择:>");
		scanf("%d", &input);
		while (ch != '\n')//清空缓存区
		{
    
    
			ch = getchar();
		}
		switch (input)
		{
    
    
		case 1:
			system("cls");
			game();
			system("cls");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default :
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

别忘了函数是需要声明的哦,我把函数声明都放在了自己创建的头文件当中,所以在使用函数的时候需要引用头文件,引用自己创建的头文件用""引用。

#include"game.h"

game.h

#include<stdio.h>
#include<stdlib.h>//rand srand
#include<time.h>//time

#define ROW 9	//打印的行
#define COL 9	//打印的列
#define ROWS ROW+2	//真实的行
#define COLS COL+2	//真实的列
#define EASY_COUNT 10//雷
//声明函数
//初始化
void Initboard(char board[ROWS][COLS], int rows, int cols, char ret);
//打印
void Displayboard(char board[ROWS][COLS], int row, int col);
//布置雷
void Mentmine(char board[ROWS][COLS], int row, int col,int count);
//防止第一次踩到雷
void Onemine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);
//排雷
void Finemine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);


总结

在此记录一个小白的成长过程,有什么写的不好的地方欢迎指出,有什么问题也可以私信问我,源代码链接

猜你喜欢

转载自blog.csdn.net/qq_45270751/article/details/113186439