【C语言】扫雷小游戏(保姆教程)

目录

一、扫雷游戏介绍

二、代码分装

三、代码实现步骤

1. 制作菜单menu函数以及游戏运行逻辑流程

2. 数组棋盘分析

3. 创建棋盘数组

 4. 初始化棋盘InitBoard函数

5. 显示棋盘DisplayBoard函数

6. 布置雷SetMine函数

 7. 统计雷个数GetMineCount函数

 8. 排查雷FindMine函数

四、扫雷完整代码


一、扫雷游戏介绍

《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。

二、代码分装

源文件:

test.c: 整个游戏相关的测试

game.c: 实现游戏的相关函数

头文件:

game.h: 相关函数的声明,整个代码要引用的头文件以及宏定义

三、代码实现步骤

1. 制作菜单menu函数以及游戏运行逻辑流程

  • 首先在 test.c 中定义一个menu函数打印菜单,提示玩家1开始游戏,0退出游戏。
  • scanf接收玩家输入并用switch判断,1即开始游戏,0则退出游戏,非1非0则提示选择错误,重新选择。而为了能够让用户重新选择以及结束一局游戏后能够再来一盘,需要使用do while语句将它们包含起来。
void menu()
{printf("*********************\n");printf("*****  1. game  *****\n");printf("*****  0. exit  *****\n");printf("*********************\n");
}void game()
{printf("扫雷\n");
}int main()
{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);return 0;
}

2. 数组棋盘分析

假设需要的棋盘大小为9*9,不是雷用0表示,是雷用1表示。

为了方便操作,这里定义两个棋盘数组,mine和show,mine棋盘用于存放雷的信息,初始化为0,而show棋盘用来打印给玩家看,初始化为*。同时两个棋盘内存放的类型统一设置为char类型即字符数组,方便操作。

  • 假设雷已经布置进去mine棋盘了,然后假设排查坐标(7,6)的区域时,如果不是雷,系统则需要返回坐标(7,6)周围8个区域的雷数(即黄色的8个区域),此时需要在坐标(7,6)处返回1,而如果返回雷的个数1放在mine棋盘的(7,6)中容易与表示雷的1产生歧义,因此这里的做法是将雷的个数1存放在show棋盘中。
  • 这里还有一个问题,如果排查例如坐标(4,9)的区域时,出现了一部分区域超出了数组的情况(即红色的区域)。为了防止数组越界,此时在每次排查时都需要对周围每个区域进行判断是否超出数组范围,这样操作起来即麻烦,效率又低。

而我这里的做法是将两个棋盘都扩充一圈(即蓝色区域),此时数组大小便是11*11,这样的好处是既不用担心数组会越界,又可以使坐标系的位置规范,坐标可以完全匹配起来,使得代码的书写更加的方便。

3. 创建棋盘数组

为了方便修改,在game.h中定义ROW=9,COL=9,ROWS=ROW+2,COLS=COL+2;

这里ROW和COL设置为9而ROWS和COLS设置为11是因为,虽然实际的使用范围是11*11,但是布雷区域和给玩家展示的区域仍然是9*9,因此需要两个符号分别表示11*11、9*9。

#define ROW 9
#define COL 9#define ROWS ROW+2 //11
#define COLS COL+2 //11
void game()
{char mine[ROWS][COLS] = { 0 };//放置雷的数组char show[ROWS][COLS] = { 0 };//排查雷信息的数组
}

 4. 初始化棋盘InitBoard函数

因为初始化的内容不定,一个是0,一个是*,因此需要添加多一个参数用于表示初始化的内容。

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;int j = 0;for ( i = 0; i < rows; i++){for ( j = 0; j < cols; j++){board[i][j] = set;}}
}

 test.c

void game()
{char mine[ROWS][COLS] = { 0 };//放置雷的数组char show[ROWS][COLS] = { 0 };//排查雷信息的数组InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');}

 接下来我们做一个显示棋盘函数观察初始化是否成功。

5. 显示棋盘DisplayBoard函数

game.h 

//显示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

 game.c

//显示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;printf("--------扫雷--------\n");for ( i = 0; i <= col; i++) //打印列号 注意这里的i是从0开始{printf("%d ", i);}printf("\n");for ( i = 1; i <= row; i++){printf("%d ", i); //打印行号for ( j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}printf("--------扫雷--------\n");}

test.c 

真正需要打印的区域是中间的9*9区域,因此DisplayBoard函数传参是ROW,COL。 

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);DisplayBoard(mine, ROW, COL);
}

