【C语言】扫雷游戏(可展开)——超细教学

🚩纸上得来终觉浅, 绝知此事要躬行。
🌟主页:June-Frost
🚀专栏:C语言

🔥该篇将运用数组来实现 扫雷游戏。

目录:

  • 🌟思路框架
    • 测试
    • 游戏
  • 🌟测试部分函数实现
  • 🌟 游戏部分函数实现
  • 🌟完整的代码
  • ❤️ 结语

🌟思路框架

测试

通过迭代保证每次玩完游戏后可以再来一局或者退出。


游戏

说明:

1 .  实现扫雷,首先需要布置雷,通过一个二维数组就可以很容易的将雷的信息存储下来。只需要将二维数组的元素赋予两个不同的值,例如,’1’ ——雷,’0‘ ——无雷,或者 ‘#’ ——雷,’ * ’ ——无雷,只不过这两种在后续排查雷中操作不同(通过一个循环前者可以加起来计算出周围有几个雷,后者可以通过比较来判定有多少雷)。


 如上图,对于 排查雷(周围有多少雷),如果只有一个数组,那么在周围如果只有一个雷的情况下(’1‘——表示雷,’0‘——表示无雷),该位置的数组元素就会被赋值为1,在最后展示棋盘的时候,这个1就会造成歧义,就会不知道这个’1‘究竟是雷,还是周围雷的个数。
所以,通过建立另一个数组,来记录周围雷的数量。
如图:

3.

但是我们可以将其逻辑统一:

  1.  为了简便操作,我们将两个数组规定为同类型同大小(两个都扩充一圈)。
  • 优点1:可以方便数组操作,下标一样,不需要重新计算对应数组元素的下标。
  • 优点2:同类型意味着可以使用同一个函数,不需要一个功能写两次。

每个部分都是单独的函数体,下面将会单独对每个函数体进行实现。


🌟测试部分函数实现

  • 选择是否玩游戏

 与三子棋一样,该板块的循环部分将会使用do while 循环来实现(保证一开始可以选择,运行完游戏部分后还可以继续选择)。

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

int main()
{
	int input = 0;
	do
	{
		menu();// 菜单
		printf("请选择:> ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();//游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入非法,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

🌟 游戏部分函数实现

 首先我们根据游戏部分的思路框架,将整个游戏的逻辑写在game()函数中。
 前提:

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

定义两种,可以在后续分别用于不同的函数。

  • 游戏逻辑
void game()
{
	char mine[ROWS][COLS] = { 0 };//存放雷
	char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
	//初始化
	InitBoard(mine, ROWS, COLS, '0');//都先初始化为无雷
	InitBoard(show, ROWS, COLS, '*');
	//布置雷
	SetMine(mine, ROW, COL);
	//打印第二个棋盘
	DisplayBoard(show, ROW, COL);
	//排查雷
	FineMine(mine, show, ROW, COL);
}
  • 初始化

 由于想使用一个函数就可以将两个棋盘初始化,所以在参数上需要增加一个变量用于接收初始化的值。

void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;
		}
	}
}
  • 布置雷

 前提:#define Set_Mine 10;//需要布置雷的数量
 布置雷是随机的,所以我们采用伪随机数,将下标在合法范围内随机,如果该坐标下是无雷的,就赋值‘1’,定为有雷。

void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = Set_Mine;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x][y] == '0')
			{
				board[x][y] = '1';
				count--;//布置成功,雷数减少1
			}
		}
	}
}
  • 打印棋盘

 为了方便玩家定位坐标,在打印棋盘的时候,可以将行和列打印出来。并且展示出来的,是中间的区域(不展示扩容区域)

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷游戏-------\n");
	int i = 0;
	for (i = 0; i <= col; i++)//打印列
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}
  • 排查雷

计数:

int GetMineCount(char board[ROWS][COLS], int x, int y)
{
	int i = x - 1;
	int j = y - 1;
	int ret = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			ret += board[i][j];
		}
	}
	return ret - 9 * '0';
}

排雷:

