纯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、退出 ********\n\n");	
}

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******所有雷排查成功,你赢了!  *********\n\n");
		return 'N';
	}

  • 判断是雷返回’Y‘,表示输了
if (hide[x][y] == '1')
{
	printf("\n****** 踩到雷了,你被炸死了!  *********\n");
	return 'Y';
}
  • 继续

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

三、完整代码实现

Game.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//布置雷的个数
#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******所有雷排查成功,你赢了!  *********\n\n");
		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("******************************\n\n");
}

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

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;
}

四、效果图

在这里插入图片描述

五、结语

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

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值