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

纯C实现【扫雷小游戏】-- 超详完整版

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

纯C实现【扫雷小游戏】-- 超详完整版

扫雷是很多人的童年,它给我们带来了许多欢乐,但是如果你玩的扫雷是你自己写的,那么会有怎样的感受。来写写看吧!
完整代码在文章末尾哦!


文章目录
  • 一、基本思路
  • 二、功能实现
    • 1、分文件编程
    • 2、创建菜单
    • 3、游戏循环
    • 4、Game函数
      • a、初始化棋盘
      • b、打印棋盘
      • c、布置地雷
      • d、开始扫雷
        • 递归展开
        • 标记功能与取消标记
        • 输赢,继续的判断
  • 三、完整代码实现
    • Game.h
    • Game.c
    • main.c
  • 四、效果图
  • 五、结语

一、基本思路

首先需要弄清楚整体的思路,再去实现所要的功能!

为了使思绪更清晰,按照逻辑去敲代码,同时也便于修改与维护和功能实现与扩张,需要分文件编程,写为三个,后缀为 .h 的头文件 (函数声明),后缀为 .c 的源文件(函数实现),后缀为 .c 的main函数主源文件(程序运行的入口)

(1)、 写个简易的菜单,几乎所有的游戏都会有一个菜单,可以让玩家选择开始游戏或退出游戏。

(2)、 游戏循环进行,当玩家玩过一次游戏,不论是成功还是失败,都需要重新回到菜单选择继续还是退出。

(3)、 game函数,当玩家选择开始游戏时,需进入game函数中去扫雷了。

a、 构建两个棋盘,创建两个数组并将其初始化。
b、 打印棋盘,只打印一个。
c、 布置地雷,将雷布置到不打印的棋盘上。
d、 开始扫雷,玩家输入需排查的坐标。

  • 递归展开,若游戏继续,那么会将玩家所选的坐标向外展开直至周围有雷才停止并写上有几个。
  • 标记,玩家可以在棋盘上未扫的猜测是雷的坐标上做标记。
  • 取消标记,玩家可以在棋盘上已标记是雷的坐标上做取消标记的操作。
  • 判断输赢或继续直至判断出输赢。

(4)、 回到菜单。


二、功能实现 1、分文件编程

http://t.csdn.cn/fCshq这里有介绍!

2、创建菜单

在main.c源文件中用menu函数实现打印菜单,你可以自己设计的漂亮些。

void menu()
{
	printf("******************************n");
	printf("******  1、开始游戏    *******n");
	printf("******  0、退出游戏    *******n");
	printf("******************************n");
}

void menu2()
{
	printf("n*** a、排查 ***** s、标记 ***** d、取消标记 ***** f、退出 ********nn");	
}

3、游戏循环

我们知道进入游戏后会有一个菜单让你选择,并且菜单至少会出现一次,那么就可以在主程序文件main函数中用一个do…while循环,因为它一定会执行一次。
而在do…while中用switch语句来判断玩家的选择 ,若为 1 就进入game函数开始扫雷,游戏结束从函数出来break退出循环回到菜单;若为 0 则就直接break退出循环回到菜单;若玩家输入不会规范则default提示继续循环重新输入。


4、Game函数 a、初始化棋盘

棋盘的大小是由数组决定的,我们希望可以自己设定其大小,于是就宏定义行和列,通过改变宏定义 ROW 和 COL就可以直接改变到想要的尺寸。
多定义两个扩展的行列,因为要两个数组,一个布置雷的数组,另一个是排查雷的数组,但它们大小不同,原因是找雷的数量时会越界访问,行列加一就不会有这种情况发生,呈现给玩家看的时候,就只需打印除去了一行一列的排查雷的数组就行。

设定:
在没有布置雷时全部初始化为字符 0
在没有排查雷时全部初始化为字符*

遍历初始化数组元素即可。

//打印的行和列
#define ROW 9 
#define COL 9

//未打印的行和列
#define ROWS ROW + 2
#define COLS COL + 2
    //创建数组
	char hide[ROWS][COLS] = { 0 };//存放布置好雷的信息
	char show[ROWS][COLS] = { 0 };//存放排查出来雷的信息

	//初始化数组为指定内容
	InitBorad(hide,ROWS, COLS, '0');//在没有布置雷时全部初始化为0
	InitBorad(show,ROWS, COLS, '*');//在没有排查雷时全部初始化为*
//初始化二维数组为所传入的符号
void InitBorad(char arr[ROWS][COLS], int rows, int cols, char ch)
{
	int i = 0;
	int j = 0;
	//两层for循环遍历数组并将每个都赋值
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = ch;//将符号赋给每个元素
		}
	}


