初阶扫雷(超详解)

图片来源于网络

✨博客主页:小钱编程成长记
🎈博客专栏:C语言小游戏
🎈推荐相关博文:初阶三子棋(超详解)

初阶扫雷

  • 1.游戏介绍
  • 2.基本思路
  • 3.实现前的准备
  • 4.实现步骤
    • 4.1 打印菜单
    • 4.2 初始化扫雷棋盘
    • 4.3 打印扫雷棋盘
    • 4.4 布置雷
    • 4.5 扫雷
    • 4.6 优化棋盘显示
  • 5.游戏代码
  • 6.总结

1.游戏介绍

《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。我们今天实现的是最基础的9*9的扫雷。> 扫雷网页版链接
在这里插入图片描述

2.基本思路

  1. 先实现并打印菜单。
  2. 初始化扫雷棋盘(两个,一个用来放置雷和排雷,另一个用来显示雷的数量)。
  3. 打印扫雷棋盘(两个,一个用来放置雷和排雷,另一个用来显示雷的数量)。
  4. 布置雷(1表示雷,0表示不是雷)。
  5. 扫雷并判断输赢:
    判断输入的坐标是否是雷:
    若是,则排雷失败,游戏结束;
    若不是,则统计周围8个坐标有几个雷并显示在当前坐标上,游戏继续;
    若不是雷的坐标都被排查完,则排雷成功,游戏结束。

3.实现前的准备

在本工程中,代码较多,并且有很多自定义函数。我们一般将代码进行拆分,主程序放在test.c源文件中,函数定义放在game.c源文件中,函数声明或宏等放到game.h头文件中。

将代码拆分的好处:

  1. 多人协作
  2. 代码保护

4.实现步骤

4.1 打印菜单

打印菜单和三子棋一样。如果我们想要多次游玩,则菜单要放进循环里,在菜单里选择开始游戏或者退出游戏。菜单中的选择我们通常用switch语句,菜单的循环我们通常用do … while循环。

//test.c
#include <stdio.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:printf("开始游戏\n");game();break;case 0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}

在这里插入图片描述

4.2 初始化扫雷棋盘

主程序框架写好了,我们现在开始写游戏具体程序。因为棋盘有很多行和列,我们想到二维数组可以表现多行多列。

注意: 为了可以方便修改棋盘的大小,我们可以用#define定义行和列,并将他们放到game.h头文件,只要在主程序中声明一下gane.h就可以使用头文件中的所有内容。

我们用1表示雷,用0表示不是雷的格。如果数组只设置9行9列,那边上和角上的格子在计算周围8个格子有几个雷时就会越界。在这里插入图片描述
为了防止越界,我们要在周围再加一圈变成11*11
在这里插入图片描述
因为在创建二维数组时全都默认为0,所以这里最外面一圈在初始化时放不放0都行,但一定不能放 1(雷),这里并不属于真正的棋盘,只是为了防越界。

如果我们在这个棋盘中的某一个格子里显示周围8个格中有几个雷。若周围只有一个雷,那这个格子上显示的1就会和是雷的那些格子上的1相混淆,就分不清哪个是雷,哪个是统计的周围的雷的数量。
在这里插入图片描述

  • 因此,我们要再初始化一个棋盘,专门用来存放一个格子周围8个格子中的雷的数量,用来展示给玩家看(那就用9 * 9就行了),先把它全都初始化为 *,我们把这个棋盘称为展示棋盘(show)
  • 存放雷的棋盘是给我们程序员看的,用来让程序排雷的,先把它全初始化为0,我们称它为排雷棋盘(mine)

为了方便和排雷棋盘共用一个初始化函数,将展示棋盘初始化11 * 11也可以,只要打印时打印的是9 * 9就行。
在这里插入图片描述

