前言
扫雷是我们同年的小游戏,今天我们就来实现一下这个童年小游戏。

与上次三子棋一样,我们用三个文件完成这个程序。

目录

  • 一. 设计思路
    • 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语言】扫雷的模拟实现详解相关推荐

  1. C语言strcpy、strcnpy函数详解

    C语言strcpy.strcnpy函数详解 一.strcpy函数 1.函数原型 2.参数.返回值解析 3.注意事项 4.strcpy函数模拟实现 二.strncpy函数 1.函数原型 2.与strcp ...

  2. C语言strcat、strncat函数详解

    C语言strcat.strncat函数详解 一.strcat函数 1.函数原型 2.函数参数.返回值解析 3.函数作用 4.注意事项 5.strcat函数模拟实现 二.strncat函数 1.函数原型 ...

  3. R语言可视化绘图基础知识详解

    R语言可视化绘图基础知识详解 图形参数:字体.坐标.颜色.标签等: 图像符号和线条: 文本属性: 图像尺寸及边界: 坐标轴.图例自定义等: 图像的组合: #install.packages(c(&qu ...

  4. php函数find的用法,c语言find函数的用法详解

    c语言find函数的用法详解 C语言之find()函数 find函数用于查找数组中的某一个指定元素的位置. 比如:有一个数组[0, 0, 5, 4, 4]: 问:元素5的在什么位置,find函数 返回 ...

  5. 蓝桥杯 Java B组 省赛决赛模拟赛 详解及小结汇总+题目下载【2013年(第4届)~2021年(第12届)】

    蓝桥杯 Java B组 省赛决赛模拟赛 详解及小结汇总+题目下载[2013年(第4届)~2021年(第12届)] 百度网盘-CSDN蓝桥杯资料(真题PDF+其它资料)   提取码:6666 2013年 ...

  6. java语言链栈_Java语言实现数据结构栈代码详解

    近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行 ...

  7. 大二c语言期末考试题库及详解答案,大学C语言期末考试练习题(带详解答案)...

    <大学C语言期末考试练习题(带详解答案)>由会员分享,可在线阅读,更多相关<大学C语言期末考试练习题(带详解答案)(55页珍藏版)>请在金锄头文库上搜索. 1.一. 单项选择题 ...

  8. c语言线性表库函数大全,数据结构(C语言版)-线性表习题详解

    <数据结构(C语言版)-线性表习题详解>由会员分享,可在线阅读,更多相关<数据结构(C语言版)-线性表习题详解(23页珍藏版)>请在人人文库网上搜索. 1.数 据 结 构 ,线 ...

  9. c语言 read 文件字节没超过数组大小时会怎样_剑指信奥 | C 语言之信奥试题详解(四)...

    趣乐博思剑指信奥系列 ❝ 趣乐博思剑指信奥系列,专门针对全国青少年信息学奥林匹克联赛 NOIP 而开展的专业教育方案.开设的课程有 C 语言基础,C++ 语言基础,算法设计入门与进阶,经典试题分析与详 ...

最新文章

  1. python并发编程之多进程理论部分
  2. Zuul:智能路由和过滤(译)
  3. jackson 序列化_jackson序列化与反序列化的应用实践
  4. maven aspectj_使用Spring AspectJ和Maven进行面向方面的编程
  5. 系统架构设计师 - 系统可靠性设计
  6. 2字节取值范围_Java注解-元数据、注解分类、内置注解和自定义注解|乐字节
  7. 邯郸学院计算机科学与技术录取分,邯郸学院录取分数线2021是多少分(附历年录取分数线)...
  8. PHP用set_error_handler()拦截程序中的错误
  9. 苹果android怎么升级,微信系统升级!苹果安卓手机如何升级更新为最新版微信8.0?...
  10. SpannableString 给TextView添加不同的显示样式
  11. Atitit.attilax软件研发与项目管理之道
  12. 《Effective C#》读书笔记——条目14:尽量减少重复的初始化逻辑.NET资源管理
  13. 凡诺CMS 未授权访问+文件包含Getshell
  14. PPT文件不能编辑怎么回事?
  15. js转换中文为拼音以及首字母
  16. UE4 骨骼动画 蓝图中调节某一根骨骼
  17. 计算机网络(五)—— 运输层(8):TCP的连接建立和连接释放
  18. 【设计模式】抽象类与接口
  19. 调查发现:手机竟然比马桶垫还脏
  20. tvb与亚视的十部巅峰代表作,论经典程度谁更胜一筹

热门文章

  1. d盾web查杀 linux,D盾扫描_D盾_Web查杀 [webshell查杀]
  2. 【云服务器】云服务器实例配置
  3. 基于SpringBoot打造的OA、CMS、ERP通用后台开发框架
  4. pp什么意思_【问答】pp测试中的“pp”是什么意思啊? - 邦阅网-外贸知识服务平台...
  5. ByShell 一个穿越主动防御的木马
  6. 杂记,生源地招生路线规划问题
  7. 多实体零件如何转换为装配体
  8. l4d2显示所有服务器的指令,求生之路2指令大全 求生之路2指令怎么用? 地图指令-游侠网...
  9. Qt-OpenCV学习笔记--计算周长--arcLength()
  10. 日落20180705001 - Unity质量设置