【C语言】简易版扫雷游戏(数组、函数的练习)

news2025/1/12 10:39:29

目录

一、分析和设计

1.1、扫雷游戏的功能分析

1.2、文件结构设计(多文件的练习)

1.3、数据结构的设计

二、代码

三、效果展示

三、优化


一、分析和设计

1.1、扫雷游戏的功能分析

        以在线版的扫雷游戏为参考,分析它的功能:扫雷游戏网页版 - Minesweeper

  • 使用控制台的输入控制游戏,控制台的输出打印游戏。
  • 简单版:9 x 9 尺寸的棋盘,10个雷。
  • 排雷规则:
(1)如果不是雷,则显示周围(8个位置)的雷个数。

(2)如果是雷,游戏失败。

(3)如果把雷之外的所有非雷位置找出,游戏成功。

1.2、文件结构设计(多文件的练习)

test.c:游戏的主逻辑,可用于测试。

game.c:实现游戏的各种函数。

game.c:实现游戏的各种函数的声明、数据的声明。

1.3、数据结构的设计

       存储棋盘形状的数据,使用二维数组再好不过了,易理解易操作。需要存储的数据:① 哪些位置是雷(用0和1表示)。② 打印在控制台的扫雷画面(未排雷的位置显示 ' * ' ,已排雷且非雷的位置显示周围雷的个数)。

        因为有字符' * ',所以存周围雷个数的数组的元素用char类型;虽然存雷位置的数组都是数字0和1,但是统一起见,元素的类型都定义为char,这样写代码的时候不用区分char还是int,不宜弄错。

        虽然说这两种数据可以放到一起,比如非雷位置放9,雷位置放10;排雷时非雷位置(9)可改成周围雷个数(≤8);打印时,等于9或10的位置打印' * ',小于等于8的位置打印周围雷个数。但是这样在打印时会多一些判断,读代码的人理解起来也没有分成两个数组容易。

        数据结构如下:

char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};

        排雷时,如果找到非雷,则显示周围雷的个数。当这个非雷是二维数组的边缘部分时,统计周围雷的个数会发生越界:

        为了解决这个问题,需要把数组的大小增大一圈(两个数组统一增大,免得写代码判断的时候范围不统一,记混了):

二、代码

        game.c

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"

void print_menu(){
	printf("******************************\n");
	printf("*********    1.play   ********\n");
	printf("*********    2.exit   ********\n");
	printf("******************************\n");
	printf("请选择:");
}

void init_mine(char mine[][EASY_COLS]) {
	for (int i = 0; i < EASY_ROWS; i++)
		for (int j = 0; j < EASY_COLS; j++)
			mine[i][j] = '0';
}

void init_show(char show[][EASY_COLS]) {
	for (int i = 0; i < EASY_ROWS; i++)
		for (int j = 0; j < EASY_COLS; j++)
			show[i][j] = '*';
}

void set_mine(char mine[][EASY_COLS]) {
	int r = 0;
	int c = 0;

	for (int i = 0; i < EASY_COUNT; i++) {
		r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
		c = rand() % EASY_COL + 1;
		if ('0' == mine[r][c]) // 这个位置不是雷,放雷
			mine[r][c] = '1';
		else
			i--; // 这个位置已经是雷了,这轮不算
	}
}

void print_mine(char mine[][EASY_COLS]) {
	for (int i = 0; i <= EASY_ROW; i++)
		printf("%d ", i);
	printf("\n");
	for (int i = 1; i <= EASY_ROW; i++) {
		printf("%d ", i);
		for (int j = 1; j <= EASY_COL; j++)
			printf("%c ", mine[i][j]);
		printf("\n");
	}
}

void print_show(char show[][EASY_COLS]) {
	for (int i = 0; i <= EASY_ROW; i++)
		printf("%d ", i);
	printf("\n");
	for (int i = 1; i <= EASY_ROW; i++) {
		printf("%d ", i);
		for (int j = 1; j <= EASY_COL; j++)
			printf("%c ", show[i][j]);
		printf("\n");
	}
	printf("输入坐标:");
}

