##目录在右边侧栏哦!
前言
扫雷游戏相信大家都很熟悉了 , 那么通过我们之前的内容就可以实现一个控制台扫雷游戏了,笔者将带领朋友们了解。
一、扫雷游戏介绍
扫雷游戏每一台windows系统的电脑都有,是一个很简单很经典的益智类游戏,但我们不能小看它,小游戏蕴含大智慧!. 最基本的规则:你点到一个数字,如果是3,那就说明最靠近他它周围的8个格里有3个雷。. 然后通过相邻或者相间的数字之间的交集来判断哪些是雷。. 以下讲具体介绍。
二、游戏要求
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷
• 可以排查雷 。如果位置不是雷,就显示周围有几个雷 。 如果该位置是雷,就炸死游戏结束 。 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
1】 扫雷解释
1.标准棋盘图例
以上图就是一个标准的 9 * 9 扫雷棋盘, 默认该棋盘有 10 颗雷。
若排查位置为 蓝色 圈圈的位置,则就会统计其周围 8 个位置雷的个数 ,如图 蓝色圈圈就会被标上数字 “ 1 ” ,就说明该周围 8 个位置上有一个雷 ,通过这个思路就可以不断进行下去 ,直到雷的个数排完, 游戏结束。
2. 深度刨析(必看)
• 我们可以看到扫雷游戏是一个棋盘组成,在 c 语言中想要生成这样的棋盘,同时还要存入 81 个数据,我们首先想到的就是通过 数组 来实现,且是二维数组。
• 有的朋友说了:当然生成这样的棋盘也很简单 , 不就是遍历 “ 行 ” ,“ 列 ” 就可以了嘛 。 是当然没错,但我们还要深度思考一下 ,真就这么简单吗? 我们不仅要生成棋盘,而且还要让棋盘统计出周围的雷个数并且显示在判断的位置上,这可能就有点难度了。
• 假如:只有一个棋盘,那还要布置雷 , 还要让其显示数字,若 我们就认为 “ 1 ” 表示 “雷“ ,那你排查了 蓝色 圈圈位置上面确实是显示出了 数字 ” 1 “ 那这个 ” 1 “ 是我们想要的意思吗? 我们又怎么能知道这个 ” 1 “ 是统计的周围雷的个数呢?还是 这个位置本来就 设置为 ” 1 “ 表示雷的位置呢? 所以为了能保证游戏的可行性,我们不妨就设置 ” 2 “ 个数组 , 一个专门布置雷的棋盘 , 一个专门显示雷的信息的棋盘。
* 有了 ” 2 “ 个棋盘就方便操作了 ,我们可以在布置了雷的棋盘中统计所查位置周围雷的个数再放到专门显示雷的信息的棋盘就可以了。
• 因为我们是用控制台来实现的游戏 ,上图雷的效果是有点难度(笔者水平有限),所以我们就用 数字代替雷 。
• 为了给玩家神秘感 , 不妨把 显示雷信息的棋盘全部放为 ‘ * ’ 。
如图:
• 为了方便计算 ,不妨把 布置雷的棋盘全部放为 ‘ 0 ’ .
如图:
注意: 是字符 0 , 不是数字 0 哦 ! 原因: 我们要把这个数组统计雷的个数信息放到另一显示棋盘的字符数组里 , 这样 2 个为同一类型数组就更好执行了 。
2】 多文件实现
基本逻辑如图:
我们要写一个扫雷游戏基本属于一个小的工程了 , 全放在一个文件中实现就有点乱了,所以我们分为 3 个模块 。
补充(必看):
多文件中 :
• 头文件(.h)文件是用来声明的,因为我们所写的扫雷需要封装函数。函数就必须先声明后使用。
• 源文件(.c)是用来实现相应功能的。
---------------------------- 以上为游戏基础部分 --------------------------------------
三 、 游戏实现
1】 生成菜单
与前面写的猜数字游戏一样,扫雷游戏也同样需要一个菜单供玩家选择。
代码就不多赘述了!!
代码如下:
void game()
{
printf("欢迎进入游戏!\n");
}
void meun()
{
printf("******************************\n");
printf("*********1 . PLAY **********\n");
printf("*********0 . EXIT **********\n");
printf("******************************\n");
}
void test()
{
int inpout = 0;
srand((unsigned int)time(NULL));
do
{
meun();
printf("请选择->:");
scanf("%d", &inpout);
switch (inpout)
{
case 1:
printf("你已进入游戏:\n");
game();
break;
case 0:
printf("你已退出游戏! ");
break;
default:
printf("输入有误,请重新输入!\n");
break;
}
} while (inpout);
}
int main()
{
test();
return 0;
}
运行:
2】游戏测试模块(test.c)
test.c 文件中:
• 以上内容讲了我们这个工程分为三个模块 , 要进行游戏测试那么游戏的基本流程信息,逻辑是要展现出来的。 共 2 个数组 , ‘ 0 ’ 数组 和 ‘ * ’数组 。
补充:宏定义 ( game.h)中声明
为了让代码易于操作 , 我们布置雷的个数 ,布置棋盘都要用到宏定义 , 宏定义只用声明了就可以在本工程中使用了 。 如下:
char mine[ROWS][COLS];
char show[ROWS][COLS];
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL +2
这样定义是很方便的 , 如果我们要改变雷的个数 , 遍历数组就不需要一直改了 ,而且只需改掉宏定义的值就可以了 。 具体以下将会体现。
• 扫雷逻辑信息: 1. 布置雷 2. 排查雷 3. 显示雷的个数
补充:数组越界问题
想想我们定义的是数组 , 玩家只会看到9*9 的棋盘 , 但当玩家排查 蓝色圈圈 位置是时就找不到数组元素了 , 就越界了 。 所以为了避免越界我们可以布置 11*11 的棋盘这样就玩家排查位置时就不会有越界问题了。 如图:
我们可以把所有位置上放置上 ‘0’ 和 ‘*‘ ,只用给玩家显示 9*9 的棋盘 ,而且我们遍历数组时需要遍历每个元素,然后只需要访问 9*9 棋盘里的元素。这样宏定义的作用就大了,也就是为什么笔者在宏定义中定义了 ROWS , COLS , ROW , COL
#define ROW 9
#define COL 9
#define ROWS ROW+2 // 11 * 11 棋盘 - 行
#define COLS COL +2 // 11 * 11 棋盘 - 列
注: ROW --> 行 COL --> 列
代码如下:
// 定义棋盘
char mine[ROWS][COLS]; // ‘0’
char show[ROWS][COLS]; // ‘*’
// 布置雷
SetMine(mine,ROW,COL);
// 查雷
FindMine(mine,show,ROW,COL);
// 打印棋盘
PrintBorad(mine, ROW, COL);
PrintBorad(show, ROW, COL);
注: 实行游戏时打印棋盘部分只需打印出 show 棋盘 !
数组的初始化:
以上测试部分基本的逻辑搞清楚了 , 但还需让数组 设置为 ’0‘和’*‘ , 所以就要把数组所有元素初始化 。 2 个数组要初始化的内容不同但类型相同 , 有的朋友想到了 写 2 个 初始化函数就可以解决 , 但是这样代码就冗余了 。 所以我们可以在给数组传参的时候就可以给其传相应的初始化的元素。
代码如下:
// 初始化棋盘
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show,ROWS,COLS,'*');
3】 函数的声明(game.h)
因为我们这个工程涉及到函数的调用 , 函数调用之前必须声明 , 所以本工程所有的声明放到
game.h 中 。
game.h 文件中:
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL +2
#define MINENUM 10
// 初始化棋盘函数声明
void InitBoard(char arr[ROWS][COLS],int rows, int cols , int set);
// 布置雷函数声明
void SetMine(char mine[ROWS][COLS], int row, int col);
// 查雷函数声明
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
// 打印棋盘函数声明
void PrintBorad(char arr[ROWS][COLS], int row , int col );
4】 游戏实现模块(game.c)
因为我们对所用到的函数进行了 声明,在使用时只需包含头文件(.h)文件就可以了。
• 自己封装的函数 -- > #include " game.h " 这样的形式 。
• 函数库里函数的声明 --> #include <stdio.h> 这样的形式。
补充:
在布置雷的函数中 题目要求时随机的雷 , 故对声明部分优化
代码如下:
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL +2
#define MINENUM 10
#include <stdio.h>
#include <stdlib.h> // rand,srand 函数需包含的头文件
#include <time.h> // time 函数需包含的头文件
// 初始化棋盘函数声明
void InitBoard(char arr[ROWS][COLS],int rows, int cols , int set);
// 布置雷函数声明
void SetMine(char mine[ROWS][COLS], int row, int col);
// 查雷函数声明
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
// 打印棋盘函数声明
void PrintBorad(char arr[ROWS][COLS], int row , int col );
--------------------------------------------------------------------------------------------------------------------------------
game.c 中
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始棋盘函数
void InitBoard(char arr[ROWS][COLS], int rows, int cols ,int set)
{
int i = 0;
for (i = 0; i < rows; i++) // 行
{
int j = 0;
for (j = 0; j < cols; j++) // 列
{
arr[i][j] = set;
}
}
}
//布置雷函数
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = MINENUM; // 雷的个数
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
解析:
• 布置雷的函数中 ” x“ " y " 为玩家所排查的坐标
• count-- --> 每次成功布置一个雷个数就减 1 , 知道雷的个数布置完成 。
• 玩家所看到的是 9*9 的 棋盘 ,故只需在 9*9 棋盘布置雷即可。
• int x = rand() % row + 1; // 产生 1- 9 随机一个数
int y = rand() % col + 1; // 产生 1- 9 随机一个数
-------------------------------------------------------------------------------------------------------------------------------
// 查雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int stop = 0;
while (1)
{
printf("请输入你要排查的坐标->:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9) // 坐标有效
{
if(show[x][y] == '*') // 用于判断是否重复排查
{
if (mine[x][y] == '1')
{
printf("抱歉,你被炸死了!!\n");
printf("雷的位置如下:\n");
PrintBorad(mine, ROW, COL);
break;
}
else
{
// 统计所查坐标周围雷的个数
int count = CountMine(mine, x, y);
show[x][y] = count + '0';
PrintBorad(show, ROW, COL);
}
}
else
{
printf("该坐标已被排查,无需再次排查\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
游戏实现基本的框架就有了 , 但通过运行发现这样很难停下来 ,除非排完所有雷的个数游戏结束。
// 想想棋盘是 9*9 = 81 个格子 , 只有 10 个 格子是雷,也就是说有 71 个格子不是雷 , 那么我们只需加2个条件.
• 当排查格子的个数 < 71 就让其进入循环继续判断 ,遇到雷游戏结束。
• 当排查格子的个数 = 71 就显示排雷成功 , 游戏结束 。
优化:
// 查雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int Find = 0;
while (Find < (row*col) - MINENUM)
{
printf("请输入你要排查的坐标->:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9) // 坐标有效
{
if(show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("抱歉,你被炸死了!!\n");
printf("雷的位置如下:\n");
PrintBorad(mine, ROW, COL);
break;
}
else
{
// 统计所查坐标周围雷的个数
int count = CountMine(mine, x, y);
show[x][y] = count + '0';
PrintBorad(show, ROW, COL);
Find++;
}
}
else
{
printf("该坐标已被排查,无需再次排查\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (Find == (row * col) - MINENUM)
{
printf("恭喜你,扫雷成功!!\n");
PrintBorad(mine, ROW, COL);
}
}
统计雷的个数函数
代码:
// 统计雷的个数函数
static int CountMine(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 + 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x][y - 1] - 8 * '0');
}
解析:
这里补充一个知识点:
• 字符 1 ASCII 的值 = 49 •
• 字符 0 ASCII 的值 = 48 •
这样就可以得到 : ’1‘ - ’0‘ 的值 = 数字 1 ;也就是说通过这样的转化我们可以让字符变成数字 。
故:统计雷的个数我们只要把所查坐标周围的位置(8个)的字符相加 - 8*’0‘ 就可以得到雷的个数了。具体如图:
如图要排查此位置 , 便对其周围 8 个位置进行统计 ,最后 - 8*字符0 = 数字 1 ,这个 1 就是雷的个数了 。
// 统计雷的个数函数 static int CountMine(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 + 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x][y - 1] - 8 * '0'); }
朋友们要动手亲自感受一下!!!!
--------------------------------------------------------------------------------------------------------------------------------
打印棋盘
// 打印棋盘 PrintBorad(mine, ROW, COL); PrintBorad(show, ROW, COL);
我们在 test.c 中有 2 个打印棋盘的函数 , 一个 mine 是布置雷的棋盘, 一个 show 是显示棋盘是给玩家的。 故我们在 test.c 中只需 对显示棋盘放开就行 ,那就要把 mine 棋盘注释了。
具体打印:
为了让玩家对所查位置的坐标更加清楚,我们可以对其显示行号 , 列号 。
// 打印棋盘函数
void PrintBorad(char arr[ROWS][COLS], int row, int col)
{
printf("-----------扫雷游戏------------\n");
int i = 0;
for (int i = 0; i <=row; i++)
{
printf("%2d ", i);
}
//以上是打印列号
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%2d ", i); //打印行号
int j = 0;
for (j = 1; j <=col; j++)
{
printf("%2c ", arr[i][j]);
}
printf("\n");
}
}
5】整体代码展示
test.c 中
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void meun()
{
printf("******************************\n");
printf("*********1 . PLAY **********\n");
printf("*********0 . EXIT **********\n");
printf("******************************\n");
}
void game()
{
// 定义棋盘
char mine[ROWS][COLS]; // ‘0’
char show[ROWS][COLS]; // ‘*’
// 初始化棋盘
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show,ROWS,COLS,'*');
// 布置雷
SetMine(mine,ROW,COL);
// 查雷
FindMine(mine,show,ROW,COL);
// 打印棋盘
//PrintBorad(mine, ROW, COL);
PrintBorad(show, ROW, COL);
}
void test()
{
int inpout = 0;
srand((unsigned int)time(NULL));
do
{
meun();
printf("请选择->:");
scanf("%d", &inpout);
switch (inpout)
{
case 1:
printf("你已进入游戏:\n");
game();
break;
case 0:
printf("你已退出游戏! ");
break;
default:
printf("输入有误,请重新输入!\n");
break;
}
} while (inpout);
}
int main()
{
test();
return 0;
}
game.h 中
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL +2
#define MINENUM 10
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 初始化棋盘函数声明
void InitBoard(char arr[ROWS][COLS],int rows, int cols , int set);
// 布置雷函数声明
void SetMine(char mine[ROWS][COLS], int row, int col);
// 查雷函数声明
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
// 打印棋盘函数声明
void PrintBorad(char arr[ROWS][COLS], int row , int col );
game.c 中
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//初始棋盘函数
void InitBoard(char arr[ROWS][COLS], int rows, int cols ,int set)
{
int i = 0;
for (i = 0; i < rows; i++) // 行
{
int j = 0;
for (j = 0; j < cols; j++) // 列
{
arr[i][j] = set;
}
}
}
//布置雷函数
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = MINENUM;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
// 查雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int Find = 0;
while (Find < (row*col) - MINENUM)
{
printf("请输入你要排查的坐标->:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9) // 坐标有效
{
if(show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("抱歉,你被炸死了!!\n");
printf("雷的位置如下:\n");
PrintBorad(mine, ROW, COL);
break;
}
else
{
// 统计所查坐标周围雷的个数
int count = CountMine(mine, x, y);
show[x][y] = count + '0';
PrintBorad(show, ROW, COL);
Find++;
}
}
else
{
printf("该坐标已被排查,无需再次排查\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (Find == (row * col) - MINENUM)
{
printf("恭喜你,扫雷成功!!\n");
PrintBorad(mine, ROW, COL);
}
}
// 统计雷的个数函数
static int CountMine(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 + 1][y + 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x][y - 1] - 8 * '0');
}
// 打印棋盘函数
void PrintBorad(char arr[ROWS][COLS], int row, int col)
{
printf("-----------扫雷游戏------------\n");
int i = 0;
for (int i = 0; i <=row; i++)
{
printf("%2d ", i);
} // 打印列号
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%2d ", i);
int j = 0;
for (j = 1; j <=col; j++)
{
printf("%2c ", arr[i][j]);
}
printf("\n");
}
}
运行:
------------------------------------------ 以上就是扫雷游戏的介绍-------------------------------------------------