目录
前言:
相信许多人都玩过扫雷吧
是像这样的?
还是玩过这样的 ?
请你不要想多了,怎么可能是上面的那种。(反正我是没有玩过上面的这一种的)
那今天就让我们来介绍扫雷游戏的原理和编写要用的代码吧?
首先就让我们先了解一下扫雷吧
背景:
1992年4月6日,扫雷和纸牌、空当接龙等小游戏搭载在Windows 3.1系统中与用户见面,主要目的是让用户训练使用鼠标。这个游戏的玩法很简单,有初级、中级、高级和自定义等模式,雷区中随机布置一定数量的地雷,玩家需要尽快找出所有不是地雷的方块,但不许踩到地雷。
扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。
不久,“方块”被改写成了游戏“Rlogic”。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。
1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。
这款游戏的玩法是在一个9*9(初级),16*16(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
现在就让我们进入正轨吧。
一·形成棋盘
1.形成菜单
任何游戏都是要去有一个菜单(在menu函数中设计一个菜单的形状)
void menu()
{
printf("************************\n");
printf("****** play 1> ******\n");
printf("****** play 0> ******\n");
printf("************************\n");
}
有了菜单的形状肯定要有功能去操控他(使用do while 结构和switch语句进行控制)
void text()
{
int input = 0;
do
{
menu();
printf("请选择将要输入的数>");
scanf("%d", &input);
switch(input)
{
case 1:
game();
//game()
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入失败,请重新在输入\n");
break;
}
} while (input);
}
结果如下:
输入1则开始游戏。(顺路进入game函数中进行实现)
输入0则结束游戏。
2.形成mine和show两个棋盘
对于扫雷游戏来说相当于是需要两个棋盘来进行操作的,一个在明,让我们去看,另一个在暗,去设计棋盘上面的雷。
形成棋盘就需要去使用数组去实现。(二维数组形成的数就相当于是一个棋盘)
所以,我们开始设计两个数组,一个以mine(雷)命名,是一个内部放置雷的数组;另一个以show命名,是一个展示给我们看的外部数组。
char mine[Rows][Clrs] = { 0 };
char show[Rows][Clrs] = { 0 };
最后展示的样子为:(,)
左边为show数组 右边为mine数组
3.初始化数组
在mine数组中因为他是要展示给我们看的。就使用”*“来当作雷;show数组因为要布置雷可以先使用字符0来布置整个棋盘,在使用字符1 来当作雷。
先来给声明吧。
//初始化棋盘
initboard(mine, Rows, Clrs, '0');//布置好的雷的信息
initboard(show, Rows, Clrs, '*');//排查出的雷的信息
此时有可能有人会对Row和Clr产生疑惑。 为什么这个数组里面写字母不写数字?会不会写错了?
但真正的答案是完全没有写错,只是运用了一个文件把Row,Clr当成数字(在后面会介绍到,请读者不要担心,如果想知道为什么可以往下参考五.文件这一节)。
初始化:
void initboard(char board[Rows][Clrs], int rows, int clrs,char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < clrs; j++)
{
board[i][j] = set;
}
}
}//初始化棋盘(将棋盘初始化为‘0’,和‘*’)
二.打印棋盘和布置雷
1.打印棋盘
先声明一下:
//打印棋盘
printboard(mine, Row, Clr);
printboard(show, Row, Clr);
在写出代码:
void printboard(char print[Rows][Clrs], int row, int clr)
{
int i = 0, j = 0;
printf("------扫雷游戏------\n");
for (int i =0; i <=clr; i++)
printf("%2d", i);//设计行的序号(相当于直接打印行号)
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%2d ", i);//设计列的序号(在打印列中的数字的时候顺路打印序号)
for (j = 1; j <=clr; j++)
{
printf("%c ",print[i][j]);
}
printf("\n");
}
}
利用for循环进行遍历棋盘
2.布置雷
在布置雷前我们要先思考一下该怎么布置,我们可以利用一个生成随机数的函数---------rand,srand。光知道随机数的生成还不够我们可以深入一下,控制一下随机数的生成。
首先,我们要了解一个头文件 。
在知道具体该怎么声明。
srand((unsigned int)time(NULL));
然后在声明一个布置雷的函数。
//布置雷
setmine(mine, Row, Clr);
最后是代码的解释。
void setmine(char board[Rows][Clrs], int row, int clr)
{
int count = setmin;
while (count)
{
int x = rand() % row + 1;//rand()%row相当于会在0~row之间随机选择数,+1可以从1到row+1;
int y = rand() % clr + 1; //形成坐标
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
注意:
需要定义函数的时候应先放布置雷的函数,后放打印函数,如果先放打印函数会导致棋盘上面看不到生成的雷(‘1’),打印出来的样子是初始化的样子。
如图:错误的代码:
//打印棋盘 printboard(mine, Row, Clr); printboard(show, Row, Clr); //布置雷 setmine(mine, Row, Clr);
![]()
无生成雷棋盘
正确的应该为:
//布置雷 setmine(mine, Row, Clr); //打印棋盘 printboard(mine, Row, Clr); printboard(show, Row, Clr);
![]()
有生成雷棋盘
三·排查雷和形成初阶棋盘
(1)排查雷
①声明一下
一般来说排查先声明一下。
findmine(mine, show, Row, Clr);
//排查雷
void findmine(char mine[Rows][Clrs], char show[Rows][Clrs], int row, int clr);
因为排查一次是排查已选择点附近一周的点,这个时候如果选择一个靠近边缘的点,就有可能会导致出问题。
如图:
这个时候就要我们选择使用一个大一点的棋盘了(这就是之前rows和row的区别)。
②开始排查
由上文可知排查是排查已选择点附近一周的点,那这句话是什么意思了?
其实就相当于利用字符1和字符0进行排查,排查到选择点时如果是字符1也就是雷,就炸了;
如果排到了不是雷,就对他的一周进行汇总看看有多少个雷和字符0(不是零的部分)。
//开始排查雷
void findmine(char mine[Rows][Clrs], char show[Rows][Clrs], int row,int clr)
{
int x = 0, y = 0;
int set = 0;//用来排雷的,记作排雷的数量,(用来让循环停下来的,不然只能排到雷才可以结束)
while (set<Row*Clr-setmin)//当排雷的数量
{
printf("请输入你要排查的目标\n");
scanf("%d %d", &x, &y);
if (x >0 && x <= row && y>0 && y <=clr)
{
if (show[x][y] == '*')//用来防止代码输入重复;
{
if (mine[x][y] == '1')//选中雷
{
printf("很遗憾你被炸死了\n");
printf("这是答案\n");
printboard(mine, Row, Clr);//把棋盘上面雷的分布给打印下来;
break;
}
else {
int count = getminecount(mine, x, y);//设计一个函数用来统计你选的坐标周围有几个雷;
//用count来接受函数返回的雷的个数;
show[x][y] = count + '0';
printboard(show, Row, Clr);
set++;
}
}
知道了原理,那有什么办法可以记录了?
这时就可以想到字符1和字符0的特性,他们是字符。可以利用字符进行加减,然后得出的结果就是周围一圈排查雷的数量。
在排查的时候有可能会出现一个问题,就是除非排查到雷,游戏才会结束,那么为了让游戏结束可以让设置一下,让每次排查到雷,就让雷的次数逐渐减少(上述代码中的set就是起到这个作用的)。
详细见代码:
方法一(相当于暴力直接把选择点周围的所以点直接排查出来):
//统计周围有几个雷
//计算雷的个数(方法一)
static int getminecount(char mine[rows][clrs], 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] +
mine[x - 1][y - 1] +
mine[x - 1][y + 1] -
8 *'0';
}
方法二(运用了循环遍历):
//方法二
static int getminecount(char mine[Rows][Clrs], int x, int y)
{
int i = 0;
int cout = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
cout += (mine[i][j] - '0');
}
}
return cout;
}
注意:有时候有可能在在排查的时候有可能会出现排查错误像输入重复,像输入的位置不符合棋盘的规定。这些情况都要在排查的时候注意。
在排查完毕后就可以看到这样界面:
(2)形成初阶棋盘
最后就可以形成初阶的棋盘了;
四.游戏的扩展
经过上述的介绍,相信大家对扫雷游戏已经有了基本的认识。那我们能不能对这个游戏加以设计,让它显得高档一些。
①可以设置游戏的难易程度,②设置排查位置不是雷,周围也不是雷,可以展开周围的一片。③可以设置标记雷,④也可以加上记录扫雷的时间。
在这本人由于技术不精只能做出②。以下是效果图:
那具体是怎样来的了?那就慢慢分析一下。
首先我们要思考一下,该怎么使用了?那就要使用到递归了。
对于这次的递归本人是这样想的:①就相当于是从选中点的位置开始想四周进行排雷,遇到雷就停下来②在实现扩展的这个过程中双层循环先执行完后在实行递归用来改变位置,就相当于在对函数进行递归先把九宫格的雷排完了,在从九宫格的每个点,以这个的九宫格在向外进行排雷。
以下是代码的实现:
//设置排查位置不是雷,周围也不是雷,可以展开周围的一片的函数。
void emptymine(char mine[Rows][Clrs], char show[Rows][Clrs], int x, int y)
{
int num = getminecount(mine, x, y);
if (x<1 || x>Row || y<1 || y>Clr)//判断输入的坐标符合棋盘要求
return;
if (num)
show[x][y] = num + '0';//用来记录选中点附近雷的数目
else if (show[x][y]=='*')
{
show[x][y] = ' ';//把不是雷的区域覆盖为空
int i = 0;
for (i = x - 1; i <= x + 1; i++)//使用循环找到无雷的区域,然后利用递归去给他覆盖
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
emptymine(mine, show, i, j);
}
}
}
}
五.文件
(1)解释:
对于之前的问题,下面开始解释:
对于Row和Rows的问题其实就是运用了一个自定义头文件(game.h)
那为什么要运用了?
因为可以用此头文件去包含其他的头文件,以达到简单的作用。不然如果后期的函数多了起来,如果要对其进行修改的话,会增大工作量,引起不必要的麻烦。
如果要是使用这个自定义头文件的话,就要这样使用了。
#include "game.h"
(2)代码展示
①game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define Row 9//相当于是Row的值为9;//以后使用Row的值就为9
#define Clr 9//Clr也是一样的
#define setmin 10
#define Rows Row+2
#define Clrs Clr+2
//初始化棋盘
void initboard(char board[Rows][Clrs], int rows, int clrs,char set);
//打印棋盘
void printboard(char print[Row][Clr], int row,int clr);
//布置雷的信息
void setmine(char board[Rows][Clrs], int row, int clr);
//排查雷
void findmine(char mine[Rows][Clrs], char show[Rows][Clrs], int row, int clr);
//设置排查位置不是雷,周围也不是雷,可以展开周围的一片的函数。
void emptymine(char mine[Rows][Clrs], char show[Rows][Clrs], int row, int clr);
②game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void initboard(char board[Rows][Clrs], int rows, int clrs,char set)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < clrs; j++)
{
board[i][j] = set;
}
}
}//初始化棋盘(将棋盘初始化为‘0’,和‘*’)
void printboard(char print[Rows][Clrs], int row, int clr)
{
int i = 0, j = 0;
printf("------扫雷游戏------\n");
for (int i =0; i <=clr; i++)
printf("%2d", i);//设计行的序号(相当于直接打印行号)
printf("\n");
for (i = 1; i <=row; i++)
{
printf("%2d ", i);//设计列的序号(在打印列中的数字的时候顺路打印序号)
for (j = 1; j <=clr; j++)
{
printf("%c ",print[i][j]);
}
printf("\n");
}
}
void setmine(char board[Rows][Clrs], int row, int clr)
{
int count = setmin;
while (count)
{
int x = rand() % row + 1;//rand()%row相当于会在0~row之间随机选择数,+1可以从1到row+1;
int y = rand() % clr + 1; //形成坐标
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//统计周围有几个雷
//计算雷的个数(方法一)
//static int getminecount(char mine[rows][clrs], 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] +
// mine[x - 1][y - 1] +
// mine[x - 1][y + 1] -
// 8 *'0';
//
// }
//方法二
static int getminecount(char mine[Rows][Clrs], int x, int y)
{
int i = 0;
int cout = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
cout += (mine[i][j] - '0');
}
}
return cout;
}
//设置排查位置不是雷,周围也不是雷,可以展开周围的一片的函数。
void emptymine(char mine[Rows][Clrs], char show[Rows][Clrs], int x, int y)
{
int num = getminecount(mine, x, y);
if (x<1 || x>Row || y<1 || y>Clr)//判断输入的坐标符合棋盘要求
return;
if (num)
show[x][y] = num + '0';//用来记录选中点附近雷的数目
else if (show[x][y]=='*')
{
show[x][y] = ' ';//把不是雷的区域覆盖为空
int i = 0;
for (i = x - 1; i <= x + 1; i++)//使用循环找到无雷的区域,然后利用递归去给他覆盖
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
emptymine(mine, show, i, j);
}
}
}
}
//开始排查雷
void findmine(char mine[Rows][Clrs], char show[Rows][Clrs], int row,int clr)
{
int x = 0, y = 0;
int set = 0;//用来排雷的,记作排雷的数量,(用来让循环停下来的,不然只能排到雷才可以结束)
while (set<Row*Clr-setmin)//当排雷的数量
{
printf("请输入你要排查的目标\n");
scanf("%d %d", &x, &y);
if (x >0 && x <= row && y>0 && y <=clr)
{
if (show[x][y] == '*')//用来防止代码输入重复;
{
if (mine[x][y] == '1')//选中雷
{
printf("很遗憾你被炸死了\n");
printf("这是答案\n");
printboard(mine, Row, Clr);//把棋盘上面雷的分布给打印下来;
break;
}
else {
//int count = getminecount(mine, x, y);//设计一个函数用来统计你选的坐标周围有几个雷;
//用count来接受函数返回的雷的个数;
emptymine(mine,show, x, y);
printboard(show, Row, Clr);
set++;
}
}
else
{
printf("输入代码重复,请重新输入\n");//用来防止代码输入重复;
}
}
else
{
printf("输入坐标错误,请重新再输入");
}
if (set == Row * Clr - setmin)//排完雷了
{
printf("恭喜你排雷成功!!!\n");
printboard(show, Row, Clr);
break;
}
}
}
③text.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("************************\n");
printf("****** play 1> ******\n");
printf("****** play 0> ******\n");
printf("************************\n");
}
void game()
{//开始实现扫雷游戏
char mine[Rows][Clrs] = { 0 };
char show[Rows][Clrs] = { 0 };
//初始化棋盘
initboard(mine, Rows, Clrs, '0');//布置好的雷的信息
initboard(show, Rows, Clrs, '*');//排查出的雷的信息
//布置雷
setmine(mine, Row, Clr);
//需要先布置雷,才可以在打印的时候出来,如果先打印棋盘会导致,
// 打印出来的样子是初始化的样子,就不会打印出布置雷的棋盘
//打印棋盘
//printboard(mine, Row, Clr);
printboard(show, Row, Clr);
//排查雷
findmine(mine, show, Row, Clr);
}
void text()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择将要输入的数>");
scanf("%d", &input);
switch(input)
{
case 1:
printf(" 游戏开始\n");
game();
//game()
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入失败,请重新在输入\n");
break;
}
} while (input);
}
int main() {
text();
return 0;
}
总结一下:
扫雷游戏代码虽然多,但可以用来锻炼一下自己的思维能力的,在敲代码的途中,你也可以去复习一下以前学过的知识点看看自己掌握的咋样?像从语言的分支与循环,数组,函数,随机数的生成,递归。其实扫雷游戏也是挺有趣的,兄弟们在看了我的博客后也可以私下去敲一下,玩一玩,希望我的博客可以大家有帮助,如果觉得我的博客还可以的话可以关注一下,三起走起(你们的鼓励就是我的动力)。谢谢各位了!
在此说一下“细雨烟台”是我在写代码的之前的名字,“%*淋濛初雨*%”是写的途中改了。