栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【C语言】扫雷2.0完善版——最接近原版的扫雷

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【C语言】扫雷2.0完善版——最接近原版的扫雷

今天我们用C语言实现一个扫雷游戏,升级版哦

可以展开,标记,取消标记,记录已排查坐标数。

直接来看看效果吧

除了没用鼠标点以外,是很接近原版的。

目录

扫雷主体思路

扫雷游戏主体的实现

 1.游戏菜单的实现

 2.初始化棋盘/雷盘

 3.打印棋盘/雷盘

 4.布置雷 

 5.排查雷/标记雷的实现

5.1 排查雷

 5.2 标记雷/取消标记

5.3 判断输赢和显示当前已排查的坐标

           6.展开雷

生成Release版         

全部代码

game.c

game.h

tese.c





                                        扫雷主体思路

需要实现扫雷游戏,我们可以先看看别人的扫雷是如何实现的, 

 可以看出,棋盘是具有两层的,第一层是我们想让玩家直接可以看到的,第二层则是隐藏起来的,存放的是雷。

所以我们需要生成两个二维数组,一个二维数组,用来存放棋盘,另一个二维数组用来存放雷(可以称为雷盘)。

棋盘初始化为*

雷盘初始化为0 然后随机放入雷(随机就可以想到用rand函数)

然后再进行排查雷,展开雷区等功能的实现。

可能有点抽象,我们来实现看看吧。等实现完就会觉得其实并不难啦,很好理解的。


                                    扫雷游戏主体的实现

我们先创建三个文件

 1.游戏菜单的实现

我们需要打印一个简单的游戏菜单,就像我们平时玩游戏也是有菜单的,让玩家选择。

使用do while循环语句与switch 选择语句 实现

代码实现如下:

void menu()
{
    //  33[34m 33[0m 是实现字体颜色 34代表蓝色 31代表红色
	printf("*********************n");
	printf("******33[34m 1.play33[0m *******n");
	printf("******33[31m 0.exit33[0m *******n");
	printf("*********************n");
}
void test()
{
   int input = 0;
	do
	{
		//菜单
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		system("cls");//输入后清理屏幕
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏n");
			break;
		default:
			printf("选择错误!n");
			break;
		}
	} while (input);
}

int main()
{
	test();
	return 0;
}

这里的菜单 1.play   0.exit 字体颜色 进行了设置,我觉得这样看起来有趣一些,不那么的枯燥。

2.初始化棋盘/雷盘

我们需要创建两个char类型二维数组

一个用于存放展示给玩家看的棋盘,另一个用于存放雷。

为了方便后期的更改,我们可以用#define 定义常量 作为数组大小。

我们创建个9*9的棋盘吧

 但是这里有一个问题,如果我们创建 9*9的,想要展示给玩家看的就是9*9的棋盘,如果玩家要查找棋盘边缘的坐标,周围的雷的数量,

像上面图中那样,就会出现数组越界访问的问题,所以我们最好把数组创建大一点,打印的时候仍然打印9*9的,但是创建数组,可以创建11*11的这样就避免了数组越界的情况。

所以可以这样定义

 现在来看看代码实现:

test.c里
//扫雷游戏的实现
void game()
{
	//创建棋盘
	//mine数组是用来存放布置好的雷的信息
	char mine[ROWS][COLS] = { 0 };//'0'
	//show数组是存放排查出的雷的信息
	char show[ROWS][COLS] = { 0 };// '*'
	//创建棋盘初始化
	InisBoard(mine, ROWS, COLS,'0');//传个字符'0'过去,代表把雷盘初始化为全'0'
	InisBoard(show, ROWS, COLS, '*'); //传个字符'*'过去,代表把雷盘初始化为全'0'
	
}

game.c里

//初始化mine雷盘/show棋盘 都可以调用这个函数实现
void InisBoard(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;
		}
	}
}

gama.h里

#define ROW 9  // 展示给玩家看的行
#define COL 9  //

#define ROWS ROW+2  //实际创建的行
#define COLS COL+2