//game.c
#include <stdio.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
//初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//game.c
#include "game.h"
//初始化扫雷棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = set;}}
}//test.c
#include "game.h"
//游戏
void game()
{char mine[ROWS][COLS] ;//定义char show[ROWS][COLS] ;//初始化排雷棋盘InitBoard(mine, ROWS, COLS, '0');//打印排雷棋盘//初始化展示棋盘InitBoard(show, ROWS, COLS, '*');//打印展示棋盘}

4.3 打印扫雷棋盘

那么多的行和列,玩家在玩时不容易看坐标是哪行哪列,所以我们可以把行和列对应的数字也打印出来。打印时也可以自由发挥一下给棋盘加一些装饰。

//game.h
//打印
void DisplayBoard(char board[ROWS][COLS], int row, int col);//game.c
#include "game.h"
//打印扫雷棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 0;int j = 0;printf("******* 扫雷 ******\n");for (j = 0; j <= col; j++){printf("%d ", j);}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
#include "game.h"
//游戏
void game()
{char mine[ROWS][COLS] ;//定义char show[ROWS][COLS] ;//初始化排雷棋盘InitBoard(mine, ROWS, COLS, '0');//打印排雷棋盘DisplayBoard(mine, ROW, COL);//初始化展示棋盘InitBoard(show, ROWS, COLS, '*');//打印展示棋盘DisplayBoard(show, ROW, COL);
}

在这里插入图片描述

4.4 布置雷

我们制作的是9 * 9版本的扫雷,所以我们希望能在排雷棋盘中随机生成10个雷,就是找到10个随机的坐标,那么就需要产生随机数。用rand可以产生随机数。
让电脑下棋需要先让电脑产生随机的坐标,那我们需要用rand产生随机数。
注意: 只用rand产生的是伪随机数,要想让rand产生真随机数,就需要先用srand为rand产生随机的种子,给srand()的()中输入的是随机数,srand产生的就是随机的种子。时间戳(需要头文件time.h)是一个随着时间的变化而变化的值,给srand()中输入时间戳->srand( (unsigne int)time(NULL) ),得到的就是随机的种子。
rand和srand都需要头文件stdlib.h

//game.h
#include <time.h>
#include <stdlib.h>
//雷的总数
#define EASY_COUNT 10
//布置雷
void SetMine(char board[ROWS][COLS], int rows, int cols);//game.c
#include "game.h"
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{int c = EASY_COUNT;while (c){int a = rand() % row + 1;int b = rand() % col + 1;board[a][b] = '1';c--;}
}//test.c
#include "game.h"
//产生随机的种子,用于rand产生真随机数
srand((unsigned int)time(NULL));
//布置雷
SetMine(mine, ROW, COL);
//打印排雷棋盘
DisplayBoard(mine, ROW, COL);

在这里插入图片描述

4.5 扫雷

  • 玩家输入的坐标一定要在我们设置的二维数组的范围内,如果不在则重新输入;

  • 玩家输入的坐标一定要是没有排查过的,若是排查过的,则需重新输入;

  • 如果输入的坐标在排雷棋盘中是1,则排雷失败,本局游戏结束;

  • 如果排查雷的次数正好等于不是雷的格子的数量,则排雷成功,本局游戏结束;

  • 如果输入一个坐标不是雷(游戏继续,要再打印一次展示棋盘为下一次排雷做准备),我们要计算这个坐标周围8个格中的雷的数量,
    -> 1. 只需要将周围8个格中的数据加在一起就行。因为雷为1,不是雷为0,这也是我们 之前这样初始化的好处。注意: 字符本身不参与计算,参与计算的是字符对应的ASCII码值,如:‘1’-‘0’=1(整型) 3+‘0’=‘3’
    -> 2. 还有一种通用方法,就是一个个的判断周围坐标是不是雷,如果是则用计数器(count)加1。周围坐标如下:
    在这里插入图片描述

//game.h
//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int rows, int cols);//game.c
#include "game.h"
//通用方法:计算坐标周围8个格中雷的数量
//static int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
//	int count = 0;
//	if (mine[x - 1][y-1] == '1')
//		count++;
//	if (mine[x - 1][y] == '1')
//		count++;
//	if (mine[x - 1][y + 1] == '1')
//		count++;
//	if (mine[x][y - 1] == '1')
//		count++;
//	if (mine[x][y + 1] == '1')
//		count++;
//	if (mine[x + 1][y - 1] == '1')
//		count++;
//	if (mine[x + 1][y] == '1')
//		count++;
//	if (mine[x + 1][y + 1] == '1')
//		count++;
//	return count;
//}//计算坐标周围8个格中雷的数量
static 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");break;}else if (show[x][y] != '*'){printf("该坐标已被排查了,请重新输入\n");}else{   //不是雷,就统计x,y有多少雷?int c = GetMineCount(mine, x, y);show[x][y] = c + '0';DisplayBoard(show, ROW, COL);win++;}}elseprintf("坐标非法,请重新输入\n");}if (win == row * col - EASY_COUNT){printf("排雷成功\n");}
}//test.c
#include "game.h"
//扫雷
FindMine(mine, show, ROW, COL);

4.6 优化棋盘显示

玩家每次排雷时,上一次的棋盘并未消失。这会使打印的棋盘越来越多,不美观。
我们可以在每次排雷(输入坐标)后都清空一次屏幕,这样屏幕就只会显示一个棋盘,更加美观。
使用system(“cls”)可以清空屏幕,需要头文件stdlib.h
在这里插入图片描述
在这里插入图片描述

5.游戏代码

game.h

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include <time.h>
#include <stdlib.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 board[ROWS][COLS], int row, int col);//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int rows, int cols);

