【C语言】简易版扫雷+进阶版扫雷

目录

前言

一、分模块化

二、准备雷盘

2.1 游戏菜单

2.2 创建雷盘思路

2.3 构建雷盘

2.4 雷盘展示

2.4.1 初始化雷盘

2.4.2 打印雷盘

三、排雷

3.1 布置雷

3.2 排查雷

四、进阶版扫雷

总结


前言

C语言实现扫雷小游戏,帮我们更进一步的掌握数组、模块化思想等知识。


一、分模块化

对于扫雷小游戏,相信老铁们应该不陌生,根据信息进行排雷,大家可以参考网页版的扫雷游戏:扫雷小游戏

对于扫雷小游戏,虽然是一个小项目,代码量不多,但对于初学者来说,重要的是学习如何分模块开发项目。

本文将该游戏分成三个文件:

  • test.c游戏测试文件
  • game.c游戏执行逻辑(函数实现)
  • game.h游戏声明(函数声明,头文件)

二、准备雷盘

扫雷游戏,首先需要一个雷盘,本文讲解的雷盘为9×9规格。

2.1 游戏菜单

一个游戏,最先呈现给用户看的是游戏菜单,对于游戏菜单,可以用do...while循环 + switch选择语句完成,因为无论用户是否开始还是退出游戏,程序都会运行一次。

