【C语言】数组的应用:扫雷游戏(包含扩展和标记功能)附完整源代码

这个代码还是比较长的,为了增加可读性,我们还是把他的功能分装到了test.c,game.c,game.h里面。

扫雷游戏的规则相信大家来阅读本文之前已经知晓了,如果点到雷就输了,如果不是雷,点到的格子会显示周围雷的个数,如果这个格子不是雷,他周围也没有雷,就再展开,如果周围八个格子里面又有符合该条件的,则又会重复这个操作,效果上就是展开了一大片。这个功能我们是用函数递归来实现的,扫雷游戏还可以标记,我们可以标记某个格子。由于本文的写法还是比较简易的,没有贴素材什么的,就是能先让玩家判断是要排雷还是要标记。本代码的循环特别多,这也是难点所在。话不多说,直接上game.h文件,通过里面的函数声明,大家应该就能对游戏大致的思路有一个理解。

代码中使用了三处宏定义,这是为了方便日后的修改。

test.c

从主函数开始看,首先看到的是一个srand((unsigned)time(NULL)),看过前面三子棋和猜数字游戏的老铁应该马上就懂了,这是我们产生随机数的固定套路,说明后面有用到随机数的地方,这里就提前告诉大家是布置雷的时候我们是随机布置的。由于我们想要游戏可以重复玩,玩一把之后不过瘾可以接着玩,我们就采用了我们熟悉的do..while循环,这个写法在猜数字游戏和三子棋游戏中也同样采用,用于游戏的重复玩。(提一嘴与这个代码无关的,如果多组输入应该怎么写?答案是while(ch=getchar()!EOF))。

如果我们输入1就开始游戏了,首先我们要创建一个数组作为棋盘,创建的时候为了防止边缘以及四个角上的元素在判断周围是否有雷的时候越界访问,我们故意创建了一个更大的棋盘,虽然我们需要的是一个9*9的棋盘,但我们直接创建一个11*11的棋盘,当然这里的9和11都是用的宏定义代替,防止以后如果想要更改棋盘大小得把所有代码全改一遍。到时候我们玩游戏打印棋盘就从这个大棋盘里面扣一个小棋盘出来打印即可。

创建了数组之后应该先初始化,这里初始化成什么字符,就很有讲究了。假如我们要在放雷的棋盘上,用0代表没有雷,用1代表有雷,这时候老铁们可能有疑问了,用数字代表有没有雷,那玩的时候点了一下出现了一个1,这个1是代表此处是雷啊,还是他周围有一个雷啊,这就有歧义了,但是我就是要坚持用0和1来初始化,这是因为后面统计周围雷的个数的时候比较方便,那这样我们就要重新创建一个相同的新数组用来存放排查的雷的信息,并使得这个新数组show和我们放雷的数组mine建立起来联系,这里刚上来我们就把mine数组全部初始化为0,表示还没有放雷,show数组初始化成什么可以根据大家的喜好,我这里把show数组全部初始化为*,接下来就要开始布置雷了,然后就是排查雷。游戏的大题思路就是这样。

接下来就是挨个实现函数的功能

首先是初始化棋盘的函数

这个函数还是比较好理解的,注意把整个大棋盘初始化而不是只初始化那个小棋盘就行。

接下来是打印棋盘的函数,我们不管是初始化棋盘还是布置雷,还是排查雷,都需要打印一下棋盘展示给玩家

打印棋盘本质上就是打印一个二维数组,注意换行就行。打印棋盘的时候为了方便玩家知道排查的格子是第几行第几列,我们还把行号和列号打印出来了。

接下来就要布置雷

我们要求是随机放雷,这里是放10个,因为不是说一次就能放完十个的,因此显然要循环,而且要求放过雷的位置就不要再放雷了。rand()会产生一个随机数,我们让他%9就能产生0~8的随机数,再+1就产生了1~9的随机数。因为我们要在9*9的棋盘里面布置雷。

统计周围雷的个数的函数

到这里老铁们明白为什么我当初为什么非要用0和1初始化mine数组了吧,这样直接return周围八个格子的和,再减去八倍的字符0的ASCII码值(因为我们存的是字符0和字符1)就是周围雷的个数了。

