当前位置: 首页 > news >正文

【C语言篇】数组和函数的实践:扫雷游戏(附源码)

文章目录

  • 前言
  • 扫雷游戏的分析和设计
    • 扫雷游戏的功能说明
    • 游戏的分析和设计
    • 文件结构设计
  • 扫雷游戏的代码实现
    • 初始化棋盘
    • 打印棋盘
    • 布置雷
    • 排查雷
  • 扫雷游戏的拓展

前言

源码在最后

扫雷游戏的分析和设计

经典扫雷游戏

扫雷游戏的功能说明

使⽤控制台实现经典的扫雷游戏

  • 游戏可以通过菜单实现继续玩或者退出游戏

  • 扫雷的棋盘是9*9的格⼦

  • 默认随机布置10个雷

  • 可以排查雷

    • 如果位置不是雷,就显⽰周围有⼏个雷
    • 如果位置是雷,就炸死游戏结束
    • 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束

游戏的界⾯
在这里插入图片描述

被炸死,游戏结束

在这里插入图片描述


游戏的分析和设计

扫雷的过程中,布置的雷排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。

因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息。

在这里插入图片描述

那如果这个位置布置雷,我们就存放1,没有布置雷就存放0.

在这里插入图片描述

假设我们排查(2,5)这个坐标时,我们访问周围的⼀圈8个⻩⾊位置,统计周围雷的个数是1

假设我们排查(8,6)这个坐标时,我们访问周围的⼀圈8个⻩⾊位置,统计周围雷的个数时,最下⾯的三个坐标就会越界,为了防⽌越界,我们在设计的时候,给数组扩⼤⼀圈,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就⾏,这样就解决了越界的问题。所以我们将存放数据的数组创建成11*11⽐较合适。

在这里插入图片描述

再继续分析,我们在棋盘上布置了雷,棋盘上雷的信息(1)和⾮雷的信息(0),假设我们排查了某⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。

这⾥我们肯定有办法解决,⽐如:雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。

这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期排查参考。

同时为了保持神秘,show数组开始时初始化为字符 '*'为了保持两个数组的类型⼀致,可以使⽤同⼀套函数处理,mine数组最开始也初始化为字符'0',布置雷改成'1'

如下:

在这里插入图片描述

对应的数组应该是:

char mine[11][11] = {0};//⽤来存放布置好的雷的信息
char show[11][11] = {0};//⽤来存放排查出的雷的个数信息

文件结构设计

之前在【C语言篇】从零带你全面了解函数(包括隐式声明等)介绍了多⽂件的形式对函数的声明和定义,这⾥我们实践⼀下,我们设计三个⽂件:

⼀般情况下,企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。

函数的声明、类型的声明以及使用的库函数所需要包含的头文件都放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。 如下:

test.c //⽂件中写游戏的测试逻辑
game.c //⽂件中写游戏中函数的实现等
game.h //⽂件中写游戏需要的数据类型和函数声明等

扫雷游戏的代码实现

game.h

其中方法会一一讲到

  • 这里我们先用#define定义了行和列,是为了方便更改游戏难度,要更改棋盘大小只需要更改这几行数据即可
#pragma  once#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define EASY_COUNT 10//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

test.c

  • 测试游戏的主要逻辑,是main函数所在的.c文件

    • 一来先打印菜单

    • 然后使用do-while循环让玩家可以多次玩游戏


#include "game.h"void menu()
{printf("*************************\n");printf("*******  1. play    *****\n");printf("*******  0. exit    *****\n");printf("*************************\n");
}void test()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);
}int main()
{test();return 0;
}

以上部分在【C语言篇】猜数字游戏(赋源码)都是类似的,这里就不再赘述

  • 接下来就是重点,我们使用game()来分装我们实现的扫雷游戏的逻辑
void game()
{char mine[ROWS][COLS] = {0};//存放雷的信息char show[ROWS][COLS] = {0};//存放排查出的雷的信息//初始化棋盘InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//打印棋盘DisplayBoard(show, ROW, COL);//布置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);//排查雷FindMine(mine, show, ROW, COL);
}

这些方法的具体实现都是在game.c文件中实现

最好是写一个方法测试一次,不然找错误的时候会很痛苦😜

初始化棋盘

  • show棋盘是一来打印给玩家看的,保持神秘感使用'*'初始化
  • mine棋盘是用来布置雷的,使用'0'初始化

所以我们在参数中多加了一个char类型的set变量,这样只需使用一个函数就能初始化两个棋盘

棋盘的初始化就是二维数组初始化,遍历即可

//game.h
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//game.c
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = set;}}
}

打印棋盘

  • 方便我们测试观察,并且需要将show数组每次打印给玩家看,所以写一个打印棋盘的函数
  • 为了方便观察和读坐标,我们希望把坐标也打印出来

