【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.hqwc.cn/news/449578.html

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

相关文章

win10重装Ubuntu22.04安装报错复盘

目录 一&#xff1a;补充启动盘制作 二&#xff1a;错误信息[0xC0030570] The file or directory is corrupted and unreadable. 三&#xff1a;ubuntu重装步骤&#xff1a; 四&#xff1a;磁盘冗余阵列 五&#xff1a;尝试将SCS11(2,0.0), 第1分区(sda)设备的一个vfat文…

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

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

帅气的性能监控平台Grafana(Windows下使用Grafana监控系统指标与GPU指标)

帅气的性能监控平台Grafana&#xff08;Windows下使用Grafana监控系统指标与GPU指标&#xff09; 前情提要 系统环境准备 windows_exporter下载 nvidia_gpu_exporter下载 prometheus下载 Grafana下载 安装指导 windows_exporter安装与nvidia_gpu_exporter安装 promethe…

《CSS3》田字网格背景(外实线内虚线)的实现

一、前言 记录一些有趣的CSS实现方式&#xff0c;总所周知&#xff0c;当一段效果可以通过CSS实现的时候&#xff0c;绝不使用Javascript来实现&#xff0c;因此记录一些有意思的CSS效果&#xff0c;一来是方便自己学习&#xff0c;另一来是方便以后在需要使用到的时候能快速找…

C++ 日期类的实现

目录 前言 日期类中的成员函数和成员变量 日期类中成员函数的详解和实现 1.天数前后的判断 2.天数加减的实现 3.前置 && 后置 4.计算天数差值 前言 日期类的实现将综合前面所学的&#xff08;类的6个默认成员函数&#xff09;&#xff0c;进一步理解和掌握类的…

2.03作业

1.请编程实现单向循环链表的头插&#xff0c;头删&#xff0c;尾插&#xff0c;尾删。 2.请编程实现单向循环链表约瑟夫环 约瑟夫环&#xff1a;用循环链表编程实现约瑟夫问题 n个人围成一圈&#xff0c;&#xff0c;从某人开始报数&#xff0c;1,2&#xff0c;......,m,数到…

【算法与数据结构】718、1143、LeetCode最长重复子数组 最长公共子序列

文章目录 一、718、最长重复子数组二、1143、最长公共子序列三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、718、最长重复子数组 思路分析&#xff1a; 第一步&#xff0c;动态数组的含义。 d p [ i ] [ j ] dp[i]…

【Java程序设计】【C00191】基于SSM的线上鲜花商城管理系统(论文+PPT)

基于SSM的线上鲜花商城管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的线上鲜花商城的管理系统 本系统分为前台用户和后台管理员2个功能模块。 前台用户&#xff1a; 当游客打开系统的网址后&#xff0c;首先看…

SpringAOP+SpringBoot事务管理

项目搭建SpringAOPSpringBoot中管理事务AOP案例实战-日志记录日志系统 一、项目搭建 第一步&#xff1a;构建项目 第二步&#xff1a;导入依赖 第三步&#xff1a;配置信息 自动配置&#xff08;项目自动生成的启动类&#xff09; /*** 启动类&#xff1a;申明当前类是一个…

unity3d的海盗王白银城演示

这是一个外网上的下载的海盗王unity3d制作的白银城演示场景。 地图只含有白银城区&#xff0c;没有野外和怪物。 当然也没有服务器端的。 我对灯光、摄像头、天空背景等做过调整&#xff0c;使它显示起来比较鲜丽。 它的模型和贴图是直接拿了海盗的&#xff0c;没有做过优化调整…

《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述(5)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述&#xff08;4&#xff09; 4.1.2 PCIe总线使用的信号 PCIe设备使用两种电源信号供电&#xff0c;分别是Vcc与Vaux&#xff0c;其额定电压为3.3V。其中Vcc为主电源&#xff0c;PCIe设备…

Leetcode的AC指南 —— 栈与队列 :1047.删除字符串中的所有相邻重复项

摘要&#xff1a; **Leetcode的AC指南 —— 栈与队列 &#xff1a;1047.删除字符串中的所有相邻重复项 **。题目介绍&#xff1a;给出由小写字母组成的字符串 S&#xff0c;重复项删除操作会选择两个相邻且相同的字母&#xff0c;并删除它们。 在 S 上反复执行重复项删除操作&a…