接下来是一次性扩展的函数

这个函数我们是使用递归的方式来写的,这个win就是我们胜利的标准,每走一个格子且不是雷他就+1,他最高加到row*col-雷的个数,这时候我们就赢了。我们传递的是win的地址,也就是进行了传址调用,这是为了能够在函数内部改变外部的变量(关于传值调用和传址调用的问题可以去看我函数的那篇文章),扩展的条件有三个,第一条是我们选择的格子本身不是雷,这个非常好办,,第二条是选择的格子周围没有雷,这个可以通过前面的统计周围雷的个数的函数来判断,第三就是我们所选的格子不能被重复遍历,不然就会陷入死递归。这个我们就直接在递归调用的时候去除x,y这个坐标就行。

接下来是最关键的函数,排查和标记雷的函数

上来先提示玩家选择排查还是标记,我这里用的是1和2,大家也可以根据自己的喜好选择别的,而且不管是排查雷还是标记,都应该能够进行多次,因此都应是循环。如果我们选择排查雷,在输入坐标之后,就有以下情况,第一种就是踩到雷了,直接就打印你寄了,然后打印一下mine棋盘让玩家输的明白,然后直接goto END即可,END可以看到是我再代码最后面放的一个空语句,因为这里循环嵌套的实在是太多了,我觉得goto语句的作用在这种情况下就很好的体现了。第二种情况就是没有踩到雷,没有踩到雷就要看看这个格子周围有几个雷,让他进入expend_board函数,出来之后连用两个break跳出switch语句,第一个break跳出的是里面的while循环,第二个break跳出的才是switch语句,当然我知道这肯定不是一种很优的解法,但是作为一个初学者我也是改了半天才写出来的,我感觉还是比较好理解的。而且我们发现每次第一个break跳出while循环的时候都会判断一下是不是赢了,如果赢了,也是直接goto END。

最后介绍一下几个可能出错的点:

1.如果你的代码每一次都会打印一下别的东西,可以看看是不是switch语句里面忘记了写break

2.expen_board函数不仅承载了周围没有雷就递归延展的功能,还要执行周围有雷要打印出雷的个数的功能,我刚上来就这么写的

这样写的问题就是,如果一个格子周围没有雷,我们本来是想打印空格,而进入expend_board函数之后也确实把这个格子改成了空格,但是执行完之后出了函数,这个空格就立马被改成了0,而且win的值也多加了一次,就像下面这样,因此我就把周围有雷的情况也弄到了expend_board函数内部去

3.在find_mine函数中,由于循环嵌套巨多,少写了break,导致死循环

4.在expend_board函数中,又遍历了x,y坐标的格子,导致死递归,判断格子是不是已经被处理过了,只需要看看show[横坐标][纵坐标]处是不是*即可

最后奉上完整源代码

game.h

#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu();//打印菜单的函数
void initboard(char arr, int rows, int cols, char a);//初始化两个大棋盘的函数
void display_board(char arr[ROWS][COLS], int row, int col);//打印棋盘的函数
void set_mine(char arr[ROWS][COLS], int row, int col);//布置雷的函数
int count_mine(char arr[ROWS][COLS], int x, int y);//统计周围雷个数的函数
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排查雷的函数

