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

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

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

相关文章

红队渗透靶机:LORD OF THE ROOT: 1.0.1

目录 信息收集 1、arp 2、nmap 3、knock 4、nikto 目录探测 1、gobuster 2、dirsearch WEB sqlmap 爆库 爆表 爆列 爆字段 hydra爆破 ssh登录 提权 信息收集 内核提权 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface: eth0, ty…

十年饮冰难凉热血——HTX重塑巴别塔

明天将会是不同的世界&#xff0c;该由不同的人来塑造。 2024年1月18日&#xff0c;HTX DAO正式成立。 作为区块链生态系统中领先的去中心化自治组织&#xff0c;HTX DAO以创新的治理方式&#xff0c;专注于开放金融和去中心化的代币化经济。 HTX DAO是一个富有远见和包容性…

基于springboot企业客户信息反馈平台源码和论文

网络的广泛应用给生活带来了十分的便利。所以把企业客户信息反馈管理与现在网络相结合&#xff0c;利用java技术建设企业客户信息反馈平台&#xff0c;实现企业客户信息反馈的信息化。则对于进一步提高企业客户信息反馈管理发展&#xff0c;丰富企业客户信息反馈管理经验能起到…

问题:在下列选项中,下列哪种情况不属于生理排泄过程的是() #媒体#学习方法#经验分享

问题&#xff1a;在下列选项中&#xff0c;下列哪种情况不属于生理排泄过程的是&#xff08;&#xff09; A.CO2由呼吸系统排出 B.食物残渣由消化道排出 C.皮肤排出汗液 D.肾脏排出尿液 E.由消化道排出的胆色素 参考答案如图所示

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

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

高宇辰:打造“π”型人才 | 提升之路系列(七)

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

抽象类(Java)、模板方法设计模式

一、概念 在Java中有abstract关键字&#xff0c;就是抽象的意思&#xff0c;可用来修饰类和成员方法。 用abstract来修饰类&#xff0c;那这个类就是抽象类&#xff1b;修饰方法&#xff0c;那这个方法就是抽象方法。 修饰符 abstract class 类名{修饰符 abstract 返回值类型…

故障诊断 | 一文解决,BiLSTM双向长短期记忆神经网络故障诊断(Matlab)

文章目录 效果一览文章概述专栏介绍模型描述源码设计参考资料效果一览 文章概述 故障诊断模型 | Maltab实现BiLSTM双向长短期记忆神经网络故障诊断 专栏介绍 订阅【故障诊断】专栏,不定期更新机器学习和深度学习在故障诊断中的应用;订阅

List集合接口的介绍和使用

一.关于List集合类的继承关系图 List接口继承了Collection接口&#xff0c;而List接口下有三个重要的实现类:ArrayList&#xff0c;LinkedList&#xff0c;Vector 二.List接口的基本介绍 1.List接口是Collection接口的子接口2.存入List集合中的元素是有序的&#xff08;即添加…

面试经典150题——文本左右对齐(困难)

​"It always seems impossible until it’s done." - Nelson Mandela 1. 题目描述&#xff1a; 这个题目标为困难题目&#xff0c;但是如果我们静下心来把题目读懂了&#xff0c;其实无非就是不同情况下不同考虑而已&#xff0c;也没什么思维上的复杂&#xff0c;还…

银行数据仓库体系实践(8)--主数据模型设计

主数据区域中保留了数据仓库的所有基础数据及历史数据&#xff0c;是数据仓库中最重要的数据区域之一&#xff0c;那主数据区域中主要分为近源模型区和整合&#xff08;主题&#xff09;模型区。上一节讲到了模型的设计流程如下图所示。那近源模型层的设计在第2.3和3这两个步骤…

微信积分系统怎么做_开启用户忠诚度之门

积分系统&#xff1a;开启用户忠诚度之门 在数字化时代&#xff0c;积分系统已经成为了企业与消费者之间互动的桥梁。它不仅是一种奖励机制&#xff0c;更是提升用户忠诚度、促进消费的重要手段。本文将深入探讨如何将积分系统作为主题&#xff0c;撰写一篇高质量的营销软文&a…

记录element-plus树型表格的bug

问题描述 如果数据的子节点命名时children,就没有任何问题&#xff0c;如果后端数据结构子节点是其他名字&#xff0c;比如thisChildList就有bug const tableData [{id: 1,date: 2016-05-02,name: wangxiaohu,address: No. 189, Grove St, Los Angeles,selectedAble: true,th…

Socket通信之获取服务器端文件列表点击下载

客户端读取服务器端的文件目录,自主选择进行下载。(AS实现) 1.Manifest添加权限 与之前博文相同,不再赘述。详见: Socket通信-CSDN博客文章浏览阅读272次,点赞4次,收藏10次。套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。socket通…

乘方计算 T1062

#include<bits/stdc.h> using namespace std; int a,n, power1; int main(){cin>>a>>n;for(int i1;i<n;i){power*a;}cout<<power<<endl;return 0; }

【Docker】网络配置network详解

一&#xff0c;network的概述 解决痛点&#xff08;能干什么&#xff1f;&#xff09;&#xff1a; &#xff08;1&#xff09;容器间的互联和通信以及端口映射 &#xff08;2&#xff09;容器IP变动时候&#xff0c;可以通过服务名直接网络通信而不受到影响 二&#xff0c;n…

小白水平理解面试经典题目_数组类Leetcode 412. Fizz Buzz【数学解法】

412 FizzBuzz 小白渣翻译&#xff1a; 给定一个整数 n &#xff0c;返回一个字符串数组 answer &#xff08;从 1 开始索引&#xff09;&#xff0c;其中&#xff1a; answer[i] “FizzBuzz” 如果 i 能被 3 和 5 整除。answer[i] “Fizz” 如果 i 能被 3 整除。answer[i]…

大数据信用报告查询费用一般要多少钱?

一些不少朋友在申贷的时候被拒贷之后&#xff0c;得到的原因就是因为大数据不良被拒&#xff0c;这时候很多人都反过来查询自己的大数据信用报告&#xff0c;而查询的价格也是不少朋友都比较关注的&#xff0c;那大数据信用报告查询费用一般要多少钱呢?下面本文就为你介绍一下…

069:vue中EventBus的使用方法(图文示例)

第069个 查看专栏目录: VUE ------ element UI 本文章目录 示例背景示例效果图示例源代码父组件&#xff1a;子组件A&#xff1a;子组件B&#xff1a;eventbus/index.js&#xff1a; EventBus的基本使用方法&#xff1a; 示例背景 在Vue中&#xff0c;使用EventBus可以实现组件…

flask基于Python的期货交易模拟系统的django-afl61-vue

期货交易模拟系统是一个便于用户在线查看期货投资、取消投资、风险控制、账户资金、持仓资金等&#xff0c;管理员进行管理的平台。因此本文主要论述了系统开发的过程和实现的功能&#xff0c;结合Web技术来实现的期货交易模拟系统。本系统以软件工程理论为开发基础&#xff0c…