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

news2024/11/15 15:26:38

这个代码还是比较长的,为了增加可读性,我们还是把他的功能分装到了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.coloradmin.cn/o/1429633.html

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

相关文章

Kotlin快速入门系列8

Kotlin的泛型 与Java一样&#xff0c;Kotlin也提供泛型。泛型&#xff0c;即 "参数化类型"&#xff0c;将类型参数化&#xff0c;可以用在类&#xff0c;接口&#xff0c;方法上。可以为类型安全提供保证&#xff0c;消除类型强转的烦恼。声明泛型类的格式如下&…

关于反爬虫的的概述

目录 前言 一、验证码验证 二、IP限制 三、User-Agent限制 四、动态页面加载 总结 前言 反爬虫是一种防止网站被自动程序&#xff08;爬虫&#xff09;访问和抓取数据的技术手段。在网络爬虫的发展和使用过程中&#xff0c;有一部分爬虫是用于非法获取网站数据、侵犯隐私…

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器&#xff08;5、IO协程调度模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、…

【C/C++ 10】扫雷小游戏

一、题目 写一个扫雷小游戏&#xff0c;每次输入一个坐标&#xff0c;若该处是地雷&#xff0c;则游戏失败&#xff0c;若该处不是地雷&#xff0c;则显示周围地雷数量&#xff0c;若扫除全部非地雷区域&#xff0c;则扫雷成功。 二、算法 设置两张地图&#xff08;二维数组&…

校园墙表白墙系统uniapp微信小程序

配置文件 (自动编号、配置参数名称、配置参数值)&#xff1b; 前端开发:vue 语言&#xff1a;javapythonnodejsphp均支持 运行软件:idea/eclipse/vscode/pycharm/wamp均支持 框架支持:Ssm/django/flask/thinkphp/springboot/springcloud均支持 数据库 mysql 数据库工具&#x…

洛谷P1002 过河卒(简单DP)

[NOIP2002 普及组] 过河卒 题目描述 棋盘上 A A A 点有一个过河卒&#xff0c;需要走到目标 B B B 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为…

PKG系统安装包及IPSW固件:MacOS 11-14 Sonoma 正式版

MacOS 14 Sonoma&#xff0c;为提高生产力和创造力带来了全新的功能&#xff0c;有了更多使用小部件和令人惊叹的新屏幕保护程序进行个性化设置的方法&#xff0c;对Safari浏览器和视频会议进行了重大更新&#xff0c;以及优化的游戏体验——Mac体验比以往任何时候都更好。 mac…

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测

2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测 目录 2024美赛预测算法 | 回归预测 | Matlab基于RIME-LSSVM霜冰算法优化最小二乘支持向量机的数据多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效…

计算huggingface模型占用硬盘空间的实战代码

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

【Java程序设计】【C00232】基于Springboot的抗疫物资管理系统(有论文)

基于Springboot的抗疫物资管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的抗疫物资管理系统 用户主要分为管理员和普通用户 管理员&#xff1a; 管理员可以对后台数据进行管理、拥有最高权限、具体权限有…

猫用空气净化器好吗?好用的养猫宠物空气净化器品牌推荐

作为一个养猫五年的资深铲屎官&#xff0c;我对如何轻松快乐地养猫有一些心得。猫咪每天在家里奔跑&#xff0c;导致家里经常会出现“猫毛雪”&#xff0c;沙发、地板和衣服都成了重灾区。在除猫毛的问题上&#xff0c;我真的尝试了各种方法&#xff0c;几乎用上了所有的技能。…

MySQL知识点总结:构建可靠高性能的关系型数据库

摘要&#xff1a;MySQL是一款广泛使用的开源关系型数据库管理系统&#xff0c;具备可靠性和高性能的特点。本文将总结MySQL的一些重要知识点&#xff0c;帮助读者了解如何使用MySQL构建可靠高性能的关系型数据库。 正文&#xff1a; ### 1. 数据类型 MySQL支持多种数据类型&…

MySQL 中 int(1) 和 int(10) 会影响存储的长度吗

一、MySQL 中 int(1) 和 int(10) 在MySQL数据库设计中&#xff0c;经常会遇到 int 类型的字段&#xff0c;并会习惯性的指定长度&#xff0c;比如&#xff1a; int(1) 和int(10)&#xff0c;而一些新手可能会误解它们之间的关系&#xff0c;认为 int(10) 能够存储更多的数据。…

20240202在Ubuntu20.04.6下使用whisper.cpp的显卡模式

20240202在Ubuntu20.04.6下使用whisper.cpp的显卡模式 2024/2/2 19:43 【结论&#xff1a;在Ubuntu20.04.6下&#xff0c;确认large模式识别7分钟中文视频&#xff0c;需要356447.78 ms&#xff0c;也就是356.5秒&#xff0c;需要大概5分钟&#xff01;效率太差&#xff01;】 …

Scrum敏捷开发企业培训-敏捷研发管理

课程简介 Scrum是目前运用最为广泛的敏捷开发方法&#xff0c;是一个轻量级的项目管理和产品研发管理框架。 这是一个两天的实训课程&#xff0c;面向研发管理者、项目经理、产品经理、研发团队等&#xff0c;旨在帮助学员全面系统地学习Scrum和敏捷开发, 帮助企业快速启动敏…

【Docker】网络配置network详解

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

路由器、路由器的构成、交换结构

目录 1 路由器 1.1 路由器的结构 “转发”和“路由选择”的区别 1.1.1 输入端口对线路上收到的分组的处理 1.1.2 输出端口将交换结构传送来的分组发送到线路 2.2 交换结构 2.2.1 通过存储器 2.2.2 通过总线 2.2.3 通过纵横交换结构 (crossbar switch fabric) 1 路由器…

vue3-print-nb打印功能实现

在 vue3 项目中实现打印 1、安装插件 npm i vue3-print-nb 2、将插件引入 main.js 文件中 import print from vue3-print-nb const app createApp(App) app.use(print).mount(#app)3、在 .vue 页面使用 在 .vue 的页面来写一个包含 id 的 div <el-button type"…

【Django-ninja】分页管理器

django ninja通过paginate装饰器即可进行分页。内置了两个分页管理器LimitOffsetPagination和PageNumberPagination&#xff0c;能够实现基本的分页要求。当内置分页器不满足要求时&#xff0c;可以继承PaginationBase进行扩展自己的分页管理器。 1 使用分页器 from ninja.pa…

vue3学习——svg使用及封装组件,color不生效问题

安装 pnpm install vite-plugin-svg-icons -D在vite.config.ts中配置插件 import { createSvgIconsPlugin } from vite-plugin-svg-icons import path from path export default () > {return {plugins: [createSvgIconsPlugin({// Specify the icon folder to be cachedi…