void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]) {
	int x = 0; // 输入的坐标
	int y = 0;
	int count = 0; // 已找到的的非雷

	while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
		print_show(show); // 打印棋盘
		scanf("%d %d", &x, &y); // 输入坐标
		system("cls");
		if (mine[x][y] == '1') {
			printf("炸弹!游戏失败!\n");
			break;
		}
		else if (mine[x][y] == '0') {
			char c = count_mine(mine, x, y); // 计算周围雷的个数
			show[x][y] = c;
			count++; //找到一个非雷
		}
	}
	if (count == EASY_ROW * EASY_COL - EASY_COUNT)
		printf("排雷成功!\n");
}

char count_mine(char mine[][EASY_COLS], int x, int y) {
	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
}

        game.h

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

#define EASY_ROW 9
#define EASY_COL 9

#define EASY_COUNT 10

#define EASY_ROWS EASY_ROW+2
#define EASY_COLS EASY_COL+2

void print_menu();
void init_mine(char mine[][EASY_COLS]);
void init_show(char show[][EASY_COLS]);
void set_mine(char mine[][EASY_COLS]);
void print_mine(char mine[][EASY_COLS]);
void print_show(char show[][EASY_COLS]);
void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS]);
char count_mine(char mine[][EASY_COLS], int x, int y);

        test.c

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"

void game() {
	char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
	char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数

	init_mine(mine); // 初始化,全放'0'
	init_show(show); // 初始化棋盘,全为'*'
	set_mine(mine); // 随机放雷
	find_mine(mine, show);// 扫雷
}

int main() {
	int choose = 0;

	srand((unsigned int)time(NULL)); // 设置随机种子
	do{
		print_menu(); // 打印主菜单
		scanf("%d", &choose); // 输入选择
		system("cls"); // 清屏
		switch (choose) {
		case 1: // 开始游戏
			game();
			break;
		case 2: // 退出
			break;
		default:
			printf("输入错误,请重新输入。\n");
		}
	} while (choose != 2);

	return 0;
}

三、效果展示

三、优化

        代码还有很多需要优化的地方,比如:

  • 如果排查的位置不是雷,它周围也没雷,周围可以进行展开。
  • 可以选择游戏难度:简单(9x9,10个雷)、中等(16x16,40个雷)、困难(16x30,99个雷)、自定义。
  • 可以标记雷。
  • 游戏中,可以显示时间。

(1)增加展开、标记雷功能

        代码如下:

        game.h:

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

#define EASY_ROW 9
#define EASY_COL 9

#define EASY_COUNT 10

#define EASY_ROWS EASY_ROW+2
#define EASY_COLS EASY_COL+2

// 打印字体颜色
#define NONE "\033[m"
#define BLUE "\033[0;32;34m"
#define RED "\033[0;32;31m"

void print_menu();
void init_mine(char mine[][EASY_COLS]);
void init_show(char show[][EASY_COLS]);
void set_mine(char mine[][EASY_COLS]);
void print_mine(char mine[][EASY_COLS]);
void print_show(char show[][EASY_COLS]);
void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]);
char count_mine(char mine[][EASY_COLS], int x, int y);
void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt);
void mark_mine(char flag[][EASY_COLS]);

        game.c:

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"

void print_menu(){ // 打印主菜单
	printf("******************************\n");
	printf("*********    1.play   ********\n");
	printf("*********    2.exit   ********\n");
	printf("******************************\n");
	printf("请选择:");
}

void init_mine(char mine[][EASY_COLS]) { // 初始化雷信息
	for (int i = 0; i < EASY_ROWS; i++)
		for (int j = 0; j < EASY_COLS; j++)
			mine[i][j] = '0';
}

void init_show(char show[][EASY_COLS]) { //初始化棋盘
	for (int i = 0; i < EASY_ROWS; i++)
		for (int j = 0; j < EASY_COLS; j++)
			show[i][j] = '*';
}