test.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void game() {//为了防止边缘以及四个角在判断周围有几个雷的时候越界,我们把棋盘加大char mine[ROWS][COLS] = { 0 };//用来存放雷的棋盘char show[ROWS][COLS] = { 0 };//用来展示周围有几个雷的棋盘//这是因为我们用1表示有雷,0表示无雷//如果把放雷和展示有几个雷用同一个棋盘,就会造成歧义//因为不知道1代表是此处是雷还是周围有一个雷initboard(mine, ROWS, COLS, '0');//用来放雷的棋盘刚上来全放上0,表示还没放雷,一会再放雷initboard(show, ROWS, COLS, '*');//用来展示周围有几个雷的棋盘,刚上来全放上*display_board(show, ROW, COL);set_mine(mine, ROW, COL);//布置雷find_mine(mine, show, ROW, COL);//排查雷
}
int main() {int input = 0;srand((unsigned)time(NULL));do {menu();scanf("%d", &input);switch (input) {case 1: {game();break;}case 0: {printf("退出游戏\n");break;}default: {printf("输入错误,请重新输入\n");break;}}} while (input);return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu() {printf("****************************\n");printf("*******1.play***************\n");printf("*******0.exit***************\n");printf("****************************\n");
}
//初始化棋盘的函数
void initboard(char arr[ROWS][COLS], int rows, int cols, char a) {int i = 0;for (i = 0; i < rows; i++) {int j = 0;for (j = 0; j < cols; j++) {arr[i][j] = a;}}
}
//打印棋盘的函数
//打印棋盘只需要打印9*9的即可,因为展示给玩家的就是9*9的
//但是在操作的时候要对11*11的数组操作,因为我们并没有创建所谓9*9的数组
void display_board(char arr[ROWS][COLS], int row, int col) {int i = 0;//从第二行也就是i=1开始打印,从第二列也就是j=1开始打印//打印列的标识for (i = 0; i <= col; i++) {printf("%d ", i);}printf("\n");for (i = 1; i <= row; i++) {int j = 0;printf("%d ", i);for (j = 1; j <= col; j++) {printf("%c ", arr[i][j]);}printf("\n");}
}
//布置雷的函数,随机放雷
void set_mine(char arr[ROWS][COLS], int row, int col) {int count = EASY_COUNT;while (count) {int x = rand() % row + 1;int y = rand() % col + 1;if (arr[x][y] == '0') {arr[x][y] = '1';count--;}}		
}
//统计周围雷的个数的函数
//自始至终我们都在操作mine或者show数组,这两个数组都是ROWS*COLS的数组
int count_mine(char arr[ROWS][COLS], int x, int y) {return(arr[x - 1][y - 1] + arr[x - 1][y] + arr[x - 1][y + 1]+ arr[x][y - 1] + arr[x][y + 1] + arr[x + 1][y - 1]+ arr[x + 1][y] + arr[x + 1][y + 1]-8*'0');//这就是为什么我们当初布置雷的时候为什么要放字符1和字符0
}
//一次性扩展的函数	递归实现
//该坐标不是雷,该坐标周围没有雷,坐标未被排查过
void expend_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int* win) {int ret = count_mine(mine, x, y);if (ret != 0) {show[x][y] = ret + '0';//显示周围雷的个数*win++;return;}else {show[x][y] = ' ';*win++;//传址调用//递归周围八个格子if (show[x - 1][y - 1] == '*' && (x - 1) >= 1 && (x - 1) <= ROW && (y - 1) >= 1 && (y - 1) <= COL) {expend_mine(mine, show, x - 1, y - 1, win);}if (show[x - 1][y] == '*' && (x - 1) >= 1 && (x - 1) <= ROW&& (y) >= 1 && (y) <= COL) {expend_mine(mine, show, x - 1, y , win);}if (show[x - 1][y + 1] == '*' && (x - 1) >= 1 && (x - 1) <= ROW&& (y + 1) >= 1 && (y + 1) <= COL) {expend_mine(mine, show, x - 1, y + 1, win);}if (show[x][y - 1] == '*' && (x ) >= 1 && (x ) <= ROW && (y - 1) >= 1 && (y - 1) <= COL) {expend_mine(mine, show, x, y - 1, win);}if (show[x][y + 1] == '*' && (x) >= 1 && (x) <= ROW&& (y + 1) >= 1 && (y + 1) <= COL) {expend_mine(mine, show, x , y + 1, win);}if (show[x + 1][y - 1] == '*' && (x + 1) >= 1 && (x + 1) <= ROW && (y - 1) >= 1 && (y - 1) <= COL) {expend_mine(mine, show, x + 1, y - 1, win);}if (show[x + 1][y] == '*' && (x + 1) >= 1 && (x + 1) <= ROW && (y) >= 1 && (y) <= COL) {expend_mine(mine, show, x + 1, y, win);}if (show[x + 1][y + 1] == '*' && (x + 1) >= 1 && (x + 1) <= ROW&& (y + 1) >= 1 && (y + 1) <= COL) {expend_mine(mine, show, x + 1, y + 1, win);}}}
//排查和标记雷的函数
//标记用#
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {int x = 0;int y = 0;int win = 0;int input = 0;while (1) {printf("排雷请按1,标记请按2,再次标记将会取消标记\n");scanf("%d", &input);switch (input) {case 1: {while (win < row * col - EASY_COUNT) {printf("请输入要排查的坐标\n");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col) {if (mine[x][y] == '1') {printf("你寄了\n");display_board(mine, row, col);goto END;}else {					expend_mine(mine, show, x, y, &win);												display_board(show, row, col);						break;}}else {printf("坐标非法,请重新输入\n");}break;}if (win == row * col - EASY_COUNT) {printf("恭喜你,排雷成功\n");display_board(mine, row, col);				goto END;}break;//跳出switch语句}case 2: {while (1) {printf("请输入要标记的坐标\n");scanf("%d %d", &x, &y);if (show[x][y] == '*') {show[x][y] = '#';display_board(show, ROW, COL);break;//跳出case 2里面的while循环}else if(show[x][y]=='#') {show[x][y] = '*';display_board(show, ROW, COL);break;//跳出case 2里面的while循环}else {printf("坐标已经被占用,无法标记\n");break;//跳出case 2里面的while循环}}break;//跳出switch语句,如果没有这个break,不管输入什么坐标都会走一遍下面的default}default:printf("输入错误,请重新输入\n");break;}}
END:;}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch-统计学方法、分布函数、随机抽样、线性代数运算、矩阵分解

Tensor中统计学相关的函数 torch.mean() #返回平均值 torch.sum() #返回总和 torch.prod() #计算所有元素的积 torch.max() # 返回最大值 torch.min() # 返回最小值 torch.argmax() #返回最大值排序的索引值 torch.argmin() #返回最小值排序的索引值 torch.std() #返回标准差 …

BEV感知算法学习

BEV感知算法学习 3D目标检测系列 Mono3D(Monocular 3D Object Detection for Autonomous Driving) 流程&#xff1a; 通过在地平面上假设先验&#xff0c;在3D空间中对具有典型物理尺寸的候选边界框进行采样&#xff1b;然后我们将这些方框投影到图像平面上&#xff0c;从而避…

在 Windows 10 上使用 Visual Studio 2022 进行 C++ 桌面开发

工具下载链接&#xff1a;https://pan.quark.cn/s/c70b23901ccb 环境介绍 在今天的快速发展的软件开发行业中&#xff0c;选择合适的开发环境是非常关键的一步。对于C开发人员来说&#xff0c;Visual Studio 2022&#xff08;VS2022&#xff09;是一个强大的集成开发环境&…

YOLOv7改进:下采样系列 | 一种新颖的基于 Haar 小波的下采样HWD,有效涨点系列

💡💡💡本文独家改进:HWD的核心思想是应用Haar小波变换来降低特征图的空间分辨率,同时保留尽可能多的信息,与传统的下采样方法相比,有效降低信息不确定性。 💡💡💡使用方法:代替原始网络的conv,下采样过程中尽可能包括更多信息,从而提升检测精度。 收录 YO…

聊聊DoIP吧(一)

DoIP是啥? DoIP代表"Diagnostic over Internet Protocol",即互联网诊断协议。它是一种用于在车辆诊断中进行通信的网络协议。DoIP的目标是在现代汽车中实现高效的诊断和通信。通过使用互联网协议(IP)作为通信基础,DoIP使得诊断信息能够通过网络进行传输,从而提…

uniCloud -- uniIdRouter自动路由

目录 自动路由 云对象响应触发needLogin 获取当前用户信息getCurrentUserInfo 实战应用 个人中心页面 pages.json配置 uni-id自动路由 uni_modules\uni-id-pages/common 登录页面store修改 自动路由 支持的HBuilderX版本 uni-appuni-app x3.5.03.99 uniIdRouter 是一…

前端学习笔记 | 响应式网页+Boostrap

一、响应式网页 一套代码适应多端 1、媒体查询media(条件){css} max-width 小于等于max-width生效min-width 【案例】左侧隐藏 因为CSS的层叠性&#xff0c;书写顺序&#xff1a;max-width从大到小&#xff1b;min-width从小到大。 【媒体查询完整写法】 在html中link用于不同…

前端小案例——导航回顶部(HTML+CSS+JS, 附源码)

一、前言 实现功能&#xff1a; 这个案例实现了页面滚动到一定位置时显示"回到顶部"按钮&#xff0c;并且点击按钮能够平滑滚动回页面顶部的功能。 实现逻辑&#xff1a; 页面结构&#xff1a;通过HTML标签定义了页面的基本结构。页面主要由多个div.content组成&am…

Springboot整合Websocket实现ws和wss连接

1. 引入pom依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.10</version> </dependency>2. 新建websocket配置文件 import org.springf…

Linux笔记之bash和expect脚本中的$和$argv参数匹配

Linux笔记之bash和expect脚本中的 和 和 和argv参数匹配 —— 杭州 2024-02-03 code review! 文章目录 Linux笔记之bash和expect脚本中的 和 和 和argv参数匹配1.bash脚本2.expect脚本 1.bash脚本 在Linux的bash脚本中&#xff0c;参数和脚本文件名的变量使用方式与你提供的稍…

曲线拟合、多项式拟合、最小二乘法

最近在做自车轨迹预测的工作&#xff0c;遇到 曲线拟合、多项式拟合、最小二乘法这些概念有点不清晰&#xff0c; 做一些概念区别的总结&#xff1a; 曲线拟合用于查找一系列数据点的“最佳拟合”线或曲线。 大多数情况下&#xff0c;曲线拟合将产生一个函数&#xff0c;可用于…

蓝桥杯省赛无忧 课件70 第九次学长直播带练配套课件

01 混境之地5 02 最快洗车时间 03 安全序列 04 可构造的序列总数 05 拍照 06 破损的楼梯

Android Display显示框架整体流程

一.Android Display显示框架整体流程图

webassembly003 ggml.js试用(暂记)

git clone https://github.com/rahuldshetty/ggml.js-examples.gitpython -m http.sever启动服务器 虽然推理运行了一会&#xff0c;但是风扇没有任何响声。 Using Examples 感觉这个有点笨拙 Instruction: {dow you know about Uncaught invalid worker function to call: …

【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)

目录 一、简介 二. 双链表的实现 1.准备工作及其注意事项 1.1 先创建三个文件 1.2 注意事项&#xff1a;帮助高效记忆 1.3 关于什么时候 用 一级指针接收&#xff0c;什么时候用 二级指针接收&#xff1f; 1.4 释放节点时&#xff0c;要将节点地址 置为NULL&#xff0…

某赛通电子文档安全管理系统 UploadFileToCatalog SQL注入漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

WordPress主题YIA的文章页评论内容为什么没有显示出来?

有些WordPress站长使用YIA主题后&#xff0c;在YIA主题设置的“基本”中没有开启“一键关闭评论功能”&#xff0c;而且文章也是允许评论的&#xff0c;但是评论框却不显示&#xff0c;最关键的是文章原本就有的评论内容也不显示&#xff0c;这是为什么呢&#xff1f; 根据YIA主…

决策树的相关知识点

&#x1f4d5;参考&#xff1a;ysu老师课件西瓜书 1.决策树的基本概念 【决策树】&#xff1a;决策树是一种描述对样本数据进行分类的树形结构模型&#xff0c;由节点和有向边组成。其中每个内部节点表示一个属性上的判断&#xff0c;每个分支代表一个判断结果的输出&#xff…

和鲸科技与智谱AI达成合作,共建大模型生态基座

近日&#xff0c;上海和今信息科技有限公司&#xff08;简称“和鲸科技”&#xff09;与北京智谱华章科技有限公司&#xff08;简称“智谱AI”&#xff09;签订合作协议&#xff0c;双方将携手推动国产通用大模型的广泛应用与行业渗透&#xff0c;并积极赋能行业伙伴探索领域大…

回归预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络多变量回归预测

回归预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络多变量回归预测 目录 回归预测 | Matlab实现CPO-BiLSTM【24年新算法】冠豪猪优化双向长短期记忆神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CPO-B…