//初始化雷盘的声明
void InisBoard(char arr[ROWS][COLS], int rows, int cols,char set);

头文件别忘了引用哦,我代码块里没有加进去,但是文章末尾的完整代码里都会有的。

3.打印棋盘/雷盘

实际游戏中雷盘不会打印给玩家看的。 

为了方便玩家识别行列位置,我们可以加上打印行号 和列号

为了更加接近平时玩的扫雷,我设置了不同数字以不同颜色显示,1 蓝色 2白色 3黄色 标记(!)雷红色显示

来看看代码吧:

game.c中

//打印mine雷盘/show 棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷-------n");
	int i = 0;
	int j = 0;
	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++)
		{
			if (board[i][j] == '!')//!代表标记了的雷 红色显示  游戏结束后雷蓝色显示 
			{
					printf("33[31m%c 33[0m", board[i][j]);
					continue;
			}
			else if (board[i][j] == '1')
			{
				printf("33[34m%c 33[0m", board[i][j]);//蓝色显示
				continue;
			}
			else if (board[i][j] == '3')
			{
				printf("33[32m%c 33[0m", board[i][j]);//黄色显示 
				continue;
			}
			printf("%c ", board[i][j]);
		}
		printf("n");
	}
	printf("--------扫雷-------n");
}

game.h中
//打印函数的声明
void DisplayBoard(char show[ROWS][COLS], int row, int col);


 我们来看看棋盘效果

4.布置雷 

 我们雷盘也初始化为全'0'了,我们现在需要布置雷了,可以把'1'作为雷

随机把'1'赋值到雷盘上9*9的格子里,我们要布置10个雷 就随机赋值10个1在雷盘上。

雷的数量也可以用#define 定义,方便后面更改。

game.c中
//布置雷
void Set_Mine(char mine[ROWS][COLS], int rows, int cols)
{
	int x = 0;
	int y = 0;
	int count1 = count;//count是define定义的常量 需要先赋值给一个变量 才能改
	while (count1)
	{
		x = rand() % rows+1;
		y = rand() % cols+1; //1-9 取值范围之间
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count1--;
		}
	}
}

game.h中

#define count 10 //代表10个雷

//布置雷函数的声明
void Set_Mine(char mine[ROWS][COLS], int rows, int cols)

我们调用打印函数 打印雷盘看看效果

5.排查雷/标记雷的实现

5.1 排查雷

雷已经布置了,现在就可以来进行游戏,排查雷

思路是这样的

我们输入一个坐标(x,y),如果这个坐标是雷则提示被炸死了,就返回到菜单,

如果不是雷,就统计周围8个坐标内雷的个数,相加,然后赋值给该坐标,如果周围坐标都不是雷,就把' '空白 赋值给该坐标。

如果周围8个坐标有雷,那我们把8个坐标相加然后再减8*字符'0'  得到周围雷的个数

我们可以写一个get_mine函数实现这个功能。

 5.2 标记雷/取消标记

我们需要标记雷,可以创建一个char类型的的临时变量 a,然后输入坐标后再输入

当 a=='f' 表示排查该坐标   a=='s'时 表示标记该坐标  a=='c'时 表示该坐标取消标记

5.3 判断输赢和显示当前已排查的坐标

我这里是在game.c里定义了一个全局变量sum 用于统计每次排查坐标后sum++,最后总坐标(ROW*COL)-count==sum 代表雷排完了,也就是获胜了。

每次在输入要排查的坐标之前都判断一次,并且实时打印当前排查了的坐标数量。

当然这里判断输赢可以再写一个判断输赢函数去遍历整个棋盘,但是我们这里就用个全局变量统计,就没有再去写判断输赢函数。

整体代码如下:

game.c中