void set_mine(char mine[][EASY_COLS]) { // 随即放置雷
	int r = 0;
	int c = 0;

	for (int i = 0; i < EASY_COUNT; i++) {
		r = rand() % EASY_ROW + 1; // 雷要在棋盘里边,不能在增加的一圈里
		c = rand() % EASY_COL + 1;
		if ('0' == mine[r][c]) // 这个位置不是雷,放雷
			mine[r][c] = '1';
		else
			i--; // 这个位置已经是雷了,这轮不算
	}
}

void print_mine(char mine[][EASY_COLS]) { // 打印雷信息
	for (int i = 0; i <= EASY_ROW; i++)
		printf(BLUE"%d "NONE, i);
	printf("\n");
	for (int i = 1; i <= EASY_ROW; i++) {
		printf(BLUE"%d "NONE, i);
		for (int j = 1; j <= EASY_COL; j++)
			printf("%c ", mine[i][j]);
		printf("\n");
	}
}

void print_show(char show[][EASY_COLS], char flag[][EASY_COLS]) { // 打印棋盘
	for (int i = 0; i <= EASY_ROW; i++)
		printf(BLUE"%d "NONE, i);
	printf("\n");
	for (int i = 1; i <= EASY_ROW; i++) {
		printf(BLUE"%d "NONE, i);
		for (int j = 1; j <= EASY_COL; j++) {
			if('1' == flag[i][j])
				printf(RED"%c "NONE, show[i][j]);
			else
				printf("%c ", show[i][j]);
		}
		printf("\n");
	}
	printf("输入坐标(输入0 0标记雷):");
}

void find_mine(char mine[][EASY_COLS], char show[][EASY_COLS], char flag[][EASY_COLS]) { // 排雷
	int x; // 输入的坐标
	int y;
	int count = 0; // 已找到的的非雷

	while (count != EASY_ROW * EASY_COL - EASY_COUNT) { // 没有找到所有非雷,游戏就继续
		x = 0;
		y = 0;
		while (x == 0 && y == 0) {
			print_show(show, flag); // 打印棋盘
			scanf("%d %d", &x, &y); // 输入坐标
			if (0 == x && 0 == y) // 标记雷
				mark_mine(flag);
			system("cls");
		}
		if (mine[x][y] == '1') { // 碰到炸弹了
			printf("炸弹!游戏失败!\n");
			break;
		}
		else if (mine[x][y] == '0' && show[x][y] == '*') { // (x,y)不是炸弹,并且没有被排查过
			find_other_mine(mine, show, x, y, &count);
		}
	}
	if (count == EASY_ROW * EASY_COL - EASY_COUNT) // 所有非雷点找齐了
		printf("排雷成功!\n");
}

char count_mine(char mine[][EASY_COLS], int x, int y) { // 计算非雷点(x,y)周围有几个雷
	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0' + '0';
}

void find_other_mine(char mine[][EASY_COLS], char show[][EASY_COLS], int x, int y, int* cnt) { // 递归,排查的位置不是雷,周围也没有雷,可以展开
	char c = count_mine(mine, x, y); // 计算周围雷的个数
	if(c == '0')
		show[x][y] = ' ';
	else
		show[x][y] = c;
	(*cnt)++; //找到一个非雷
	if (show[x][y] != ' ') { // 周围有雷,就不需要扩展了
		return;
	}
	for (int i = -1; i <= 1; i++) {
		for (int j = -1; j <= 1; j++) {
			int xt = x + i;
			int yt = y + j;
			if (show[xt][yt] != '*' || mine[xt][yt] == '1') // 已经排查过的,就不需要重复扩展了;本身是炸弹的,也不要扩展
				continue;
			if(xt >= 1 && yt >= 1 && xt <= EASY_ROW && yt <= EASY_COL) // 拓展的坐标要在棋盘范围内
				find_other_mine(mine, show, xt, yt, cnt);
		}
	}
}

void mark_mine(char flag[][EASY_COLS]) {
	int x;
	int y;
	printf("输入雷的坐标:");
	scanf("%d %d", &x, &y); // 输入坐标
	flag[x][y] = '1';
}

        test.c:

#define _CRT_SECURE_NO_WARNINGS 1;
#include "game.h"

void game() {
	char mine[EASY_ROWS][EASY_COLS]; // 存储生成的雷
	char show[EASY_ROWS][EASY_COLS]; // 存储排雷后雷的个数
	char flag[EASY_ROWS][EASY_COLS]; // 屏幕上,标记的雷

	init_mine(mine); // 初始化,全放'0'
	init_show(show); // 初始化棋盘,全为'*'
	init_mine(flag); // 初始化全'0','0' 表示未标记,‘1’表示已标记
	set_mine(mine); // 随机放雷
	find_mine(mine, show, flag);// 扫雷
}

int main() {
	int choose = 0;

	srand((unsigned int)time(NULL)); // 设置随机种子
	do{
		print_menu(); // 打印主菜单
		scanf("%d", &choose); // 输入选择
		system("cls"); // 清屏
		switch (choose) {
		case 1: // 开始游戏
			game();
			break;
		case 2: // 退出
			break;
		default:
			printf("输入错误,请重新输入。\n");
		}
	} while (choose != 2);

	return 0;
}

        效果展示:

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

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

相关文章

JAVA中的多线程详解

1.概念 进程(Process)&#xff1a; 进程是一个包含自身执行地址的程序&#xff0c;多线程使程序可以同时存在多个执行片段&#xff0c;这些执行片段根据不同的条件和环境同步或者异步工作&#xff0c;由于转换的数独很快&#xff0c;使人感觉上进程像是在同时运行。 现在的计…

Kafka知识总结(事务+数据存储+请求模型+常见场景)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 事务 事务Producer保证消息写入分区的原子性&#xff0c;即这批消…

nodejs与npm版本对应表

Node.js — Node.js 版本 (nodejs.org)

GB28181国标视频汇聚平台EasyCVR视频管理系统如何更改GIS地图的默认位置?

GB28181国标视频汇聚平台EasyCVR视频管理系统以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力&#xff0c;为各行各业的视频监控需求提供了优秀的解决方案。通过简单的配置和操作流程&#xff0c;用户可以轻松地进行远程视频监控、存储和查看&#xff0…

【Qt开发】No matching signal for on_toolButton_clicked() 解决方案

【Qt开发】No matching signal for on_toolButton_clicked() 解决方案 文章目录 No matching signal for xxx 解决方案附录&#xff1a;C语言到C的入门知识点&#xff08;主要适用于C语言精通到Qt的C开发入门&#xff09;C语言与C的不同C中写C语言代码C语言到C的知识点Qt开发中…

【C51】8051 微控制器入门指南

目录 1. 理解 C51 编程环境1.1 了解 8051 微控制器架构1.2 设置开发环境 2. 编写 C51 嵌入式代码2.1 基础代码结构2.2 使用寄存器和 I/O 端口2.3 中断处理2.4 调试和测试 3. 高级特性和优化3.1 嵌套中断3.2 内存管理3.3 外设接口3.4 编译器优化 4. 示例项目4.1 LED 闪烁程序4.2…

vardaccico前端私有库

vardacico docker pull verdaccio/verdaccio:4 docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio Docker | Verdaccio 拷贝docker中的配置到宿主机 进入docker内部 docker exec -it verdaccio /bin/sh 进入到指定目录 cd /verdaccio 开始拷贝到指定目…

BOM管理挑战:识别不同业务需求下的应对策略

BOM作为制造行业中的核心概念&#xff0c;其架构的复杂度直接影响到企业的运营效率、成本控制以及市场响应速度。道合顺接下来将介绍如何在管理目标与BOM架构复杂度之间找到平衡点&#xff0c;以满足不同业务需求&#xff0c;助力企业在激烈的市场竞争中脱颖而出。 一、理解BO…

window下编译UCL

window下编译UCL 一、环境安装二、编译error: ACC conformance test failed. Stop. 一、环境安装 安装minGW minGW中安装g 和gcc 安装msys-base 二、编译 启动msys.bat C:\MinGW\msys\1.0\msys.bat 切换到ucl源码目录 执行语句 ./configure CPPFLAGS"$CPPFLAGS -stdc…