void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
	int win = 0;//如果要赢 需要的有检查的次数,例如:9*9棋盘,有10个雷,检查出71个地区不是雷,就意味着获胜。
	while (win<ROW*COL-Set_Mine)
	{
		int x = 0;
		int y = 0;
		printf("请输入坐标,中间用空格:> ");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法
		{
			//踩到雷
			if (mine[x][y] == '1')
			{
				printf("很遗憾,已踩到雷\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			//没猜到雷
			else
			{
				Unfold(mine,show, ROW, COL, x, y,&win);//递归展开
				DisplayBoard(show, ROW, COL);//展示棋盘
			}
		}
		else
		{
			printf("输入非法,需要重新输入\n");
		}

	}
	if (win == ROW * COL - Set_Mine)
	{
		printf("游戏获胜\n");
		DisplayBoard(mine, ROW, COL);
	}
}

展开:

如何实现上面的展开效果呢?
当我们点击一个坐标后,看是否满足3个条件:
1.这个坐标不是雷(如果按照博主的排雷逻辑,这个条件不需要判断,因为坐标下不是雷的时候才会调用展开函数)
2.周围没有雷
3.没有被递归过(防止死递归)
 这时被点击的地方就可以被赋值为空,并对周围的8个区块看是否符合这样的条件,8个区块也按照这个逻辑,如果满足三个条件,被赋空并且遍历周围,如果不满足就显示周围有多少雷,或者不做任何反应,按照这样的逻辑一直延续下去。这种情况我们可以使用递归去解决。

void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col,int x,int y, int* count_w)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)//判断合法
	{
		if (!GetMineCount(mine, x, y) && show[x][y] != ' ')//周围没有雷并且该位置没有被递归过
		{
			show[x][y] = ' ';
			int i = 0;
			int j =0;
			int ret = 0;
			//直接遍历9个,至于中间的那个坐标,最后一个else if 会处理
			for (i = x - 1; i <= x + 1; i++)
			{
				for (j = y - 1; j <= y + 1; j++)
				{
					Unfold(mine, show, row,col,i, j, count_w);
					(* count_w)++;
				}
			}
		}
		else if (GetMineCount(mine, x, y))//周围有雷
		{
			int count = GetMineCount(mine, x, y);
			show[x][y] = count + '0';
			(* count_w)++;
		}
		else if (show[x][y] == ' ')//被递归过
		{
			;//不需要操作
		}
	}
	//不合法的函数不需要任何处理
}

这里需要判断合法性,因为扩容出去的那一圈的元素不可以递归,而且也不可以计算 (不然按照GetMineCount的逻辑会越界访问,而且就会可以计算出,也不会打印这个区域,所以可以直接不用管,递归这些地方的时候,函数会什么都不处理)。


🌟完整的代码

game .h

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

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define Set_Mine 10//需要布置雷的数量
//游戏逻辑
void game();
//初始化
void InitBoard(char board[ROWS][COLS], int row, int col, char set);
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col);
//展示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col); 
//排雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game .c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//菜单
void menu()
{
	printf("***********************\n");
	printf("*****   1. play   *****\n");
	printf("*****   0. exit   *****\n");
	printf("***********************\n");
}

//初始化
void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;
		}
	}
}
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
	int count = Set_Mine;
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x][y] == '0')
			{
				board[x][y] = '1';
				count--;//布置成功,雷数减少1
			}
		}
	}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("--------扫雷游戏-------\n");
	int i = 0;
	for (i = 0; i <= col; i++)//打印列
	{
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}
//计数
int GetMineCount(char board[ROWS][COLS], int x, int y)
{
	int i = x - 1;
	int j = y - 1;
	int ret = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			ret += board[i][j];
		}
	}
	return ret - 9 * '0';
}

//展开
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col,int x,int y, int* count_w)
{
	if (x >= 1 && x <= row && y >= 1 && y <= col)
	{
		if (!GetMineCount(mine, x, y) && show[x][y] != ' ')//周围没有雷并且该位置没有被递归过
		{
			show[x][y] = ' ';
			int i = 0;
			int j =0;
			int ret = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				for (j = y - 1; j <= y + 1; j++)
				{
					Unfold(mine, show, row,col,i, j, count_w);
					(* count_w)++;
				}
			}
		}
		else if (GetMineCount(mine, x, y))//周围有雷
		{
			int count = GetMineCount(mine, x, y);
			show[x][y] = count + '0';
			(* count_w)++;
		}
		else if (show[x][y] == ' ')//被递归过
		{
			;
		}
	}
}
//排雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
	int win = 0;
	while (win<ROW*COL-Set_Mine)
	{
		int x = 0;
		int y = 0;
		printf("请输入坐标,中间用空格:> ");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//踩到雷
			if (mine[x][y] == '1')
			{
				printf("很遗憾,已踩到雷\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			//没猜到雷
			else
			{
				Unfold(mine,show, ROW, COL, x, y,&win);//递归展开
				DisplayBoard(show, ROW, COL);//展示棋盘
			}
		}
		else
		{
			printf("输入非法,需要重新输入\n");
		}

	}
	if (win == ROW * COL - Set_Mine)
	{
		printf("游戏获胜\n");
		DisplayBoard(mine, ROW, COL);
	}
}