void find_mine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	char a = 0;//标记雷:s 取消标记:c  排查雷:f
	while(1)
  {
		printf("已排查坐标数量:33[36m%d 33[0mn", sum);//事实显示已经排查了的坐标数量
		if (sum==ROW*COL-count)//判断输赢   已排查的坐标数-雷
		{
			
			DisplayBoard(mine, ROW, COL);//打印雷的位置
			printf("33[31m 恭喜您扫雷成功!n33[0mn");//游戏结束红色显示
			return;
		}
	printf("请输入要排查坐标以f结束/要标记的雷以s结束:>");
	scanf("%d %d %c", &x, &y,&a);
	if (x >= 1 && x <= row && y >= 1 && y <= col && a=='f')
	{
		int i = 0;
		int j = 0;
		if (mine[x][y] == '0'&&show[x][y]=='*')// 即不是雷 又没被排查过
		{
			expand_mine(show,mine, x, y);//展开雷
			system("cls");//每次打印棋盘之前清理屏幕
			DisplayBoard(show, ROW, COL);
		}
		else if (show[x][y] != '*')
		{
			printf("该坐标已经排查过了,请重新输入n");
		}
		else//只能是mine[x][y]=='1'的时候(即是雷)
		{
			printf("你被炸死了n");
			DisplayBoard(mine, ROW, COL);//并且给玩家展示一下雷盘 让他知道是怎么输的
			break;
		}
	}
	else if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*' && a == 's') //把雷标记为!
	{
		show[x][y] = '!';
		system("cls");
		DisplayBoard(show, ROW, COL);
		printf("坐标已经标记,输入坐标以c结尾可取消标记n");
	}
	else if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '!' && a == 'c')// 取消标记
	{
		show[x][y] = '*';
		system("cls");
		DisplayBoard(show, ROW, COL);
	}
	 else if(x >= 1 && x <= row && y >= 1 && y <= col)
	{
		printf("坐标已排查或标记请重新输入n");
	}
	 else
	{
		printf("坐标非法,请重输入");
	}
   }
}

上面代码里expand_mine 是展开雷的函数,马上就会讲到,get_mine 获取周围雷的个数的函数 也是在expand_mine函数里调用。

还运用到了sysem("cls")每次打印棋盘之前先清理屏幕。

6.展开雷

我们如果只运用get_mine函数每次只能排查一个坐标,效率很慢,无法像平时我们玩的扫雷那样

一点展开一大片

 那我们现在就来实现他,我们仔细观察这个图,可以发现只要周围没有雷就继续展开,遇到周围8个坐标内有雷才停止展开。那我们就可以运用递归,来实现这个函数。

当排查的坐标x,y周围没有雷时,我们就排查他周围的8个坐标是否有雷,这样递归下去,直到周围坐标有雷就停止递归返回回去。这里的排查周围8个坐标我写了个循环实现,就显得代码量少点。

但是要注意的点是,1.排查过的坐标不再排查。 2.如果是标记了的坐标也是要排查的。3.x,y的取值范围最好是在1-9之间,这样方便后面我们统计排查的次数。

现在来看看代码吧

game.c中
void expand_mine(char show[ROWS][COLS],char mine[ROWS][COLS],int x,int y)
{
	if (x == 0 || y == 0 || y == COLS - 1 || x == ROWS - 1)//如果不用这个边界判断条件 排查的次数就会是ROWS*COLS 并且会导致随着雷的位置如果在边缘 排查次数就会相对来说少点,排查次数会不固定
	{
		return;
	}
	if (get_mine(show,mine,x,y)!=0 && (show[x][y] == '*' || show[x][y] == '!') && mine[x][y] != '1')    //!=0是指周围8个坐标没有雷  show[x][y]=='*'是指没有被排查过 并且被标记了也会排查  
	{
		show[x][y] = get_mine(show,mine,x,y) + '0';
		sum++; //统计排查次数  每次排查过后sum++
		return ;
	}
	else if((show[x][y] == '*'||show[x][y]=='!')&&mine[x][y]!='1') //  //解决c6385 读取数据无效 就加个对x y的限制语句  !='*'是表示已经被排查过的 就直接return 不排查了
	{
		show[x][y] = ' ';
		sum++;//每次排查过后sum++
		int i = 0;
		int j = 0;
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{
				if (j == 0 && i == 0)
				{
					continue;// 当 i=0 j=0 会再次重复执行expand_mine 所以直接跳到下一个
				}
				expand_mine(show, mine, x + i, y + j);
			}
		}
	}
}