此时调用一下显示棋盘函数观察一下初始化是否完成。 

可以发现初始化完成,一个为全*一个为全0。

6. 布置雷SetMine函数

    将雷布置到棋盘mine数组中。

    这里布置雷的策列是让雷随机排布,这就需要使用到随机数,而随机数的获取又涉及到srand函数、rand函数以及time函数,关于srand函数、rand函数和time函数之间的知识点,我在往期的博客中有进行讲解,感兴趣的可以前往:【C语言】猜数字小游戏——深度刨析rand函数生成随机数_Hacynn的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/132635456?spm=1001.2014.3001.5502查看,在这里就不过多赘述。

    使用srand和rand需要包含 stdlib.h 头文件,使用time需要包含 time.h 头文件。并且在main函数中调用srand生成随机种子,这样rand函数便可以生成随机数。

 game.h

#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define EASY_COUNT 10//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);

 game.c

使用ESAY_COUNT来表示雷数,用while循环遍历布置,并且每当放入1个雷时count就减1,直到为0时退出循环。

//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;while (count){int x = rand() % row + 1; //0~8+1 = 1~9int y = rand() % col + 1; //0~8+1 = 1~9if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}
}

test.c 

真正需要布置雷的区域是中间的9*9区域,因此SetMine函数传参依然是ROW,COL。 

布置完雷之后使用DisplayBoard函数打印mine棋盘,布置雷部分便完成。

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);
}

 7. 统计雷个数GetMineCount函数

假设排查(x,y)坐标,如果不是雷,则需要统计(x,y)周围8个区域的雷数。接下来我将用一张图来解释如何返回。

上图的x,y就是排查的坐标,那么(1,1)区域就是(x-1,y-1),(1,2)区域就是(x-1,y),其他同理。所以只需要将8个区域的值相加,则可以得出雷的个数。

game.c 

//统计x,y坐标周围有几个雷
int GetMineCount(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';
}

但是需要注意的是,因为mine数组中存放的是字符类型,所以返回的值不能直接使用,需要减去一个字符‘0’才能等于对应的数字(例如下图),所以一共要减去8个'0',从而实现从char类型到int类型的转变。

 8. 排查雷FindMine函数

排查雷涉及到两个数组。在棋盘mine数组中排查雷的个数,并在棋盘show数组中展示个数,因此FindMine函数传参时需要传入两个数组。

 game.h

//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

 game.c

首先使用while循环嵌套排查操作部分,当排查到雷时,则提示被炸死了并打印棋盘mine。

当排查到不是雷时,则需要返回周围8个区域的雷个数,此时需要调用GetMineCount函数,然后打印棋盘show显示雷数,此时的数为int类型,在传入show数组前需要将它与‘0’相加从而得到对应的char类型。

//排查雷
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 < row * col - 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 c = GetMineCount(mine, x, y);show[x][y] = c + '0'; //int转charDisplayBoard(show, ROW, COL);win++;}}else{printf("坐标非法,重新输入!\n");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功!\n");DisplayBoard(mine, ROW, COL);}
}

test.c

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);//排查雷FindMine(mine, show, ROW, COL);
}

四、扫雷完整代码

 game.h

#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

#define _CRT_SECURE_NO_WARNINGS 1#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 = 0;int j = 0;printf("--------扫雷--------\n");for ( i = 0; i <= col; i++) //打印列号 注意这里的i是从0开始{printf("%d ", i);}printf("\n");for ( i = 1; i <= row; i++){printf("%d ", i); //打印行号for ( j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}printf("--------扫雷--------\n");}//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{int count = EASY_COUNT;while (count){int x = rand() % row + 1; //0~8+1 = 1~9int y = rand() % col + 1; //0~8+1 = 1~9if (mine[x][y] == '0'){mine[x][y] = '1';count--;}}
}//统计x,y坐标周围有几个雷
int GetMineCount(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';
}//排查雷
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 < row * col - 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 c = GetMineCount(mine, x, y);show[x][y] = c + '0'; //int转charDisplayBoard(show, ROW, COL);win++;}}else{printf("坐标非法,重新输入!\n");}}if (win == row * col - EASY_COUNT){printf("恭喜你,排雷成功!\n");DisplayBoard(mine, ROW, COL);}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1#include "game.h"void menu()
{printf("*********************\n");printf("*****  1. game  *****\n");printf("*****  0. exit  *****\n");printf("*********************\n");
}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);//排查雷FindMine(mine, show, ROW, COL);
}int main()
{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);return 0;
}