注意showmine数组扩大了一圈,有效的下标范围都是1-9
在这里插入图片描述

//game.h
void DisplayBoard(char board[ROWS][COLS], int row, int col);//game.c
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("--------扫雷------\n");int i = 0;for (i = 0; i <= col; i++){printf("%d ", i);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);int j = 0;for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}
}

布置雷

  • 要布置雷,需要是随机的,所以需要用到随机数生成

以下内容在【C语言篇】猜数字游戏(赋源码)有很详细的介绍,这里简略说一下,想更具体的了解的读者可以去看看

C语⾔提供了⼀个函数叫rand,这函数是可以⽣成随机数的,函数原型如下所⽰:

int rand (void);

rand函数会返回⼀个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的⼤⼩是依赖编译器上实现的,但是⼤部分编译器上是32767。

rand函数的使⽤需要包含⼀个头⽂件是:stdlib.h


C语⾔中⼜提供了⼀个函数叫srand,⽤来初始化随机数的⽣成器的,srand的原型如下:

void srand (unsigned int seed);

程序中在调⽤rand函数之前先调⽤srand函数,通过srand函数的参数seed来设置rand函数⽣成随机数的时候的种⼦,只要种⼦在变化,每次⽣成的随机数序列也就变化起来了。


在程序中我们⼀般是使⽤程序运⾏的时间作为种⼦的,因为时间时刻在发⽣变化的。

在C语⾔中有⼀个函数叫time,就可以获得这个时间,time函数原型如下:

time_t time (time_t* timer);

time函数会返回当前的⽇历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的差值,单位是秒。返回的类型是time_t类型的,time_t类型本质上其实就是32位或者64位的整型类型。

time函数的参数timer如果是⾮NULL的指针的话,函数会将这个返回的差值放在timer指向的内存中。

如果timerNULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳。

time函数的时候需要包含头⽂件:time.h


所以在test.c我们使用了如下代码初始化rand函数的种子

srand((unsigned int)time(NULL));

好的,回到正题

  • 为了方便更改游戏难度,使用#define定义我们需要布置的雷的个数,这里以10个为例
  • 还是注意x和y的坐标范围
  • 在布置雷前别忘了先判断这里是否已经布置过雷,保证最后一定是布置了是个雷
//game.h//game.c
//布置雷是在棋盘上随机的找10个坐标布置的
//x: 1~9
//y: 1~9void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;int x = 0;int y = 0;while (count){x = rand()%row+1;y = rand()%col+1;if (mine[x][y] != '1'){mine[x][y] = '1';//布置一个雷count--;}}
}

排查雷

  • 玩家输入坐标

    • 判断玩家输入坐标范围是否正确

    • 如果是雷,就游戏结束,打印mine棋盘

在这里插入图片描述

  • 如果不是,统计mine棋盘此位置周围8个地方的雷的数量,并将show棋盘对应位置更改为雷的个数,打印show棋盘

在这里插入图片描述

  • 如何判断玩家是否获得胜利?

    • 以10个雷为例,总共棋盘大小为9*9,所以玩家需要将剩下的71个不是雷的地方找出来,定义win变量循环实现,每次猜中就++,最后如果和需要猜的次数相等,则说明排雷成功
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win<col*row-EASY_COUNT){printf("请输入要排查的坐标:");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){//输入的位置是雷if (mine[x][y] == '1'){printf("很遗憾,你踩雷了,游戏结束\n");DisplayBoard(mine, ROW, COL);break;}else  //不是雷{int count = GetMineCount(mine, x, y);show[x][y] = count + '0';DisplayBoard(show, ROW, COL);win++;}}else{printf("输入的坐标有误x(1~9),y(1~9),重新输入");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}
  • 为了得到雷的数量,我们实现另一个函数
    • 利用数学关系,依次相加,注意mine数组里面是数字字符,所以我们要减去'0'才能得到真正的数字
    • 使用循环简化一下也可以

在这里插入图片描述

//方法一:
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{return mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] +mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8*'0';
}
//方法二:
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{int i = 0;int count = 0;for (i = -1; i <= 1; i++){int j = 0;for (j = -1; j <= 1; j++){count += (mine[x + i][y + j] - '0');}}return count;
}
  • 得到了雷的个数,由于show数组也是char类型的,所以也需要给count+'0'才能赋值到show棋盘对应的位置,然后打印即可

以上就是最基本的扫雷游戏的逻辑实现

源码如下:

game.h

#pragma  once#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define EASY_COUNT 10//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

