c语言期中项目实战二—简易扫雷,思路分析+代码详细注释

  • 游戏介绍
  • 项目步骤
    • 模块化编程
    • 设置菜单
    • 设置棋盘
    • 打印棋盘
    • 布置雷
    • 排查雷
  • 总结及总代码和详细注释

游戏介绍

扫雷这个经典游戏,直到现在仍有很多人在玩,可以说它是全世界最多人玩过的游戏之一,对很多人来说甚至是他们在电脑上接触的第一款游戏。记得我上小学的时候也经常玩,今天我就写这篇文章简单地介绍一下写这个游戏的思路和分享一下代码,本篇文章讲解的是一个16*16(中级)扫雷,有40个雷,这篇文章只是实现了简单的排雷版本,1表示雷,0表示不是雷,你排一个位置的雷,有雷会被炸死,没雷显示周围雷的情况,不会一片的展开没雷的位置,你也可以自己设计一个几行几列的雷区,自定义雷的个数,只需要更改#define定义的宏常量即可。(这里只是实现后端的算法,其他的没有,前段界面的东西没有涉及)

项目步骤

模块化编程

多文件(模块化)开发C程序的方法,就是把不同功能的函数封装到不同的文件中,多个.c文件和一个.h文件,c语言中主函数调用其他文件中的函数实现相应的功能。此次项目也将程序分为三个模块,各模块功能如下
test.c :扫雷游戏的测试
game.c:扫雷游戏的函数实现
game.h:游戏函数的声明

设置菜单

任何一个游戏你玩不玩之前都有一个菜单,你是要玩(play),还是不玩想退出(exit),使用do{ }while()循环,0为假,非0为真,即达到玩不玩都打印一次菜单,也达到了要么你输入0退出,要么输入1,输入其他的数字是错误的目的。

void menu()
{printf("******************************\n");printf("******    扫雷游戏     *******\n");printf("******    1. play     *******\n");printf("******    0. exit     *******\n");printf("*******  请选择数字    *******\n");
}int main()
{int input = 0;srand((unsigned int)time(NULL));//使用rand函数之前调用srand函数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;
}

设置棋盘

其实设置雷区这里也简单的,只要理解几个关键步骤就行:

  1. 存放雷的棋盘比真实所放雷的格子行和列都多二

因为之后我们设置了雷,在排雷的时候,要检查我们所排位置周围的八个格子有几个雷,返回数字给玩家,让他们赢得游戏,但如果设置雷区没有比真正放雷的区域大的话,在排雷的时候,如果排到雷区边缘的话,就会产生数组越界的问题。下面以图形的方式大致了讲解一下。(这里把雷区大致比作棋盘,其实他们都有许多小格子的)

  1. 创建两个数组
    我们首先写这个代码时,用字符1表示雷,字符0表示非雷,我们首先想到的是创建一个二维数组,18行18列,然后放雷,之后放雷扫雷,遇到非雷的时候返回这个位置周围的雷的个数,如果是一个二维数组,假设我们扫雷了,那个无雷的位置返回了一个‘1’,本来是表示那个位置有一个雷的,但我们也会歧义的认为那是一个雷,所以不能只定义,使用一个数组。
    那有的老铁可能会说了,那我不使用字符1和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;//实参set为*或者‘0’,传过来放在数组的每一个元素里面,之后就初始化了成‘*’或者‘0’了}}
}

排雷的时候雷在数组边沿会产生数组越界问题如下图:

打印棋盘

定义和初始化了了棋盘,我们把棋盘打印出来,在打印的时候我们顺便打印行号和列号方便玩家排对应坐标的雷,行号和列号都是1—16

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;printf("---------扫雷游戏-----------\n");//为了好打印给的区分//打印列号for (i = 0; i <= col; i++){printf("%2d  ", i);//因为行和列有两位数字所以设置为%2d,并且右对齐,使整个雷区棋盘整齐}printf("\n");for (i = 1; i <= row; i++){printf("%2d  ", i);for (j = 1; j <= col; j++){printf("%2c  ", board[i][j]);//字符也占两位}printf("\n");}printf("---------扫雷游戏-----------\n");
}

布置雷

这里我们利用随即函数rand随机的产生40个数字,再利用取余产生有效的坐标,并在坐标处设置雷。