此次扫雷小游戏的内容就到此为止了,相信大家也能够做出属于自己的三子棋小游戏,这个游戏最重要的是代码思维而不是代码本身,理解代码思维能够得到更大的提升。

如果觉得作者写的不错,求给作者一个大大的点赞支持一下,你们的支持是我更新的最大动力!

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

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

相关文章

算法分析与设计编程题 动态规划

矩阵连乘 题目描述 解题代码 void printOptimalParens(vector<vector<int>>& partition, int i, int j) {if (i j) cout << "A" << i; // 单个矩阵&#xff0c;无需划分else {cout << "(";printOptimalParens(partit…

网络安全中的欺骗攻击与防御技术

在Internet上计算机之间相互进行的交流建立在两个前提之下&#xff1a;认证、信任。 认证是网络上的计算机用于相互间进行识别的一种鉴别过程&#xff0c;经过认证的过程&#xff0c;获准相互交流的计算机之间就会建立起相互信任的关系。信任和认证具有逆反关系&#xff0c;即…

爬虫逆向实战(33)-某联社数据(webpack)

一、数据接口分析 主页地址&#xff1a;某联社 1、抓包 通过抓包可以发现数据接口是/nodeapi/telegraphList 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个sign加密参数 请求头是否加密&#xff1f; 无 响应是否加密&#x…

性能监控-grafana+prometheus+node_exporter

Prometheus是一个开源的系统监控和报警工具。它由SoundCloud开发并于2012年发布&#xff0c;后来成为了一个独立的开源项目&#xff0c;并得到了广泛的应用和支持。 Prometheus的主要功能包括采集和存储各种系统和应用程序的监控数据&#xff0c;并提供强大的查询语言PromQL来…

【云原生】kubectl常用命令大全

目录 一、资源管理方法 kubectl 的命令大全 二、 kubectl常用命令大全 2.2 项目的生命周期&#xff1a;创建-->发布-->更新-->回滚-->删除 1、创建 kubectl create命令 2、发布 kubectl expose命令 3、更新 kubectl set 4、回滚 kubectl rollou…

线性代数的本质(九)——二次型与合同

文章目录 二次型与合同二次型与标准型二次型的分类度量矩阵与合同 二次型与合同 二次型与标准型 Grant&#xff1a;二次型研究的是二次曲面在不同基下的坐标变换 由解析几何的知识&#xff0c;我们了解到二次函数的一次项和常数项只是对函数图像进行平移&#xff0c;并不会改变…

Qt 围炉札记

文章目录 一、Qt 调试二、vscode 与 Qt1、安装插件&#xff1a;2、设置中配置插件 一、Qt 调试 【Qt调试技巧】Profile配置使用及一些坑 QT运行时的Debug、Release、Profile选项区别 Qt Creator release版本进行调试 【Qt调试技巧】如何在Release下调试Qt程序&#xff1f; …

STM32 CAN使用记录:bxCAN基础通讯

文章目录 目的关键配置与代码轮询方式中断方式收发测试 示例链接总结 目的 CAN是非常常用的一种数据总线&#xff0c;被广泛用在各种车辆系统中。这篇文章将对STM32中CAN的使用做个示例。 CAN的一些基础介绍可以参考下面文章&#xff1a; 《CAN基础概念》https://blog.csdn.n…

node 之 express 框架(初级)

一、express 热更新 1、安装扩展 npm install node-dev -D2、在根目录下的 package.json 文件中进行配置 3、之后的启动执行下面的命令即可 npm run dev二、mvc中的 模板引擎 1、ejs模板引擎的安装 npm install ejs -s2、在根目录下的app.js文件中配置 app.set(view engin…

Python中使用item()方法遍历字典的例子

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 这篇文章主要介绍了Python中使用item()方法遍历字典的例子, for…in这种是Python中最常用的遍历字典的方法了,需要的朋友可以参考下 Python字典的遍历方法有好几种&#xff0c;其中一种是for…in&#xff0c;这个我就…

vue学习之条件渲染

条件渲染 用于控制组件显示创建 demo6.html,内容如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title&…

Minio入门系列【1】Windows/Linux/K8S单机部署Minio

1 Minio简介 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象文件可以是任意大小&…