b、打印棋盘

在打印棋盘的时候,我们需要为行和列排序,玩家才能知道每个位置的坐标。可以先打印一行数字,再在遍历数组时每轮打印 i 即可。

//打印数组
void DisplayBorad(char arr[ROWS][COLS], int rows, int cols)
{
	printf("**** 【扫雷游戏】 *************n");
	int i = 0;
	int j = 0;
	int k = 0;

	//打印行的序号
	for (k = 0; k <= cols; k++)
	{
		printf("%d ", k);
	}
	printf("n");

	for (i = 1; i <= rows; i++) 
	{
		//打印列的序号
		printf("%d ", i);

		//遍历输出每个元素
		for (j = 1; j <= cols; j++)
		{

			printf("%c ", arr[i][j]);
		}
		printf("n");
	}
}

c、布置地雷

http://t.csdn.cn/BCIOb看随机数生成去这!

宏定义MIN为雷的个数,方便改动。

//布置雷的个数
#define MIN 10 

为算出随机数给定时间种子

srand((unsigned int)time(NULL));//为rand函数添加随机种子

获得两个随机数

//获取两个随机数 x,y ,范围 1 ~ 9
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;

将MIN传给DeployMine函数

d、开始扫雷

首先有一个功能选项,a、排查 ,s、标记 ,d、取消标记 , f、退出;与前面一样可以用do…while里面加个switch来实现,在这就不多说了,要注意的点是,SweepMine函数返回不同的值来判断输赢或继续!

void SweepMine(char hide[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
    
	do
	{
		char let = 0;
 		char select = '0';
		menu2();//菜单打印
		printf("请选择:> ");
		//玩家选择排查,标记,退出
		getchar();//清除缓存区
		scanf("%c", &select);
		switch (select)
		{
		case 'a':
			let = SweepMine(hide, show, ROW, COL);//排查地雷返回输赢或继续!
			break;//游戏结束,退出!
		case 's':
			SignMine(show,ROW, COL);//标记地雷
			break;
		case 'd':
			UnSignMine(show, ROW, COL);//取消标记地雷
			break;
		case 'f':
			break;//退出游戏!
		default:
			printf("输入有误,重新选择!n");//若玩家输入不会规范则继续循环重新输入。
		}
		if ('Y' == let || 'N' == let || select == 'f')
		{
			break;
		}
	} while (1);

	
	
}

递归展开

判断是否该坐标周围有雷,若有,那么将flag赋值为1;采用for循环遍历坐标周围的八个数,若若该坐标没有被排查过,且周围也没有雷,那么就递归调用自身,直至某个坐标有雷就停下,在此期间标记不是雷且排查过的坐标,避免死递归,如果该坐标是雷,将该坐标改为周围雷的数量,没有雷记为空。

void Unfold(char show[ROWS][COLS], char hide[ROWS][COLS], int x, int y, int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;//统计雷的数量
	int flag = 0;//

	//判断是否该坐标周围有雷,若有,那么将flag赋值为1
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (hide[i][j] == '1')
			{
				flag = 1;
				break;
			}
		}
	}
	//递归,若该坐标周围没有雷,那么就调用八次该函数,直至有雷就停下
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			//若该坐标没有被排查过,就进入if
			if (hide[i][j] != '#' && i >= 1 && i <= row && j >= 1 && j <= col)
			{
				//若该坐标不是雷,且周围也没有雷则进入
				if (hide[i][j] != '1' && flag == 0)
				{
					//如果该坐标被排查过,是安全的,那么就做一个标记,以后不在排查,避免死递归
					hide[i][j] = '#';
					//将该坐标展开
					Unfold(show, hide, i, j, row, col);
				}
				//该坐标不是雷
				else if (hide[i][j] == '1')
				{
					count++;//计算雷的数量
					continue;//排查下一个坐标
				}
			}
		}
	}
	//如果有雷则进入
	if (count > 0)
	{
		show[x][y] = count + '0';//将该坐标改为周围雷的数量
	}
	else
		show[x][y] = ' ';//没有雷记为空
}

标记功能与取消标记

标记与取消标记类似,这里之举一个例子;
如果该坐标为*,就改为!,否则输出:输入有误,重新输入!,并继续循环,直至改变就break退出!

void SignMine(char show[ROWS][COLS], int row, int col)
{
	do {
		int x = 0;
		int y = 0;
		printf("请选择要标记的坐标:> ");
		scanf("%d %d", &x, &y);
		if (show[x][y] == '*')
		{
			show[x][y] = '!';
			DisplayBorad(show, ROW, COL);
			break;
		}
		else
		{
			printf("输入有误,重新输入!n");
			continue;
		}
	} while (1);
}
输赢,继续的判断
  • 赢