//游戏逻辑
void game()
{
	char mine[ROWS][COLS] = { 0 };//存放雷
	char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
	//初始化
	InitBoard(mine, ROWS, COLS, '0');//都先初始化为无雷
	InitBoard(show, ROWS, COLS, '*');
	//布置雷
	SetMine(mine, ROW, COL);
	//打印第二个棋盘
	DisplayBoard(show, ROW, COL);
	DisplayBoard(mine, ROW, COL);
	//排查雷
	FineMine(mine, show, ROW, COL);
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

int main()
{
	int input = 0;
	srand((unsigned)time(NULL));
	do
	{
		menu();//菜单
		printf("请选择:> ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();//扫雷游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入非法,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

❤️ 结语

文章到这里就结束了,如果对你有帮助,你的点赞将会是我的最大动力,如果大家有什么问题或者不同的见解,欢迎大家的留言~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/88353.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云计算在线实训系统建设方案

一、 人工智能与云计算系统概述 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是一种模拟人类智能的科学和工程&#xff0c;通过使用计算机系统来模拟、扩展和增强人类的智能能力。人工智能涉及多个领域&#xff0c;包括机器学习、深度学习、自然…

Web 开发 Django 管理工具

上次为大家介绍了 Django 的模型&#xff0c;通过模型就可以操作数据库&#xff0c;从而就可以改变页面的展示内容&#xff0c;那问题来了&#xff0c;我们只能通过手动编辑模型文件来配置模型吗&#xff1f;当然不是&#xff0c;Django 为我们提供了强大的工具&#xff0c;可以…

优化Python代理爬虫的应用

当我们在资源受限的环境中使用Python代理爬虫时&#xff0c;我们需要采取一些优化措施&#xff0c;以确保程序的高效性和稳定性。在本文中&#xff0c;我将分享一些关于如何优化Python代理爬虫在资源受限环境下的应用的实用技巧。 首先我们来了解&#xff0c;哪些情况算是资源…

GNU make系列之介绍Makefile

一.欢迎来到我的酒馆 在本章节介绍Makefile。 目录 一.欢迎来到我的酒馆二.GNU make 预览三.一个简单的Makefile四.make程序如何处理Makefile文件 二.GNU make 预览 2.1 GNU make工具会自动决定哪些程序需要被重新编译&#xff0c;并且执行相应的命令来重新编译程序。在本系列博…

[MyBatis系列④]核心配置文件

目录 1、简介 2、DTD 3、typeHandlers 3.1、默认类型处理器 3.2、自定义类型处理器 4、plugins ⭐MyBatis系列①&#xff1a;增删改查 ⭐MyBatis系列②&#xff1a;两种Dao开发方式 ⭐MyBatis系列③&#xff1a;动态SQL 1、简介 MyBatis的核心配置文件&#xff08;通常命…

whisper 语音识别项目部署

1.安装anaconda软件 在如下网盘免费获取软件&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1zOZCQOeiDhx6ebHh5zNasA 提取码&#xff1a;hfnd 2.使用conda命令创建python3.8环境 conda create -n whisper python3.83.进入whisper虚拟环境 conda activate whisper4.…

HQL解决连续三天登陆问题

1.背景 统计连续登录天数超过3天的用户&#xff0c;输出信息包括&#xff1a;用户id&#xff0c;登录天数&#xff0c;起始时间&#xff0c;结束时间&#xff1b; 2.准备数据 -- 建表 create table if not exists user_login_3days(user_id STRING,login_date date );--插入…

01.Django入门

1.创建项目 1.1基于终端创建Django项目 打开终端进入文件路径&#xff08;打算将项目放在哪个目录&#xff0c;就进入哪个目录&#xff09; E:\learning\python\Django 执行命令创建项目 F:\Anaconda3\envs\pythonWeb\Scripts\django-admin.exe&#xff08;Django-admin.exe所…

手写模拟SpringBoot核心流程(二):实现Tomcat和Jetty的切换

实现Tomcat和Jetty的切换 前言 上一篇文章我们聊到&#xff0c;SpringBoot中内置了web服务器&#xff0c;包括Tomcat、Jetty&#xff0c;并且实现了SpringBoot启动Tomcat的流程。 那么SpringBoot怎样自动切换成Jetty服务器呢&#xff1f; 接下来我们继续学习如何实现Tomcat…

Python代理池健壮性测试 - 压力测试和异常处理

大家好&#xff01;在构建一个可靠的Python代理池时&#xff0c;除了实现基本功能外&#xff0c;我们还需要进行一系列健壮性测试来确保其能够稳定运行&#xff0c;并具备应对各种异常情况的能力。本文将介绍如何使用压力测试工具以及合适的异常处理机制来提升Python代理池的可…

linux系统(centos、ubuntu、银河麒麟服务、uos、deepin)判断程序是否已安装,通用判断方法:使用所有应用和命令的判断

前言 项目中需要判断linux服务器中是否已经安装了某个服务 方法有很多种&#xff0c;但是很多都不通用&#xff0c; 脚本代码就不容易做成统一的 解决方案 用下面的脚本代码去进行判断 用jdk测试 脚本意思如下&#xff1a; 输入java -version命令&#xff0c;将返回的字…

微服务中间件--分布式搜索ES

分布式搜索ES 11.分布式搜索 ESa.介绍ESb.IK分词器c.索引库操作 (类似于MYSQL的Table)d.查看、删除、修改 索引库e.文档操作 (类似MYSQL的数据)1) 添加文档2) 查看文档3) 删除文档4) 修改文档 f.RestClient操作索引库1) 创建索引库2) 删除索引库/判断索引库 g.RestClient操作文…