昇思25天学习打卡营第19天|DCGAN生成漫画头像

DCGAN生成漫画头像总结 实验概述 本实验旨在利用深度卷积生成对抗网络&#xff08;DCGAN&#xff09;生成动漫头像&#xff0c;通过设置网络、优化器以及损失函数&#xff0c;使用MindSpore进行实现。 实验目的 学习和掌握DCGAN的基本原理和应用。熟悉使用MindSpore进行图像…

气象水文耦合模WRF-Hydro建模技术

原文链接&#xff1a;气象水文耦合模WRF-Hydro建模技术https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247610398&idx4&sn34b4bbed4c74dcbbb0ac19ef8dcdaaff&chksmfa8271f9cdf5f8ef34ea6f721736a2fbbf8be896744ab7e46caa571c52a30628f056b4bd6964&t…

AI如何助力UI设计师互联网学习?

嘿&#xff0c;咱 UI 设计师想用互联网学习&#xff0c;可真不容易&#xff01;资料筛选难&#xff0c;学习资源杂&#xff0c;真让人头疼。不过还好有 AI 工具能帮忙&#xff0c;提效率&#xff01; 这一年多来&#xff0c;我在 ai123.cn 这个平台上&#xff0c;可算是找到了…

羊大师:羊奶营养揭秘,健康关爱的另一优选选择

在琳琅满目的乳制品中&#xff0c;羊奶如同一颗璀璨的明珠&#xff0c;以其独特的营养价值和健康益处&#xff0c;逐渐走进千家万户&#xff0c;成为许多人健康关爱的新选择。那么&#xff0c;羊奶究竟蕴藏着怎样的营养奥秘&#xff0c;让它能够在众多饮品中脱颖而出呢&#xf…

【MySQL】:在Centos 7 环境下的安装

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来如何在Centos7环境下安装MySQL&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

关于C++11一些新特性的介绍(下)

文章目录 1. 可变参数模板1.1 可变参数模板介绍1.2 STL容器中的empalce相关接口函数 2. lambda表达式2.1 lambda诞生背景2.2 lambda表达式语法2.3 捕捉列表说明2.4 函数对象与lambda表达式 3. 包装器3.1 function包装器3.2 bind函数 1. 可变参数模板 1.1 可变参数模板介绍 C1…

我的创作纪念日(一)——Giser?Noder?不如“Computer”

目录 Giser&#xff1f;Noder&#xff1f;不如“Computer” 一、根源&#xff1a;保持学习习惯的刚需 二、机缘&#xff1a;processOn的另类替代 三、日常&#xff1a;对技术栈丰富的思考 四、成就&#xff1a;保持心态健康的活着 五、憧憬&#xff1a;能一直心态健康的活…

大模型学习(1)

初学者&#xff0c;仅做自己学习记录&#xff0c;如果对你有什么帮助&#xff0c;那更好了。 下面是论文《Attention Is All You Need》的经典transformer架构&#xff0c;在学习的过程中&#xff0c;有很多疑惑。 embedding层在做什么 Transformer的embedding层在做的是将输…

【C++进阶】AVL树详解

文章目录 1. AVL树的概念2. AVL树结点的定义3. AVL 树的插入3.1 关于平衡因子3.2 插入代码 4. AVL 树的旋转逻辑4.1 不需要旋转4.2 左旋4.3 右旋4.4 双旋4.4.1 先右后左单旋&#xff08;RL 旋转&#xff09;4.4.2 先左后右单旋&#xff08;LR 旋转&#xff09; 4.5 完整插入代码…

正则采集器之五——商品匹配规则

需求设计 实现分析 系统通过访问URL得到html代码&#xff0c;通过正则表达式匹配html&#xff0c;通过反向引用来得到商品的标题、图片、价格、原价、id&#xff0c;这部分逻辑在java中实现。 匹配商品的正则做成可视化编辑&#xff0c;因为不同网站的结构不同&#xff0c;同…