【C语言】扫雷的模拟实现详解
前言
扫雷是我们同年的小游戏,今天我们就来实现一下这个童年小游戏。
与上次三子棋一样,我们用三个文件完成这个程序。
目录
- 一. 设计思路
- 1.选项
- 2. 扫雷游戏的设计
- 3. 棋盘的初始化
- 4. 布置雷的函数
- 5. 棋盘的打印
- 6. 排雷函数
- 6.1 排雷
- 6.2 记录周围雷个数
- 6.3 非雷的连片展开
- 6.4 标雷
- 二. 整体程序的完成
- 1. ==game.h文件== :声明
- 2. ==game.c文件== :扫雷的实现模块
- 3. ==test.c文件==:测试扫雷的基本功能
- 结尾
一. 设计思路
1.选项
每一个游戏都有属于自己的游戏选择界面
(1 / 0) 玩游戏/退出游戏 && 输入其他数字则输入错误重新输入
void menu()
{printf("**************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("**************************\n");
}
#include "game.h"int main()
{int input = 0;menu();do{printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("输入错误\n");break;}} while (input);return 0;
}
游戏模块
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);
}
主函数中的#include
并没有使用库函数的头文件,而是使用自己写出来的game.h文件
。
2. 扫雷游戏的设计
我们用两个数组[show 数组 (用户排雷观看用)与 mine数组(布置雷用)], show数组用来展示该坐标周围八个坐标有多少个雷。当我学习了一位大佬的设计,mine数组用来设置地雷,用字符'1'
来设置为雷,而字符'0'
为非雷,当然我们也可以用其他字符来设置雷,但是当我们看了后续代码就可以看出大佬这样设计的方便之处了。
show数组(棋盘)
mine数组(棋盘)
当我们设置这么一个 9 x 9
的二维数组,就会发现当选择的坐标不是边界的坐标时,只需要统计周围八个坐标是否是雷,而如果是选择边界的坐标时,若访问周围八个坐标时,则有可能会越界访问,并且如果我们将这些特殊情况单独列出来,对于我们目前来说还是有些许困难,所以我选择在这个9 x 9
的二维数组加大一圈。
如果我们在我们需要的大小下再加大一圈的话,就不需要担心这个问题了。若我们需要一个 9 x 9
的扫雷界面 ,我们则设计一个11 x 11
的数组。像下图,我们需要的是蓝色部分,但是如果我们选择的是边界,周围八个坐标都在11 x 11
的数组中,也不会出现数组越界的情况。
3. 棋盘的初始化
我们要将棋盘的内容进行初始化。当我们进入一个扫雷游戏的时候,刚开始我们什么都看不见,所以我们在给用户显示的棋盘上都初始化为*
,对于布置雷的棋盘先全部初始化为'0'
,之后再布置雷。
当我们看下面这段初始化代码,这里的board[i][j]
我们并不知道放什么值,假设初始化mine棋盘放'0'
,那么我们还要设计一个初始化show棋盘的函数,相反也是,无论我们传什么进去都要设计两个函数,下面对该函数进行优化。
void InitBoard(char board[ROWS][COLS], int rows, int cols)
{int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = ; // 0 ? *}}
}
当我们将函数多一个参数,我们想初始化什么就将该字符传进去。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = set;}}
}
4. 布置雷的函数
注意事项:
(1)布置雷的时候,记得控制坐标的范围,防止越界。
(2)布置雷的时候,要判断该坐标是否为非雷的坐标。
(3)记得rand()函数
使用的必要条件。
void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;while (count){int x = rand() % row + 1;int y = rand() % col + 1;if (x >= 1 && x <= row && y <= col && y >= 1){if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}}
}
5. 棋盘的打印
这里做一个用户方便看的棋盘可能要试验好几次。
记得在每一行,每一列记录一下行号和列号,方便用户输入坐标。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 1;int j = 1;printf(" *************** 扫雷 ***************\n");for (j = 0; j <= col; j++){printf(" %d ", j); //记录行号}printf("\n\n");for (i = 1; i <= row; i++) {printf(" %d ", i); 记录列号for (j = 1; j <= col; j++){printf(" %c ", board[i][j]);if (j != col){printf("|");}}printf("\n");if (i != row){printf(" ");for (j = 1; j <= col; j++){printf("---");if (j != col){printf("|");}}}printf("\n");}
}
6. 排雷函数
6.1 排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;char ch = 0;while (win < row * col - EASY_COUNT) {printf("请输入需要排除的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y <= col && y >= 1){if (show[x][y] == '*') //判断该坐标是否已经被排除{if (mine[x][y] == '1') //判断是否为雷{printf("你输了,游戏结束\n");DisplayBoard(mine, ROW, COL);return;}else{//记录排除坐标的个数win += Explode_spread(show , mine , row , col , x ,y);DisplayBoard(show, ROW, COL);}}else{printf("该坐标已被占用\n");}}else{printf("输入的坐标非法\n");}printf("是否需要标记地址,若需要则输入'Y',否则则输入’N'\n");while ((ch = getchar()) != '\n'); //清理缓存区scanf("%c", &ch);switch (ch){case 'Y':SignMine(show, row, col);break;case 'N':break;}}if (win == row * col - EASY_COUNT){printf("恭喜你,游戏胜利\n");}
}
6.2 记录周围雷个数
这里统计雷个数的方法很巧妙。
若用其他的定义雷,把周围的八个坐标全部判断会有点麻烦,但是这里用'1'
定义雷只需要将八个坐标的值相加再与8 * '0'
相减则能直接得到雷的个数。
int MineCount(char mine[ROWS][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';
}
6.3 非雷的连片展开
扫雷游戏中,当排查的坐标周围的八个坐标没有一个雷,show棋盘上该坐标显示为空格,然后排查的的范围将会扩大,将周围的八个坐标挨个向前面一样判断,直到遇到周围有雷的时候停止,并且将周围的雷的个数记录在show棋盘上。
注意事项:
(1)我们设计的这个函数为递归函数,必须有条件来限制,否则会造成死递归。这里我们看到两个重合的位置,若不加限制条件,这几个坐标相互向外判断,造成死循环。
(2)注意控制坐标的合法性,不能让数组越界访问。
int Explode_spread(char show[ROWS][COLS],char mine[ROWS][COLS],int row,int col,int x,int y)
{int i = 0, j = 0;int win = 0;if (x >= 1 && x <= row && y <= col && y >= 1){//防止数组越界if ((MineCount(mine, x, y) + '0') == '0'){//若该坐标周围八个坐标都没有雷,向外扩散show[x][y] =' ';for (i = x - 1; i <= x + 1; i++){for (j = y - 1; j <= y + 1; j++){if (show[i][j] == '*'){//判断是否该坐标已经被判断过Explode_spread(show, mine, row, col, i, j);win++;}}}}else{show[x][y] = MineCount(mine, x, y) + '0';win++;}}return win;
}
6.4 标雷
void SignMine(char show[ROWS][COLS],int row, int col)
{static sign = EASY_COUNT; //标记的次数char option = 'Y';int x = 0;int y = 0;char ch = 0;if (sign) //现在标记次数{while (option == 'Y'){printf("还能标记%d个坐标\n", sign);printf("请输入需要标记的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y <= col && y >= 1){if (show[x][y] == '*'){show[x][y] = '?';sign--;DisplayBoard(show, ROW, COL);}else{printf("该坐标已被占用\n");}}else{printf("输入的坐标非法\n");}printf("是否需要继续标记地址,若需要则输入'Y',否则则输入’N'\n");while ((ch = getchar()) != '\n'); //清理缓存区scanf("%c", &option);}}else{printf("标记次数过多\n");return;}
}
二. 整体程序的完成
1. 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 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);
2. game.c文件 :扫雷的实现模块
#include "game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;int j = 0;for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){board[i][j] = set;}}
}//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 1;int j = 1;printf(" *************** 扫雷 ***************\n");for (j = 0; j <= col; j++){printf(" %d ", j); //记录行号}printf("\n\n");for (i = 1; i <= row; i++){printf(" %d ", i); //记录列号for (j = 1; j <= col; j++){printf(" %c ", board[i][j]);if (j != col){printf("|");}}printf("\n");if (i != row){printf(" ");for (j = 1; j <= col; j++){printf("---");if (j != col){printf("|");}}}printf("\n");}
}//布置地雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;while (count){int x = rand() % row + 1;int y = rand() % col + 1;if (x >= 1 && x <= row && y <= col && y >= 1){if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}}
}//记录一个坐标周围有多少个地雷
int MineCount(char mine[ROWS][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';
}//若一个坐标周围八个坐标都没有雷,连片展开
int Explode_spread(char show[ROWS][COLS],char mine[ROWS][COLS],int row,int col,int x,int y)
{int i = 0, j = 0;int win = 0;if (x >= 1 && x <= row && y <= col && y >= 1){//防止数组越界if ((MineCount(mine, x, y) + '0') == '0'){//若该坐标周围八个坐标都没有雷,向外扩散show[x][y] =' ';for (i = x - 1; i <= x + 1; i++){for (j = y - 1; j <= y + 1; j++){if (show[i][j] == '*'){//判断是否该坐标已经被判断过Explode_spread(show, mine, row, col, i, j);win++;}}}}else{show[x][y] = MineCount(mine, x, y) + '0';win++;}}return win;
}//标记地雷
void SignMine(char show[ROWS][COLS],int row, int col)
{static sign = EASY_COUNT;char option = 'Y';int x = 0;int y = 0;char ch = 0;if (sign){while (option == 'Y'){printf("还能标记%d个坐标\n", sign);printf("请输入需要标记的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y <= col && y >= 1){if (show[x][y] == '*'){show[x][y] = '?';sign--;DisplayBoard(show, ROW, COL);}else{printf("该坐标已被占用\n");}}else{printf("输入的坐标非法\n");}printf("是否需要继续标记地址,若需要则输入'Y',否则则输入’N'\n");while ((ch = getchar()) != '\n'); //清理缓存区scanf("%c", &option);}}else{printf("标记次数过多\n");return;}
}//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;char ch = 0;while (win < row * col - EASY_COUNT) {printf("请输入需要排除的坐标:>");scanf("%d%d", &x, &y);if (x >= 1 && x <= row && y <= col && y >= 1){if (show[x][y] == '*') //判断该坐标是否已经被排除{if (mine[x][y] == '1') //判断是否为雷{printf("你输了,游戏结束\n");DisplayBoard(mine, ROW, COL);return;}else{//记录排除坐标的个数win += Explode_spread(show , mine , row , col , x ,y);DisplayBoard(show, ROW, COL);}}else{printf("该坐标已被占用\n");}}else{printf("输入的坐标非法\n");}printf("是否需要标记地址,若需要则输入'Y',否则则输入’N'\n");while ((ch = getchar()) != '\n'); //清理缓存区scanf("%c", &ch);switch (ch){case 'Y':SignMine(show, row, col);break;case 'N':break;}}if (win == row * col - EASY_COUNT){printf("恭喜你,游戏胜利\n");}
}
3. test.c文件:测试扫雷的基本功能
#include "game.h"void menu()
{printf("**************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("**************************\n");
}void game()
{//定义两个11 x 11 的数组(棋盘)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);
}int main()
{int input = 0;srand((unsigned)time(NULL));menu();do{printf("请选择:>");scanf("%d", &input);switch (input){case 1:game();break;case 0:printf("退出游戏\n");break;default:printf("输入错误\n");break;}} while (input);return 0;
}
结尾
如果有什么建议和疑问,或是有什么错误,希望大家能够提一下。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,希望能给我一个小小的赞!
【C语言】扫雷的模拟实现详解相关推荐
- C语言strcpy、strcnpy函数详解
C语言strcpy.strcnpy函数详解 一.strcpy函数 1.函数原型 2.参数.返回值解析 3.注意事项 4.strcpy函数模拟实现 二.strncpy函数 1.函数原型 2.与strcp ...
- C语言strcat、strncat函数详解
C语言strcat.strncat函数详解 一.strcat函数 1.函数原型 2.函数参数.返回值解析 3.函数作用 4.注意事项 5.strcat函数模拟实现 二.strncat函数 1.函数原型 ...
- R语言可视化绘图基础知识详解
R语言可视化绘图基础知识详解 图形参数:字体.坐标.颜色.标签等: 图像符号和线条: 文本属性: 图像尺寸及边界: 坐标轴.图例自定义等: 图像的组合: #install.packages(c(&qu ...
- php函数find的用法,c语言find函数的用法详解
c语言find函数的用法详解 C语言之find()函数 find函数用于查找数组中的某一个指定元素的位置. 比如:有一个数组[0, 0, 5, 4, 4]: 问:元素5的在什么位置,find函数 返回 ...
- 蓝桥杯 Java B组 省赛决赛模拟赛 详解及小结汇总+题目下载【2013年(第4届)~2021年(第12届)】
蓝桥杯 Java B组 省赛决赛模拟赛 详解及小结汇总+题目下载[2013年(第4届)~2021年(第12届)] 百度网盘-CSDN蓝桥杯资料(真题PDF+其它资料) 提取码:6666 2013年 ...
- java语言链栈_Java语言实现数据结构栈代码详解
近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行 ...
- 大二c语言期末考试题库及详解答案,大学C语言期末考试练习题(带详解答案)...
<大学C语言期末考试练习题(带详解答案)>由会员分享,可在线阅读,更多相关<大学C语言期末考试练习题(带详解答案)(55页珍藏版)>请在金锄头文库上搜索. 1.一. 单项选择题 ...
- c语言线性表库函数大全,数据结构(C语言版)-线性表习题详解
<数据结构(C语言版)-线性表习题详解>由会员分享,可在线阅读,更多相关<数据结构(C语言版)-线性表习题详解(23页珍藏版)>请在人人文库网上搜索. 1.数 据 结 构 ,线 ...
- c语言 read 文件字节没超过数组大小时会怎样_剑指信奥 | C 语言之信奥试题详解(四)...
趣乐博思剑指信奥系列 ❝ 趣乐博思剑指信奥系列,专门针对全国青少年信息学奥林匹克联赛 NOIP 而开展的专业教育方案.开设的课程有 C 语言基础,C++ 语言基础,算法设计入门与进阶,经典试题分析与详 ...
最新文章
- python并发编程之多进程理论部分
- Zuul:智能路由和过滤(译)
- jackson 序列化_jackson序列化与反序列化的应用实践
- maven aspectj_使用Spring AspectJ和Maven进行面向方面的编程
- 系统架构设计师 - 系统可靠性设计
- 2字节取值范围_Java注解-元数据、注解分类、内置注解和自定义注解|乐字节
- 邯郸学院计算机科学与技术录取分,邯郸学院录取分数线2021是多少分(附历年录取分数线)...
- PHP用set_error_handler()拦截程序中的错误
- 苹果android怎么升级,微信系统升级!苹果安卓手机如何升级更新为最新版微信8.0?...
- SpannableString 给TextView添加不同的显示样式
- Atitit.attilax软件研发与项目管理之道
- 《Effective C#》读书笔记——条目14:尽量减少重复的初始化逻辑.NET资源管理
- 凡诺CMS 未授权访问+文件包含Getshell
- PPT文件不能编辑怎么回事?
- js转换中文为拼音以及首字母
- UE4 骨骼动画 蓝图中调节某一根骨骼
- 计算机网络(五)—— 运输层(8):TCP的连接建立和连接释放
- 【设计模式】抽象类与接口
- 调查发现:手机竟然比马桶垫还脏
- tvb与亚视的十部巅峰代表作,论经典程度谁更胜一筹
热门文章
- d盾web查杀 linux,D盾扫描_D盾_Web查杀 [webshell查杀]
- 【云服务器】云服务器实例配置
- 基于SpringBoot打造的OA、CMS、ERP通用后台开发框架
- pp什么意思_【问答】pp测试中的“pp”是什么意思啊? - 邦阅网-外贸知识服务平台...
- ByShell 一个穿越主动防御的木马
- 杂记,生源地招生路线规划问题
- 多实体零件如何转换为装配体
- l4d2显示所有服务器的指令,求生之路2指令大全 求生之路2指令怎么用? 地图指令-游侠网...
- Qt-OpenCV学习笔记--计算周长--arcLength()
- 日落20180705001 - Unity质量设置