void SetMine(char mine[ROWS][COLS], int row, int col)//18*18的数组里随机得放上40个雷,但真实的是把雷放在里面16*16的数组里,//所以要传过mine数组,形式上传过ROWS,COLS,但正真操作的是16*16的格子放置的雷,所以形参为row,col和要传递的实参对应也为row,col
{//布置10个雷int count = EASY_COUNT;//#define定义的宏常量,玩一个中级版本,设置40颗雷while (count)//count等于0的时候就结束循环{//生产随机的下标int x = rand() % row + 1;//产生1-row+1的随机数字int y = rand() % col + 1;if (mine[x][y] == '0')//如果是字符‘0’没放上雷,我们就放上雷{mine[x][y] = '1';count--;}}
}

排查雷

在排查雷这里我们应用到一点c语言不同数据类型之间运算的一点小知识,就是int 型数据和char型数据相加,相减,以及字符和字符类型数据相加相减,这该怎么运算呢?经过我查阅资料阅读其他的文章明白了:
Char型与int型数据进行运算,就是把字符的ASCII码与整型数据进行运算,如果返回结果为整形就返回ASCLL码,返回结果为字符就返回ASCLL码对应的字符;字符与字符相运算也是字符对应的ASCLL相运算,看运算结果返回对应的值。
数字1+‘0’返回的如是字符‘1’,1加‘0’的ASCLL码,1+48=49,ASCLL,49对应的字符为‘1’,以此类推,一个数字加上‘0’为这个数字对应的字符
下面附上部分ASCLL码表:

下面是不同数据类型间混合运算的一篇博客,可以看看:https://blog.csdn.net/Fengjingdisan/article/details/76358642?utm_source=app&app_version=4.5.2

static int get_mine_count(char mine[ROWS][COLS], int x, int y)//get_mine_count 仅仅只为了支持Findmine函数而已,加了static让这个函数只能在自己所在的源文件使用,
{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';//这里的函数返回值是一个数字(ASCLL码值),之后在下面的FindMine函数里转换为字符数字,打印雷的个数
}void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//1. 输入排查的坐标//2. 检查坐标处是不是雷// (1) 是雷   - 很遗憾炸死了 - 游戏结束// (2) 不是雷  - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续int x = 0;int y = 0;int win = 0;while (win < row*col - EASY_COUNT)//所有的雷区减掉雷的个数是无雷区,当还有无雷区未排除时继续游戏{printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);//x--(1,16)  y--(1,16)//判断坐标的合法性if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("很遗憾,你被炸死了\n");DisplayBoard(mine, row, col);//打印一下你排的位置是雷,你是怎么死的break;//跳出循环,继续游戏}else{//不是雷情况下,统计x,y坐标周围有几个雷int count = get_mine_count(mine, x, y);show[x][y] = count + '0';//显示排查出的信息DisplayBoard(show, row, col);//显示该格子周围有几个雷win++;}}else{printf("坐标不合法,请重新输入\n");}}if (win == row * col - EASY_COUNT)//判断所有空白雷区排完,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, row, col);}
}

总结及总代码和详细注释

这篇文章只是实现了简单的排雷版本(16*16),你排一个位置的雷,有雷会被炸死,没雷显示周围雷的情况,不会一片的展开没雷的位置,之后我还会续上一个应用递归的展开一片的排雷的版本 的。
文章是我学习后的总结和分享,如有错误评论区留言指正,看了的xdm觉得还行的话,希望点赞鼓励,之后有时间我就会持续输出的。

递归版扫雷连接