若所有坐标为*和!符号为十个,那么返回 1,表示 赢了,否则返回表示还没赢,继续排查。

int MineCount(char hide[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int count = 0;
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{

		//遍历输出每个元素
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*' || show[i][j] == '!')
			{
				count++;
			}
		}
	}

	if (count == MIN)
	{
		return 1;
	}
	return 0;

}

Win函数值是1,进入if返回’N‘,表示赢了!

if (Win(hide, show, ROW, COL))
	{
		printf("n******所有雷排查成功,你赢了!  *********nn");
		return 'N';
	}
  • 输
    判断是雷返回’Y‘,表示输了
if (hide[x][y] == '1')
{
	printf("n****** 踩到雷了,你被炸死了!  *********n");
	return 'Y';
}
  • 继续

输赢都没发生返回’C‘,表示继续!

三、完整代码实现 Game.h
#pragma once

#include 
#include 
#include 

//布置雷的个数
#define MIN 10

//打印的行和列
#define ROW 9
#define COL 9

//未打印的行和列
#define ROWS ROW + 2
#define COLS COL + 2

void InitBorad(char arr[ROWS][COLS], int rows, int cols, char ch);

void DisplayBorad(char arr[ROWS][COLS], int rows, int cols);

void DeployMine(char arr[ROWS][COLS], int min);

char SweepMine(char hide[ROWS][COLS], char show[ROWS][COLS], int row, int col);

void SignMine(char show[ROWS][COLS], int row, int col);
void UnSignMine(char show[ROWS][COLS],int row,int col);

int MineCount(char hide[ROWS][COLS], char show[ROWS][COLS], int row, int col);
Game.c
#define _CRT_SECURE_NO_WARNINGS

#include "Game.h"

//初始化二维数组为所传入的符号
void InitBorad(char arr[ROWS][COLS], int rows, int cols, char ch)
{
	int i = 0;
	int j = 0;
	//两层for循环遍历数组并将每个都赋值
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = ch;//将符号赋给每个元素
		}
	}
}


//打印数组
void DisplayBorad(char arr[ROWS][COLS], int rows, int cols)
{
	printf("n**** 【扫雷游戏】 *************n");
	int i = 0;
	int j = 0;
	int k = 0;

	//打印行的序号
	for (k = 0; k <= cols; k++)
	{
		printf("%d ", k);
	}
	printf("n");

	for (i = 1; i <= rows; i++)
	{
		//打印列的序号
		printf("%d ", i);

		//遍历输出每个元素
		for (j = 1; j <= cols; j++)
		{
			printf("%c ", arr[i][j]);
		}
		printf("n");
	}
}


void DeployMine(char arr[ROWS][COLS], int min)
{
	//while循环用应布置雷的数量作为判断条件,min为0退出循环
	while (min)
	{
		//获取两个随机数 x,y ,范围 1 ~ 9
		int x = rand() % 9 + 1;
		int y = rand() % 9 + 1;

		//用字符‘0’代表不是雷,字符‘1’代表雷;
		//若 x,y 处不是雷,那就赋值‘1’将它变为雷,同时 应布置雷的数量min--;
		//若是雷,就不进入if语句中
		if (arr[x][y] == '0')
		{
			arr[x][y] = '1';
			min--;
		}
	}
}

void Unfold(char show[ROWS][COLS], char hide[ROWS][COLS], int x, int y, int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;//统计雷的数量
	int flag = 0;

	//判断是否该坐标周围有雷,若有,那么将flag赋值为1
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (hide[i][j] == '1')
			{
				flag = 1;
				break;
			}
		}
	}
	//递归,若该坐标周围没有雷,那么就调用八次该函数,直至有雷就停下
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			//若该坐标没有被排查过,就进入if
			if (hide[i][j] != '#' && i >= 1 && i <= row && j >= 1 && j <= col)
			{
				//若该坐标不是雷,且周围也没有雷则进入
				if (hide[i][j] != '1' && flag == 0)
				{
					//如果该坐标被排查过,是安全的,那么就做一个标记,以后不在排查,避免死递归
					hide[i][j] = '#';
					//将该坐标展开
					Unfold(show, hide, i, j, row, col);
				}
				//该坐标不是雷
				else if (hide[i][j] == '1')
				{
					count++;//计算雷的数量
					continue;//排查下一个坐标
				}
			}
		}
	}
	//如果有雷则进入
	if (count > 0)
	{
		show[x][y] = count + '0';//将该坐标改为周围雷的数量
	}
	else
		show[x][y] = ' ';//没有雷记为空
}