game.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//初始化扫雷棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;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 (j = 0; j <= col; j++){printf("%d ", j);}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 board[ROWS][COLS], int row, int col)
{int c = EASY_COUNT;while (c){int a = rand() % row + 1;int b = rand() % col + 1;board[a][b] = '1';c--;}
}//通用方法:计算坐标周围8个格中雷的数量
//static int GetMineCount(char mine[ROWS][COLS], int x, int y)
//{
//	int count = 0;
//	if (mine[x - 1][y-1] == '1')
//		count++;
//	if (mine[x - 1][y] == '1')
//		count++;
//	if (mine[x - 1][y + 1] == '1')
//		count++;
//	if (mine[x][y - 1] == '1')
//		count++;
//	if (mine[x][y + 1] == '1')
//		count++;
//	if (mine[x + 1][y - 1] == '1')
//		count++;
//	if (mine[x + 1][y] == '1')
//		count++;
//	if (mine[x + 1][y + 1] == '1')
//		count++;
//	return count;
//}//计算坐标周围8个格中雷的数量
static 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");break;}else if (show[x][y] != '*'){printf("该坐标已被排查了,请重新输入\n");}else{   //不是雷,就统计x,y有多少雷?int c = GetMineCount(mine, x, y);show[x][y] = c + '0';DisplayBoard(show, ROW, COL);win++;}}elseprintf("坐标非法,请重新输入\n");}if (win == row * col - EASY_COUNT){printf("排雷成功\n");}
}