来看看实现效果


                                          生成Release版

现在我们已经实现了全部功能,可以发给好友玩玩试玩试玩。

如果用VS的小伙伴,可以生成Release版 然后就会在本地生成一个Release文件,然后去找到里面的.exe文件,就可以直接发给朋友玩了。

 好啦,今天就分享到这里,文中可能有些地方表达不够清楚,请见谅。谢谢大家的支持

全部完整代码会贴在文章末尾,有兴趣的小伙伴可以看看。

                                               

                                             全部代码

game.c
#include"game.h"
int sum = 0;//统计被排查了的坐标数量
//
//初始化mine雷局/show棋盘 都可以调用这个函数实现
void InisBoard(char board[ROWS][COLS],int rows,int cols,char set)
{
	sum = 0;//每局游戏把sum重置成0 
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

//计时器//功能待完善

//打印mine雷盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷-------n");
	int i = 0;
	int j = 0;
	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++)
		{
			if (board[i][j] == '!')//标记了的雷红色显示  游戏结束后雷红色显示 
			{
					printf("33[31m%c 33[0m", board[i][j]);
					continue;
			}
			else if (board[i][j] == '1')
			{
				printf("33[34m%c 33[0m", board[i][j]);//蓝色显示
				continue;
			}
			else if (board[i][j] == '3')
			{
				printf("33[32m%c 33[0m", board[i][j]);//黄色显示 
				continue;
			}
			printf("%c ", board[i][j]);
		}
		printf("n");
	}
	printf("--------扫雷-------n");
}

//布置雷
void Set_Mine(char mine[ROWS][COLS], int rows, int cols)
{
	int x = 0;
	int y = 0;
	int count1 = count;
	while (count1)
	{
		x = rand() % rows+1;
		y = rand() % cols+1; //
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count1--;
		}
	}
}
//递归展开雷


int get_mine(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
	return (mine[x][y + 1] +
		mine[x][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] +
		mine[x + 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x - 1][y - 1] - 8 * '0');
}
//递归实现展开雷
void expand_mine(char show[ROWS][COLS],char mine[ROWS][COLS],int x,int y)//字符0减去字符0 ==数字1   字符1减去字符1==数字1...
{
	if (x == 0 || y == 0 || y == COLS - 1 || x == ROWS - 1)//如果不用这个边界判断条件 排查的次数就会是ROWS*COLS 并且会导致随着雷的位置如果在边缘 排查次数就会相对来说少点,排查次数会不固定
	{
		return;
	}
	if (get_mine(show,mine,x,y)!=0 && (show[x][y] == '*' || show[x][y] == '!') && mine[x][y] != '1')    //!=0是指周围8个坐标没有雷  show[x][y]=='*'是指没有被排查过 并且被标记了也会排查  
	{
		show[x][y] = get_mine(show,mine,x,y) + '0';
		sum++; //每次排查过后sum++
		return ;
	}
	else if((show[x][y] == '*'||show[x][y]=='!')&&mine[x][y]!='1') //  //解决c6385 读取数据无效 就加个对x y的限制语句  !='*'是表示已经被排查过的 就直接return 不排查了
	{
		show[x][y] = ' ';
		sum++;//每次排查过后sum++
		int i = 0;
		int j = 0;
		for (i = -1; i <= 1; i++)
		{
			for (j = -1; j <= 1; j++)
			{
				if (j == 0 && i == 0)
				{
					continue;//i=0 j=0 会再次执行expand_mine 所以直接跳到下一个
				}
				expand_mine(show, mine, x + i, y + j);
			}
		}
	}
}
//排查雷
void find_mine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	char a = 0;//标记雷:s 取消标记:c  排查雷:f
	while(1)
  {
		printf("已排查坐标数量:33[36m%d 33[0mn", sum);//事实显示已经排查了的坐标数量
		if (sum==ROW*COL-count)//判断输赢   已排查的坐标数-雷
		{
			//system("cls");//
			DisplayBoard(mine, ROW, COL);//打印雷的位置
			printf("33[31m 恭喜您扫雷成功!n33[0mn");//游戏结束红色显示
			return;
		}
	printf("请输入要排查坐标以f结束/要标记的雷以s结束:>");
	scanf("%d %d %c", &x, &y,&a);
	if (x >= 1 && x <= row && y >= 1 && y <= col && a=='f')
	{
		int i = 0;
		int j = 0;
		if (mine[x][y] == '0'&&show[x][y]=='*')// 即不是雷 又没被排查过
		{
			expand_mine(show,mine, x, y);//sum这里需要传址 因为要改变它的值	
			system("cls");//每次打印下次屏幕之前清理屏幕
			DisplayBoard(show, ROW, COL);
		}
		else if (show[x][y] != '*')
		{
			printf("该坐标已经排查过了,请重新输入n");
		}
		else//只能是mine[x][y]=='1'的时候(即是雷)
		{
			printf("你被炸死了n");
			DisplayBoard(mine, ROW, COL);//并且给玩家展示一下雷盘 让他知道是怎么输的
			break;
		}
	}
	else if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '*' && a == 's') //把雷标记为!
	{
		show[x][y] = '!';
		system("cls");
		DisplayBoard(show, ROW, COL);
		printf("坐标已经标记,输入坐标以c结尾可取消标记n");
	}
	else if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y] == '!' && a == 'c')// 取消标记
	{
		show[x][y] = '*';
		system("cls");
		DisplayBoard(show, ROW, COL);
	}
	 else if(x >= 1 && x <= row && y >= 1 && y <= col)
	 {
		printf("坐标已排查或标记请重新输入n");
	 }
	 else
	{
		printf("坐标非法,请重输入");
	}
   }
}