test.c文件
#include "game.h"
void menu()
{printf("********************\n");printf("***** 1、play ******\n");printf("***** 0、exit ******\n");printf("********************\n");
}
int main()
{int input = 0;do{//游戏菜单menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}game.h文件
#pragma once
#include <stdio.h>

2.2 创建雷盘思路

对于雷盘,使用二维数组存储数据是最合适的,符合行列。那一个雷盘就可以了吗?

我们规定:雷用字符1表示,非雷用字符0表示。

因此冲突点就出现了,如果对某个位置进行排雷,统计雷数时,正好也为1,那究竟是雷还是雷数呢?

为此,这里用了一个很巧妙的方法,我们定义两个大小一致的二维数组,也就是两个雷盘,一个雷盘用于布置雷,一个雷盘用于存放统计后雷的个数。

因此,我们有以下规定:

  • 布置雷的二维数组叫mine。
  • 存放雷数的二维数组叫show。因为要展示给用户看选择的位置周围的雷数,因此叫show。
  • mine雷盘中字符0为非雷,字符1为雷。
  • show雷盘中字符*表示未排查,字符数字表示该位置已被排查。

前面说过,我们的雷盘是一个9×9的规格,那相对于的两个数组的大小也是9×9吗?

其实不是,应该为11×11,因为当我们对最上、最下、最左、最右中的位置进行排雷时,统计周围有几颗雷时,会造成数组越界的情况,因此我们定义多2行2列的数据,这部分位置不存放雷,将雷存放在9×9的雷盘中,多定义的2行2列只是为了防止数组越界。


2.3 构建雷盘

对于数组的大小,我们不能写死,应该用#define标识符常量来表示,方便以后想扩大雷盘。并且我们需要分别定义9×9和11×11的标识符,因为在布置雷、打印雷盘、排雷的功能中只需要用到9×9的区域,而在初始化雷盘时,需要用到11×11区域。

test.c文件
#include "game.h"
void menu()
{printf("********************\n");printf("***** 1、play ******\n");printf("***** 0、exit ******\n");printf("********************\n");
}
void game()
{//定义两个大小相同的二维数组//这里的数组大小最好用#define标识符表示,后续想改变就该#define就好了//用于布置雷,用字符数组的原因是规定字符0为非雷,字符1为雷char mine[ROWS][COLS] = { 0 };//用于存放排查雷的信息(主要给用户看的页面//用字符数组的原因是规定雷数用字符数字表示,其它位置用字符*表示char show[ROWS][COLS] = { 0 };}
int main()
{int input = 0;do{//游戏菜单menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}game.h文件
#include <stdio.h>
/*解释一下这里定义的标识符:该游戏的排雷范围为9×9。点击一处位置,那周围的8处位置就要判断有几个雷。当位置位于上下一行时或者左右一行时,那访问周围8处位置时,就会造成越界访问了,因此这里可以加多两行两列,目的是为了不越界访问这两行两列不放置雷,雷的范围只在9×9中
*/
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2

2.4 雷盘展示

2.4.1 初始化雷盘

首先,我们要对六个雷盘进行初始化操作,mine雷盘一开始全为字符0,因为还没布置雷;show雷盘一开始全为字符*,因为还没开始排雷。

test.c文件
#include "game.h"
void game()
{//初始化两个棋盘//mine雷盘一开始全是字符0,因为还没布置雷InitializeMinefield(mine, ROWS, COLS, '0');//show雷盘一开始全是字符*,因为还没统计周围雷数InitializeMinefield(show, ROWS, COLS, '*');
}game.h文件
#pragma once
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化雷盘
//这里的参数写什么?
//既然是初始化雷盘,那要有个数组来接收传来的mine数组和show数组
//初始化9×9还是11×11?11×11,这样方便后续计算雷的个数
//还有一个参数很重要,那就是set,既然用一个函数就初始化两个雷盘
//那就要将标识字符传过来,进行设置
void InitializeMinefield(char init[ROWS][COLS],int rows,int cols,char set);game.c文件
#include "game.h"
//初始化雷盘
void InitializeMinefield(char init[ROWS][COLS], int rows, int cols, char set)
{for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){init[i][j] = set; //set为标识符号,mine雷盘传'0',show雷盘传'*'}}
}

初始化两个雷盘,要为每个雷盘分别写一个函数吗?

不需要,mine雷盘初始化时将字符0当成参数;show雷盘初始化时将字符*当成参数即可。


2.4.2 打印雷盘

打印雷盘的注意点在于打印多大的雷盘:是9×9,还是11×11?

前面说过,其实我们真正的雷盘大小为9×9的,11×11是为了让数组不越界。

test.c文件
#include "game.h"
void game()
{//打印雷盘,给用户展示的是show雷盘//打印多大?9×9,因为这是真正排雷区域PrintMindefield(show, ROW, COL);
}game.h文件
#pragma once
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2//打印雷盘
//这里需要注意,接收数组的大小需要写11×11,因为定义数组的时候就是11×11
//打印时只打印9×9,但在语法上来说,数组的大小为11×11。
void PrintMindefield(char print[ROWS][COLS],int row,int col);game.c文件
#include "game.h"
//打印雷盘
void PrintMindefield(char print[ROWS][COLS], int row, int col)
{printf("-------扫雷--------\n");//给雷盘编号,这样用户更快找到坐标进行排雷//上边编号for (int i = 0; i <= col; i++){printf("%d ", i);}printf("\n");//初始值从下标1开始,最后下标row/col//因为数组的大小为11×11,只打印9×9时,就不能从0开始了,结尾也同理for (int i = 1; i <= row; i++){printf("%d ", i); //左边编号for (int j = 1; j <= col; j++){printf("%c ", print[i][j]);}//每打印一行就\nprintf("\n");}
}

虽然我们只打印9×9的雷盘,但是定义数组时的大小为11×11,在函数形参接收时,数组中的[][]应为11×11,但传过去的行和列为9、9。


雷盘准备完毕,我们看看效果:


三、排雷

3.1 布置雷

布置雷的思路很简单,我们在mine数组上,生成随机坐标,需要注意的如下:

  • 该坐标位置没布置过雷(坐标内容为字符0)。
  • 生成随机数的函数,srand随机数生成器和rand函数搭配使用。
  • #define标识符定义雷的个数。
test.c文件
#include "game.h"
void game()
{//布置雷//在mine数组中布置//#define标识符定义雷数//同样,只需要在row × col中布置Place_mine(mine, ROW, COL);
}game.h文件
#pragma once
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//雷的总个数
#define Mine_Sum 10
//布置雷
void Place_mine(char mine[ROWS][COLS], int row, int col);game.c文件
#include "game.h"
//布置雷
//随机布置,随机生成坐标,使用rand函数前要设置随机生成器srand
/*条件:坐标不是雷,即不是'1'
*/
void Place_mine(char mine[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int count = Mine_Sum;//总雷数,当count为0时,表示已布置好雷while (count){x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}}

3.2 排查雷

对于用户操作方面:

  • 用户通过坐标进行排雷。
  • 输入的坐标有边界条件:x>=1 && x<=row && y>=1 && y<=col
  • 输入的坐标不能重复

什么时候结束呢?两种情况:

  • 被炸死
  • 排雷成功:排雷次数<row*col - 雷数

如何统计周围雷数:

  • 周围坐标之和 - 8*字符0。
  • 因为是mine数组中只有字符0或字符1.
  • 字符数字转为数字:字符数字-字符0 = 数字。因为字符进行加减操作时,是以ascll码进行。
  • 数字转为字符数字:数字+字符0 = 数字。
  • 因此周围坐标之和再给每个坐标依次减字符0,那最终的结果就为周围雷的个数(数字)。
  • 统计完成后,将个数转为字符个数,存储到对应位置的show雷盘中。
test.c文件
#include "game.h"
void game()
{//排雷(用户输入坐标排雷)/*排雷功能需要两个雷盘,mine雷盘用来检查周围雷的个数show雷盘用于存放统计好周围雷的个数,并展示*/Examine(mine, show, ROW, COL);
}game.h文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//雷的总个数
#define Mine_Sum 10
//排雷
void Examine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);game.c文件
#include "game.h"
//排雷
/*两个核心:1、通过用户坐标排雷2、检查周围坐标有几个雷后,放置到show雷盘中显示
*/
//统计该坐标周围的雷数
//此时的查找的雷盘范围为:11×11,防止越界
int StatisNumberMines(char mine[ROWS][COLS], int x, int y)
{/*mine雷盘中放的都是:字符0 或 字符1前备知识:字符数字转为数字: 字符数字 - 字符0 = 数字数字转字符数字:数字 + 字符0 = 字符数字原因:因为在进行字符间的加减操作时,是以ascll码进行的。现在mine雷盘中放的都是字符数字,那将8个位置的字符数字相加后: (8个位置之和) - 8*字符0再依次减字符0,那就能统计处周围有几个雷*/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';
}
void Examine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;/*坐标限制:1、不能重复2、不能超出有效范围:x>=1 && x<=row && y>=1 && y<=col*//*什么时候结束呢?两种情况:1、被炸死。2、排雷成功:排雷次数<row*col - 雷数*/int num = 0;while (num< row * col-Mine_Sum){printf("请玩家输入排雷坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){//输入坐标不能重复/*检查show雷盘,未排雷过的坐标为字符*,排查过的坐标字符0或字符数字*/if (show[x][y] == '*'){//判断是否是雷if (mine[x][y] == '0'){/*输入的坐标不是雷统计周围8个坐标有几颗雷*///在mine雷盘中统计int count = StatisNumberMines(mine, x, y);//统计好后放到show雷盘中//当然,要将数字转为字符数字,因为数组时char类型show[x][y] = count + '0';PrintMindefield(show, ROW, COL);num++;}//是雷就结束else{printf("您被炸死了\n");PrintMindefield(mine, ROW, COL);break;}}else{printf("该坐标已被排查\n");}}else{printf("输入的坐标超出范围\n");}}if (num == row*col - Mine_Sum){printf("恭喜你,排雷成功!\n");}
}

四、进阶版扫雷

简易版的扫雷有什么缺陷呢?

其实有很多功能都没有实现,比如即时、标记、剩余雷数等等,但最重要的是展开一片的功能没有实现,因此进阶版扫雷,将修改排雷函数,让用户输入坐标后,将不是周围一大片不是雷的地方展开,并提供更多雷数信息。

此时的扫雷,只能一个一个坐标的排查,并不会展开一片。

展开一大片的关键点:

  • 该坐标不是雷
  • 该坐标周围坐标没有雷
  • 排查坐标没有被排查过。(为什么?因为当有一个坐标满足三个条件后,然后该坐标周围又有坐标满足条件,进行展开,那原先满足条件的坐标也算是它周围8个坐标,那还要判断它吗?它已经不是雷了,因此这个条件很重要,容易造成死递归)
//统计该坐标周围的雷数
//此时的查找的雷盘范围为:11×11,防止越界
int StatisNumberMines(char mine[ROWS][COLS], int x, int y)
{/*mine雷盘中放的都是:字符0 或 字符1前备知识:字符数字转为数字: 字符数字 - 字符0 = 数字数字转字符数字:数字 + 字符0 = 字符数字原因:因为在进行字符间的加减操作时,是以ascll码进行的。现在mine雷盘中放的都是字符数字,那将8个位置的字符数字相加后: (8个位置之和) - 8*字符0再依次减字符0,那就能统计处周围有几个雷*/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';
}
//排雷,展开一片
/*基本思路:1、递归,终止条件为周围坐标之和不为0,说明周围有雷2、如果为0,那将该坐标设为空字符,然后依次查看周围坐标的周围坐标是否为0,如果是那就设置为空但前提是查看的坐标没有被排查过,就是坐标里的值是'*',并且该坐标没超出范围
*/
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{int count = StatisNumberMines(mine, x, y);if (count == 0){show[x][y] = ' ';int i = 0;int j = 0;for (i = x - 1; i <= x + 1; i++){for (j = y - 1; j <= y + 1; j++){if (show[i][j] == '*' && i > 0 && i < 10 && j>0 && j < 10){Unfold(mine, show, i, j);}}}}else{show[x][y] = count + '0';}
}
//查看show雷盘中还剩多少个位置没排查,当只剩Mine_Sum个时,排雷成功
int Win(char show[ROWS][COLS],int row,int col)
{int count = 0;for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){if (show[i][j] == '*'){count++;}}}return count;}
void Examine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;/*坐标限制:1、不能重复2、不能超出有效范围:x>=1 && x<=row && y>=1 && y<=col*//*什么时候结束呢?两种情况:1、被炸死。2、排雷成功:排雷次数<row*col - 雷数*/int num = 0;while (num< row * col-Mine_Sum){printf("请玩家输入排雷坐标:>");scanf("%d %d", &x, &y);if (x >= 1 && x <= row && y >= 1 && y <= col){//输入坐标不能重复/*检查show雷盘,未排雷过的坐标为字符*,排查过的坐标字符0或字符数字*/if (show[x][y] == '*'){//判断是否是雷if (mine[x][y] == '0'){//进阶版----展开一片Unfold(mine, show, x, y);PrintMindefield(show, ROW, COL);}//是雷就结束else{printf("您被炸死了\n");PrintMindefield(mine, ROW, COL);break;}}else{printf("该坐标已被排查\n");}}else{printf("输入的坐标超出范围\n");}if (Win(show, row, col) == Mine_Sum){printf("恭喜你,排雷成功!\n");PrintMindefield(mine, ROW, COL);break;}}
}

使用递归实现,当周围坐标没有雷时,将该坐标设置为空字符,然后再依次对周围坐标判断是否被排查过并且不超过有效范围,如果满足,则递归,看该坐标的周围坐标是否符合没有雷条件。直到周围坐标有雷,递归就结束,开始返回。


总结

这就是扫雷小游戏,希望对您有所帮助,后续会出更多干货,多多支持,关注我❤❤❤!源代码自取。

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

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

相关文章

bugku-web-decrypt

这里的提示解密后没有什么意义 这里下载文件包 得到一个index.php文件 得到代码 <?php function encrypt($data,$key) {$key md5(ISCC);$x 0;$len strlen($data);$klen strlen($key);for ($i0; $i < $len; $i) { if ($x $klen){$x 0;}$char . $key[$x];$x1;}for…

鸿蒙开发快速入门

基本概念 ArkTS 因为ArkTS是基于Type Script扩展而来&#xff0c;是Type Script的超集&#xff0c;所以也可以关注一下Type Script的语法来理解ArkTS的语法 ArkUI HarmonyOS提供了一套UI开发框架&#xff0c;即方舟开发框架&#xff08;ArkUI框架&#xff09;。方舟开发框架…

k8s:kubectl 命令设置简写启用自动补全功能

k8s&#xff1a;kubectl 命令设置简写&启用自动补全功能 1、设置kubectl命令简写2、启用kubectl自动补全功能 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Kubernetes&#xff08;K8s&#xff09;是一个强大的容器编排平台&#xff0…

MES管理系统在人工智能方面的应用

为了加强生产管理&#xff0c;提升企业管理水平&#xff0c;制造业之中的很多企业都运用的MES生产管理系统&#xff0c;借以提高对生产车间的监管。那么&#xff0c;MES系统应用的哪些技术&#xff0c;可以促使生产管理变得简单呢?其核心技术主要有以下几个方面。 1、过程控制…

Linux配置程序后台运行(前后台来回切换)

Linux配置程序后台运行 在日常开发过程中&#xff0c;会遇到我们在前台运行程序&#xff0c;此时我们临时有事&#xff0c;但不能关闭终端&#xff0c;否则程序就会在电脑熄屏&#xff08;终端session断开后&#xff09;停止运行。 那么作为一个合格的开发&#xff0c;就必须要…

LeetCode刷题记(三):61~90题

61. 旋转链表 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k 4 输出&…

今天掏心窝子!聊聊35岁了程序员何去何从?

今天的内容不聊技术&#xff0c;聊聊轻松的话题&#xff0c;脑子高速转了好几周&#xff0c;停下来思考一下人生…… 不对&#xff0c;关于35岁的问题好像也不轻松&#xff0c;些许有点沉重&#xff0c;反正不是技术&#xff0c;不用高速转动脑细胞了&#xff0c;哈哈。 兄弟…

未来已来,一键解锁AI秘境:全能型人工智能技术网站大揭秘(一键收藏)

1、KKAI&#xff08;kk.zlrxjh.t op&#xff09; KKAI是一个融合了星火大模型和文心大模型技术的知识增强型大语言模型&#xff0c;主要针对自然语言处理&#xff08;NLP&#xff09;的技术开发与研究。 该模型展现出卓越的语义理解与生成功能&#xff0c;能有效处理多样的自然…

【数据挖掘】实验6:初级绘图

实验6&#xff1a;初级绘图 一&#xff1a;实验目的与要求 1&#xff1a;了解R语言中各种图形元素的添加方法&#xff0c;并能够灵活应用这些元素。 2&#xff1a;了解R语言中的各种图形函数&#xff0c;掌握常见图形的绘制方法。 二&#xff1a;实验内容 【直方图】 Eg.1&…

一分钟学会旋转一个矩阵

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《深入浅出算法》系列…

05.MySQL索引事务

1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针。 可以对表中的一列或多列创建索引&#xff0c;并指定索引的类型&#xff0c;各类索引有各自的数据结构实现 1.2 作用 数据库中的表、数据、索引之间的关系&#xff0c;类似于书架上的…

解锁生成式 AI 的力量:a16z 提供的 16 个企业指南

企业构建和采购生成式AI方面的16项改变 生成式 AI 领域趋势洞察&#xff1a;企业构建和采购生成式 AI 的方式正在发生重大转变&#xff0c;具体表现在&#xff1a;* 专注于可信度和安全性&#xff1a;75% 的企业将信任和安全性视为关键因素。* 优先考虑可扩展性和灵活性&#x…