include "game.h"void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = set;}}
}void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("--------扫雷------\n");int i = 0;for (i = 0; i <= col; i++){printf("%d ", i);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);int j = 0;for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}
}//布置雷是在棋盘上随机的找10个坐标布置的
//x: 1~9
//y: 1~9void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;int x = 0;int y = 0;while (count){x = rand()%row+1;y = rand()%col+1;if (mine[x][y] != '1'){mine[x][y] = '1';//布置一个雷count--;}}
}//int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
//	return mine[x - 1][y] + 
//		mine[x - 1][y - 1] + 
//		mine[x][y - 1] + 
//		mine[x + 1][y - 1] +
//		mine[x + 1][y] + 
//		mine[x + 1][y + 1] + 
//		mine[x][y + 1] + 
//		mine[x - 1][y + 1] - 8*'0';
//}int GetMineCount(char mine[ROWS][COLS], int x, int y)
{int i = 0;int count = 0;for (i = -1; i <= 1; i++){int j = 0;for (j = -1; j <= 1; j++){count += (mine[x + i][y + j] - '0');}}return count;
}void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win<col*row-EASY_COUNT){printf("请输入要排查的坐标:");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){//输入的位置是雷if (mine[x][y] == '1'){printf("很遗憾,你踩雷了,游戏结束\n");DisplayBoard(mine, ROW, COL);break;}else  //不是雷{int count = GetMineCount(mine, x, y);show[x][y] = count + '0';DisplayBoard(show, ROW, COL);win++;}}else{printf("输入的坐标有误x(1~9),y(1~9),重新输入");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}

test.c


#include "game.h"
void game()
{char mine[ROWS][COLS] = {0};//存放雷的信息char show[ROWS][COLS] = {0};//存放排查出的雷的信息//初始化棋盘InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//打印棋盘DisplayBoard(show, ROW, COL);//布置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);//排查雷FindMine(mine, show, ROW, COL);
}void menu()
{printf("*************************\n");printf("*******  1. play    *****\n");printf("*******  0. exit    *****\n");printf("*************************\n");
}void test()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误,重新选择\n");break;}} while (input);
}int main()
{test();return 0;
}

扫雷游戏的拓展

  • 是否可以选择游戏难度

    • 简单 9*9 棋盘,10个雷

    • *中等 16*16棋盘,40个雷

    • 困难 30*16棋盘,99个雷

  • 如果排查位置不是雷,周围也没有雷,可以展开周围的⼀⽚

    • 这里需要用递归
  • 是否可以标记雷

  • 是否可以加上排雷的时间显⽰

等等·····,篇幅有限,就不再继续介绍了,各位读者有兴趣可以去实现一下呀👍


以上就是关于【C语言篇】数组和函数的实践:扫雷游戏(附源码)的内容啦,各位大佬有什么问题欢迎在评论区指正,您的支持是我创作的最大动力!❤️

在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 抽卡机小程序,开启全新拆卡乐趣
  • 基于Python的金融数据采集与分析的设计与实现
  • 【银河麒麟高级服务器操作系统】实际案例分析,xfsaild占用过高
  • Chapter 8 事件组
  • gitlab修改默认访问端口
  • 简单的class.getResource与classLoader.getResource区别
  • 【Golang】go mod的使用
  • 性能测试 —— linux服务器搭建JMeter+Grafana+Influxdb监控可视化平台!
  • Spring Boot集成钉钉群通知机器人
  • Vue2 和 Vue3 自定义指令比较
  • 昂科烧录器支持PAI-IC澎湃微电子的32位微控制器PT32L031K6T6
  • 多模态论文自己学习路程_每天推出新版本_请看当天版本
  • 【vue3|第23期】Vite + Vue3: 深入理解public和assets文件夹的作用与使用
  • 安装postgresql和PGVector
  • Linux线程基础学习记录(线程的创建、回收以及结束)
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • Android开源项目规范总结
  • Apache Pulsar 2.1 重磅发布
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • GraphQL学习过程应该是这样的
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Lucene解析 - 基本概念
  • mysql innodb 索引使用指南
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Rancher如何对接Ceph-RBD块存储
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • springboot_database项目介绍
  • Terraform入门 - 1. 安装Terraform
  • 构建二叉树进行数值数组的去重及优化
  • 我感觉这是史上最牛的防sql注入方法类
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 白色的风信子
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • #基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
  • ${ }的特别功能
  • (4)logging(日志模块)
  • (差分)胡桃爱原石
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (三)mysql_MYSQL(三)
  • (算法)N皇后问题
  • (算法)求1到1亿间的质数或素数
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)负载均衡,回话保持,cookie
  • (转载)Linux网络编程入门
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .Net Core 笔试1
  • .Net 垃圾回收机制原理(二)
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .NET8 动态添加定时任务(CRON Expression, Whatever)
  • .net反混淆脱壳工具de4dot的使用
  • .NET高级面试指南专题十一【 设计模式介绍,为什么要用设计模式】
  • .net通过类组装数据转换为json并且传递给对方接口
  • .NET中 MVC 工厂模式浅析
  • .NET中GET与SET的用法