排雷棋盘打印出来是给我们程序员看的,方便我们调试,给玩家玩时可以注释掉。
随机数的种子不需要每局都获取,所以我们可以把它放进main函数里。
test.c

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"//菜单
void menu()
{printf("*******************\n");printf("******1.play ******\n");printf("******0.exit ******\n");printf("*******************\n");
}//游戏
void game()
{char mine[ROWS][COLS] ;//定义char show[ROWS][COLS] ;//初始化排雷棋盘InitBoard(mine, ROWS, COLS, '0');//打印排雷棋盘//DisplayBoard(mine, ROW, COL);//初始化展示棋盘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 int)time(NULL));//产生随机的种子,用于rand产生真随机数do{menu();printf("请输入>:");scanf("%d", &input);switch (input){case 1:printf("开始游戏\n");game();break;case 0:printf("退出游戏\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}

6.总结

好啦,这就是初阶扫雷的全部内容了,大家可以跟着操作起来,一起进步。由于我目前能力有限,写的扫雷代码还是有很大的优化空间,比如不能自动将周围没有雷的格子跳过,不能标记雷等。大家有什么问题也可以在评论区多多交流,感谢大家的阅读!

点赞收藏加关注,C语言学习不迷路!

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

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

相关文章

单片机之硬件记录

一、概念 VBAT 当使用电池或其他电源连接到VBAT脚上时&#xff0c;当VDD断电时&#xff0c;可以保存备份寄存器的内容和维持RTC的功能。如果应用中没有使用外部电池&#xff0c;VBAT引脚应接到VDD引脚上。 VCC&#xff1a;Ccircuit 表示电路的意思,即接入电路的电压&#x…

C语言入门Day_18 判断和循坏的小结

目录 前言&#xff1a; 1.判断 2.循环 3.课堂笔记 4.思维导图 前言&#xff1a; 判断语句和循环语句都可以大致分为三个部分&#xff0c;第一个部分是固定的语法格式&#xff1b;第二部分是代码的执行顺序&#xff0c;第三部分是判断和循环成立与否的判断条件。 1.判断 1…

Allegro166版本如何在颜色管理器中实时显示层面操作指导

Allegro166版本如何在颜色管理器中实时显示层面操作指导 在用Allegro166进行PCB设计的时候,需要在颜色管理器中频繁的开关层面。但是166不像172一样在颜色管理器中可以实时的开关层面,如下图 需要打开Board Geometry/Soldermask_top层,首先需要勾选这个层面,再点击Apply即…

直播|DITA内容发布工具解析 - 问答总结

9月6日&#xff0c;我们进行了一场名为“DITA内容发布工具解析”的直播。通过直播&#xff0c;大家了解到&#xff1a; DITA-OT简介 默认输出效果 定制以后输出效果 发布过程与样式定制 在问答环节&#xff0c;大家进行了热烈沟通。我将几个大家关心的问题和答复总结如下&…

Vue2进阶篇学习笔记

文章目录 Vue2进阶学习笔记前言1、Vue脚手架学习1.1 Vue脚手架概述1.2 Vue脚手架安装1.3 常用属性1.4 插件 2、组件基本概述3、非单文件组件3.1 非单文件组件的基本使用3.2 组件的嵌套 4、单文件组件4.1 快速体验4.2 Todo案例 5、浏览器本地存储6、组件的自定义事件6.1 使用自定…

MySQL 全局锁、表级锁、行锁详解

前言 MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类&#xff0c;全局锁和表级锁是在server层实现的。 全局锁 全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法&#xff0c;命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状…

软件测试/测试开发丨测试用例自动录入 学习笔记

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27139 测试用例自动录入 测试用例自动录入的价值 省略人工同步的步骤&#xff0c;节省时间 兼容代码版本的自动化测试用例 用例的执行与调度统一化管理…

【数据结构】红黑树的插入与验证

文章目录 一、基本概念1.时代背景2. 基本概念3.基本性质 二、实现原理1. 插入1.1变色1.2旋转变色①左旋②右旋③右左双旋④左右双旋 2.验证 源码总结 一、基本概念 1.时代背景 1972年鲁道夫拜尔(Rudolf Bayer)发明了一种数据结构&#xff0c;这是一种特殊的B树4阶情况。这些树…

蓝桥杯打卡Day6

文章目录 N的阶乘基本算术整数查询 一、N的阶乘OI链接 本题思路&#xff1a;本题是关于高精度的模板题。 #pragma GCC optimize(3) #include <bits/stdc.h>constexpr int N1010;std::vector<int> a; std::vector<int> f[N];std::vector<int> mul(in…

MATLAB入门-数据的导入和导出

MATLAB入门-数据的导入和导出 注&#xff1a;本篇文章是课程学习笔记&#xff0c;课程链接为&#xff1a;头歌 常见的几个导入数据的方法 load函数 load函数专门用于引入MATLAB的.mat格式数据&#xff0c;十分的简单方便。 例如&#xff1a;一个-ASCII编码形式存储的数据文件…

使用本地mysql+linux实现mysql主从同步

1.配置linux 保证linux已经安装好了mysql1.1修改该linux配置文件 vim /etc/my.cnf1.2重启linux的mysql systemctl restart mysqld1.3使用账户密码登录linux中的mysql,查看是否配置成功 mysql> show master status;若显示有FIile和Posttion就表示注linux的主节点配置成功…

【算法】一文带你从浅至深入门dp动态规划

文章目录 一、前言二、动态规划理论基础1、基本概念2、动态规划五部曲【✔】3、出错了如何排查&#xff1f; 三、实战演练&#x1f5e1;0x00 斐波那契数0x01 第N个泰波那契数0x02 爬楼梯0x03 三步问题0x04 使用最小花费爬楼梯⭐解法一解法二 0x05 解码方法* 四、总结与提炼 一、…

输入时并未按照格式,没注意汉字符号

&#x1f388;问题现象&#xff1a; 运行出来的代码没得到想要的结果&#xff1a; &#x1f388;原因分析&#xff1a; 程序运行起来了&#xff0c;计算的结果是错误的&#xff0c;这个最好的解决办法就是调试&#xff0c;一步步的看代码在每个阶段的值是不是我们期望的&…

docker-compose deploy 高可用 elasticsearch TLS

文章目录 1.sysctl2. swap3. hosts4. 配置 instances.yaml5. 创建证书6. 部署7. 修改 kibanna 密码8. 清理 1.sysctl [rootgithub es_tls]# cat /etc/sysctl.conf # sysctl settings are defined through files in # /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/…

Redis优化 RDB AOF持久化

---------------------- Redis 高可用 ---------------------------------------- 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境…

【SpringMVC】实现增删改查(附源码)

目录 引言 一、前期准备 1.1.搭建Maven环境 1.2.导入pom.xml依赖 1.3.导入配置文件 ①jdbc.properties ②generatorConfig.xml ③log4j2.xml ④spring-mybatis.xml ⑤spring-context.xml ⑥spring-mvc.xml ⑦修改web.xml文件 二、逆向生成增删改查 2.1.导入相关u…

长胜证券:融券打新虽失宠 券源分配仍需透明

近期&#xff0c;关于战略投资者出借限售股作为融券券源的准则备受商场热议。不少投资者担心&#xff0c;跟着新股的大都券源被量化私募掌握&#xff0c;量化私募融券打新的战略有或许成为新股上市首日上涨后回身跌跌不休的首要原因。 券源分配是否有失公允&#xff1f;融券打…

基于SSM的家居商城系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【云原生】Kubeadmin安装k8s集群

目录 前言&#xff1a; 一 环境部署 1.1 服务器部署功能 1.2 环境准备&#xff08;所有节点&#xff09; 二 安装docker&#xff08;所有节点&#xff09; 三 所有节点安装kubeadm&#xff0c;kubelet和kubectl 3.1 定义kubernetes源 3.2 开机自启kubelet 四 部署K8S集…

【LeetCode】剑指 Offer <二刷>(6)

目录 题目&#xff1a;剑指 Offer 12. 矩阵中的路径 - 力扣&#xff08;LeetCode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 13. 机器人的运动范围 - 力扣&#…