Linux之基础IO文件系统讲解

基础IO文件系统讲解 回顾C语言读写文件读文件操作写文件操作输出信息到显示器的方法stdin & stdout & stderr总结 系统文件IOIO接口介绍文件描述符fd文件描述符的分配规则C标准库文件操作函数简易模拟实现重定向dup2 系统调用在minishell中添加重定向功能 FILE文件系统…

【Unity3D】程序纹理简单应用

1 几何纹理应用 本文所有案例的完整资源详见→Unity3D程序纹理简单应用。 1.1 边框 1&#xff09;边框子图 Border.shadersubgraph 说明&#xff1a;Any 节点用于判断输入向量中是否存在一个分量非零&#xff0c;Branch 节点根据输入的真假走不同的分支&#xff0c;详见→Shad…

aws PinPoint发附件demo

php 版aws PinPoint发附件demo Laravel8框架&#xff0c;安装了"aws/aws-sdk-php": "^3.257" 主要代码&#xff1a; public function sendRawMail(Request $request) {$file $request->file(attachment);/*echo count($file);dd($file);*/$filenam…

MinIO【部署 01】MinIO安装及SpringBoot集成简单测试

MinIO安装及SpringBoot集成测试 1.下载安装1.1 Install the MinIO Server1.2 Launch the MinIO Server1.3 Connect Your Browser to the MinIO Server 2.SpringBoot集成2.1 依赖及配置2.2 代码2.3 测试结果 1.下载安装 下载 https://min.io/download#/linux&#xff1b; 安装文…

IDEA下SpringBoot指定环境、配置文件启动

1、idea下的SpringBoot启动&#xff1a;指定配置文件 Springboot项目有如下配置文件 主配置文件application.yml&#xff0c; 测试环境&#xff1a;application-test.yml 生产环境&#xff1a;application-pro.yml 开发环境&#xff1a;application-dev.yml 1.1.配置文件…

概率密度函数 累积分布函数

概率密度函数&#xff1a;是指想要求得面积的图形表达式&#xff0c;注意只是表达式&#xff0c;要乘上区间才是概率&#xff0c;所以概率密度并不是概率&#xff0c;而是概率的分布程度。 为什么要引入概率密度&#xff0c;可能是因为连续变量&#xff0c;无法求出某个变量的…

CnetSDK .NET OCR SDK Crack

CnetSDK .NET OCR SDK Crack CnetSDK.NET OCR库SDK是一款高度准确的.NET OCR扫描仪软件&#xff0c;用于使用手写、文本和其他符号等图像进行字符识别。它是一款.NET OCR库软件&#xff0c;使用Tesseract OCR引擎技术&#xff0c;可将字符识别准确率提高99%。通过将此.NET OCR扫…

分类预测 | MATLAB实现MIV-SVM的平均影响值MIV算法结合支持向量机分类预测

分类预测 | MATLAB实现MIV-SVM的平均影响值MIV算法结合支持向量机分类预测 目录 分类预测 | MATLAB实现MIV-SVM的平均影响值MIV算法结合支持向量机分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 先利用平均影响值MIV算法对特征进行排序&#xff0c;确定分类特征…
最新文章