下面是总的完整代码。
game.h

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define EASY_COUNT 40#define ROW 16
#define COL 16#define ROWS ROW+2
#define COLS COL+2//函数的声明
//初始化棋盘的
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//三个形参//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//(形参)row和col和实参呼应,虽然你打印中间的16行16列,但这个数组传过去任然是18行18列的数组//布置雷
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

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"void menu()
{printf("******************************\n");printf("******    扫雷游戏     *******\n");printf("******    1. play      *******\n");printf("******    0. exit      *******\n");printf("*******  请选择数字    *******\n");
}void game()
{char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息//初始化棋盘InitBoard(mine, ROWS, COLS, '0');//'0','0'函数传参,首先传给set ,你想初始化什么你给我传什么InitBoard(show, ROWS, COLS, '*');//'*'//打印一下棋盘DisplayBoard(show, ROW, COL);//ROW,COL是实际参数//布置雷SetMine(mine, ROW, COL);//把雷放在18*18的数组里,所以传递mine 数组,但我们实际操作的是中间16*16的格子,所以传递ROW,COL,操作中间的格子//DisplayBoard(mine, ROW, COL);//注意这里不能让别人看见我们设置的雷,屏蔽调//排查雷FindMine(mine, show, ROW, COL);//
}int main()
{int input = 0;srand((unsigned int)time(NULL));//传time函数的返回值,即时间戳。返回值强制类型转换为intdo{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.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//包含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;//set实参传递*或者‘1’,set放在数组的每一个元素里面,之后就初始化了成‘*’或者‘0’了}}
}void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;printf("---------扫雷游戏-----------\n");//为了好打印给的区分//打印列号for (i = 0; i <= col; i++){printf("%2d  ", i);}printf("\n");for (i = 1; i <= row; i++){printf("%2d  ", i);for (j = 1; j <= col; j++){printf("%2c  ", board[i][j]);}printf("\n");}printf("---------扫雷游戏-----------\n");
}void SetMine(char mine[ROWS][COLS], int row, int col)//18*18的数组里随机得放上40个雷,但真实的是把雷放在里面16*16的数组里,//传过mine数组,形式上传过ROWS,COLS,但正真操作的是16*16的格子放置雷,所以形参为row,col和要传递的实参对应也为row,col
{//布置10个雷int count = EASY_COUNT;//#define定义的宏常量,玩一个中级版本,设置40颗雷while (count)//count等于0的时候就结束循环{//生产随机的下标int x = rand() % row + 1;//产生1-row+1的随机数字int y = rand() % col + 1;if (mine[x][y] == '0')//如果是字符‘0’没放上雷,我们就放上雷{mine[x][y] = '1';count--;}}
}//static的三种用法
//1. 修饰局部变量
//2. 修饰全局变量
//3. 修饰函数static int get_mine_count(char mine[ROWS][COLS], int x, int y)//get_mine_count 仅仅只为了支持Findmine函数而已,加了static让这个函数只能在自己所在的源文件使用,
{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 FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//1. 输入排查的坐标//2. 检查坐标处是不是雷// (1) 是雷   - 很遗憾炸死了 - 游戏结束// (2) 不是雷  - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续int x = 0;int y = 0;int win = 0;while (win < row*col - EASY_COUNT)//所有的雷区减掉雷的个数是无雷区,当还有无雷区未排除时继续游戏{printf("请输入要排查的坐标:>");scanf("%d%d", &x, &y);//x--(1,16)  y--(1,16)//判断坐标的合法性if (x >= 1 && x <= row && y >= 1 && y <= col){if (mine[x][y] == '1'){printf("很遗憾,你被炸死了\n");DisplayBoard(mine, row, col);//打印一下你排的位置是雷,你是怎么死的break;//跳出循环,继续游戏}else{//不是雷情况下,统计x,y坐标周围有几个雷int count = get_mine_count(mine, x, y);show[x][y] = count + '0';//显示排查出的信息DisplayBoard(show, row, col);//显示该格子周围有几个雷win++;}}else{printf("坐标不合法,请重新输入\n");}}if (win == row * col - EASY_COUNT)//判断但所有空白雷区排完,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, row, col);}
}