int Win(char hide[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int count = 0;
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		//遍历输出每个元素
		for (j = 1; j <= col; j++)
		{
			if (show[i][j] == '*' || show[i][j] == '!')
			{
				count++;
			}
		}
	}
	if (count == MIN)
	{
		return 1;
	}
	return 0;
}

char SweepMine(char hide[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	do {
		printf("请输入需排查的坐标:> ");
		//玩家输入坐标
		rewind(stdin);
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '*')
			{
				if (hide[x][y] == '1')
				{

					printf("n****** 踩到雷了,你被炸死了!  *********n");
					return 'Y';
				}
				else
				{
					Unfold(show, hide, x, y, ROW, COL);
					DisplayBorad(show, ROW, COL);//打印棋盘
					break;

				}
			}
			else
			{
				printf("该处已排查,重新输入!n");
				continue;
			}
		}
		else
		{
			printf("输入有误,重新输入!n");
			continue;

		}
			
	} while (1);

	
	if (Win(hide, show, ROW, COL))
	{
		printf("n******所有雷排查成功,你赢了!  *********nn");
		return 'N';
	}

	return 'C';
}


void SignMine(char show[ROWS][COLS], int row, int col)
{
	do {
		int x = 0;
		int y = 0;
		printf("请选择要标记的坐标:> ");
		scanf("%d %d", &x, &y);
		if (show[x][y] == '*')
		{
			show[x][y] = '!';
			DisplayBorad(show, ROW, COL);
			break;
		}
		else
		{
			printf("输入有误,重新输入!n");
			continue;
		}
	} while (1);
}

void UnSignMine(char show[ROWS][COLS], int row, int col)
{
	do {
		int x = 0;
		int y = 0;
		printf("请选择要取消标记的坐标:> ");
		scanf("%d %d", &x, &y);
		if (show[x][y] == '!')
		{
			show[x][y] = '*';
			DisplayBorad(show, ROW, COL);
			break;
		}
		else
		{
			printf("输入有误,重新输入!n");
			continue;
		}
	} while (1);
}
main.c
#define _CRT_SECURE_NO_WARNINGS

#include "Game.h"

void menu()
{
	printf("n******************************n");
	printf("******  1、开始游戏    *******n");
	printf("******  0、退出游戏    *******n");
	printf("******************************nn");
}

void menu2()
{
	printf("n*** a、排查 ***** s、标记 ***** d、取消标记 ***** f、退出 ********nn");	
}

void Game()
{

	//创建数组
	char hide[ROWS][COLS] = { 0 };//存放布置好雷的信息
	char show[ROWS][COLS] = { 0 };//存放排查出来雷的信息

	//初始化数组为指定内容
	InitBorad(hide,ROWS, COLS, '0');//在没有布置雷时全部初始化为0
	InitBorad(show,ROWS, COLS, '*');//在没有排查雷时全部初始化为*


	//打印两个棋盘
    //DisplayBorad(hide, ROW, COL);//不打印布置地雷的数组
	DisplayBorad(show, ROW, COL);//打印玩家排查雷的数组


	//布置地雷
	DeployMine(hide,MIN);
	//DisplayBorad(hide, ROW, COL);

	do
	{
		char let = 0;
 		char select = '0';
		menu2();//菜单打印
		printf("请选择:> ");
		//玩家选择排查,标记,退出
		getchar();
		scanf("%c", &select);
		switch (select)
		{

		case 'a':

			let = SweepMine(hide, show, ROW, COL);//排查地雷返回输赢或继续!
			break;//游戏结束,退出!
		case 's':
			SignMine(show,ROW, COL);//标记地雷
			break;
		case 'd':
			UnSignMine(show, ROW, COL);//取消标记地雷
			break;
		case 'f':
			break;//退出游戏!
		default:
			printf("输入有误,重新选择!n");//若玩家输入不会规范则继续循环重新输入。
		}
		if ('Y' == let || 'N' == let || select == 'f')
		{
			break;
		}
	} while (1);
}

int main()
{
	int choice = 0;//用整型变量choice来存储玩家的选择
	do
	{
		srand((unsigned int)time(NULL));//为rand函数添加随机种子
		menu();//菜单打印
		printf("请选择:> ");
		scanf("%d", &choice);//玩家选择开始或者退出!
		switch (choice)
		{
			case 1:
				Game();//进入game函数,开始扫雷!
				break;//游戏结束,退出!
			case 0:
				break;//退出游戏!
			default:
				printf("输入有误,重新选择!n");//若玩家输入不会规范则继续循环重新输入。
		}
	} while (choice);//判断玩家的选择。
	return 0;
}
四、效果图

五、结语

看到最后的兄弟姐妹们,三连支持一下,求求了!

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

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

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