game.h
#pragma once
#include
#include
#include
#include


#define ROW 9  // 展示给玩家看的行
#define COL 9  //

#define ROWS ROW+2  //实际创建的行
#define COLS COL+2

#define count 10 //雷的数量


//初始化雷盘/棋盘
void InisBoard(char board[ROWS][COLS], int rows, int cols,char set);
//打印
void DisplayBoard(char show[ROWS][COLS], int row, int col);
//布置雷
void Set_Mine(char mine[ROWS][COLS], int row, int col);
//排查雷
void find_mine(char show[ROWS][COLS],char mine[ROWS][COLS], int row, int col);
//展开雷(递归实现)
void expand_mine(char show[ROWS][COLS], char mine[ROWS][COLS],int x,int y);

tese.c
#define  _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//菜单
void menu()
{
	printf("*********************n");
	printf("******33[34m 1.play33[0m *******n");//33[34m 33[0m 是实现字体颜色  34代表蓝色 31代表红色
	printf("******33[31m 0.exit33[0m *******n");
	printf("*********************n");
}

//扫雷游戏的实现
void game()
{
	//创建棋盘
	//mine数组是用来存放布置好的雷的信息
	char mine[ROWS][COLS] = { 0 };//'0'
	//show数组是存放排查出的雷的信息
	char show[ROWS][COLS] = { 0 };// '*'
	//创建棋盘初始化
	InisBoard(mine, ROWS, COLS,'0');
	InisBoard(show, ROWS, COLS, '*');
	//布雷
	Set_Mine(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);
	//排查雷
   find_mine(show,mine,ROW,COL);

}

void test()
{

	int input = 0;
	do
	{
		//菜单
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		system("cls");//输入后清理屏幕
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏n");
			break;
		default:
			printf("选择错误!n");
			break;
		}

	} while (input);
}

int main()
{
	srand((unsigned)time(NULL));//不能写在布置雷的循环里,写进去就很慢每次都要生成时间戳
	system("mode con cols=25 lines=25");//设置窗口宽度高度
	test();//函数的实现
	return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/868926.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号