c语言期中项目实战二—简易扫雷,思路分析加代码详细注释相关推荐

  1. 【Python】python初学者应该知道与其他语言差异化的高效编程技巧(附测试代码+详细注释)

    目录 1. 交换变量 2. 集合去重 3. 列表推导.集合推导和字典推导 4. 统计字符串中各个字符出现的次数 5.优雅地打印JSON数据 6.行内的if语句 6. 符合正常逻辑的数值比较 7. 田忌 ...

  2. 【C 语言之项目实战】判断闰年及计算天数(详细版)

    目录 1.项目要求 2.定义模块函数 3.各模块函数实现 4.项目源代码 5.项目总结 1. 项目要求 1.1 首先判断用户输入的年份是否为闰年: 1.2 计算一年中每个月份的天数: 1.3 用户输入 ...

  3. Taro多端开发实现原理与项目实战(二)

    Taro多端开发实现原理与项目实战(二) 多端电商平台项目概述及开发准备 学习了前面的基础知识和进阶后是否跃跃欲试?我们准备了一个电商平台的项目来和大家一起实践使用 Taro 开发电商平台. 项目概述 ...

  4. flutter 项目实战二 网络请求

    本项目借用 逛丢 网站的部分数据,仅作为 flutter 开发学习之用. 逛丢官方网址:https://guangdiu.com/ flutter windows开发环境设置 flutter 项目实战 ...

  5. Spark项目实战—电商用户行为分析

    文章目录 一.[SparkCore篇]项目实战-电商用户行为分析 前言:数据准备 1.数据规则如下: 2.详细字段说明: 3.样例类 (一)需求1:TOP10热门品类 1.需求说明 2.代码实现方案1 ...

  6. 项目实战:简易俄罗斯方块(附源码)

    前言 学了java,一直想找一个项目实战,俄罗斯方块就是一个不错的实战项目,它原理实现比较简单.话虽如此,我一开始还是毫无头绪,直到去油管上看一个俄罗斯方块的视频和在GitHub上看他实现的源码,才有 ...

  7. 基于React全家桶开发「网易云音乐PC」项目实战(二)

    前言 本篇开始做 「网易云音乐PC」项目,建议最好有以下基础react.redux.redux-thunk.react-router,上一章只是对项目进行初步介绍认识,本章节会带你完成:网易云的基本骨 ...

  8. Spark项目实战:大数据实时流处理日志(非常详细)

    实战概览 一.实战内容 二.大数据实时流处理分析系统简介 1.需求 2.背景及架构 三.实战所用到的架构和涉及的知识 1.后端架构 2.前端框架 四.项目实战 1.后端开发实战 1.构建项目 2.引入 ...

  9. Vue.js实训【基础理论(5天)+项目实战(5天)】博客汇总表【详细笔记】

    目   录 前言 基础理论(5天) 基础理论-Day01 基础理论-Day02 基础理论-Day03 基础理论-Day04 基础理论-Day05 项目实战 项目实战-Day01 项目实战-Day02 ...

最新文章

  1. Asynctask源码分析
  2. docker linux界面版,centos 7 Docker使用Portainer搭建可视化界面
  3. 《为自己工作——世界顶级设计师成功法则》—第1章1.7节平衡
  4. 企业应用架构模式 读书笔记
  5. python find函数 和index的区别_python中index()与find()的区别
  6. [转]一位研究生的职业生涯规划和心得体会
  7. 小白自学深度学习——目录
  8. JAVA输出希腊union_Java Geometry.union方法代码示例
  9. mysql java safe model_被 MySQL sql_mode 深深伤害( 中 )
  10. 谈一谈Oracle11gR2的审计管理
  11. 系统架构设计师-软件开发模型(螺旋模型)
  12. Mlp-Mixer 阅读笔记
  13. python的30个编程技巧
  14. 6个小故事:让你变身营销超人!
  15. 黑盒测试和白盒测试的基本原理/区别是什么?
  16. 纪录片:互联网之子 亚伦·斯沃茨的故事
  17. php5.6安装zendopcache加速
  18. 毫米和像素怎么换算_将mm换算为px (毫米换算为像素)
  19. linux 类似winscp_linux 类似 winscp
  20. 揭秘!中国人一定要知道的北斗卫星系统

热门文章

  1. %JAVA_HOME% bin,添加 Path 变量,变量值 :%JAVA_HOME%\\bin;%JAVA_HOME%\\ jre
  2. graphx中的pregel原理详解
  3. C++ web框架drogon 使用对象关系映射ORM(Object Relational Mapping)模式
  4. 小米手机被指不务正业:基础功能弱 刷机成主业
  5. linux搭建rtmp服务器搭建,linux下利用Nginx搭建RTMP服务器
  6. MATLAB中MOD函数
  7. 大饼、DeFI、元宇宙将改变整个互联网
  8. 天创速盈:拼多多怎么样开车?拼多多开车四大秘诀
  9. 如何通过三视图判断立方体个数_由三视图怎样确定小立方体的个数
  10. 吐槽 依赖倒置原则/DIP