最近刚学完STL,心血来潮突然想做个扫雷玩玩,写了大半天,如有错误,请多指正。
做完这个之后,我在csdn上搜到的扫雷实现方法里,基本上都是在实际数组的基础上向四周扩展一圈然后使用递归,来保证不会出界,我的方法不大一样,再加上是第一个正经的cpp项目,故以此留念。
完整代码在文章结尾
扫雷的实现思路
第一步:创建出一个9*9的数组来存放方块,然后随机生成地雷,在地雷周围的方块正确生成数字。
第二步:用另外的9*9数组来存放方块是否可见,接收输入的坐标,根据两个数组容器来显示地图
第三步:判断成功或失败
具体的实现方法
- 主函数以及全局变量
vector<int>vbase;
vector<vector<int>>v; //存放地雷和数字的数组
vector<vector<int>>showv; //存放方块是否可见的数组
int main()
{
Flag:
vbase.assign(9,0);
v.assign(9,vbase); //利用vector容器嵌套,创建类似二维数组,初始化全为0
showv.assign(9,vbase);
MineRespawn(); //随机生成地雷方块
numberRespawn(); //给地雷周围方块赋值
while(judge()) //判断是否踩雷
{
system("cls"); //清屏
vprint(); //画出地图
inputV(); //输入坐标并判断
}
system("pause");
goto Flag; //重新开始游戏
return 0;
}
理论上来说用二维数组来表示方块就可以了,但是为了复习STL知识,“多此一举”地使用了vector容器
- MineRespawn函数 - 随机生成地雷方块
void MineRespawn()
{
srand((unsigned int)time(NULL));
for(int i = 0;i < 10;i++)
{
int randomNumLenth = rand()%9; //随机生成一个0~8的数字
int randomNumHeigh = rand()%9;
if(v[randomNumLenth][randomNumHeigh]!=9)//将地雷方块的数字赋值为9,后面有用的
{
v[randomNumLenth][randomNumHeigh]=9;
}
else
{
i--; //如果出现重复的,则让i后退一位,防止地雷数量减少
}
}
}
- numberRespawn函数 - 给地雷周围方块赋值
void numberRespawn()
{
for(int i = 0;i < 9;i++) //对所有方块进行遍历
{
for(int j = 0;j < 9;j++)
{
if(v[i][j] >= 9) //地雷方块为9,条件为>=9是因为会出现地雷挨着地雷的情况
{
expand(i,j,8,8,addfunc); //下文中会讲这个
}
}
}
}
首先上一部分中给地雷方块赋值为9,是因为一个普通方块最大为8(被8个地雷包围),并且如果地雷挨着地雷,一个地雷会给另一个地雷方块数字加1(变成10),因此判定条件为>=9
扫雷中难的部分来了,如果是地图中间的方块的话,只需要简单地对周围8个方块加1就行了,但是还会出现地雷在角上,在边上的情况。
第一种不动脑子的解决办法是
void numberRespawn1()
{
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(v[i][j] >= 9)
{
if(i == 0&&j == 0)
{
v[i+1][j] += 1;
v[i][j+1] += 1;
v[i+1][j+1] += 1;
}
else if(i == 8&&j == 8)
{
v[i-1][j] += 1;
v[i][j-1] += 1;
v[i-1][j-1] += 1;
}
else if(i == 0&&j == 8)
{
v[i+1][j] += 1;
v[i][j-1] += 1;
v[i+1][j-1] += 1;
}
else if(i == 8&&j == 0)
{
v[i-1][j] += 1;
v[i][j+1] += 1;
v[i-1][j+1] += 1;
}
else if(i == 0)
{
v[i+1][j] += 1;
v[i][j+1] += 1;
v[i][j-1] += 1;
v[i+1][j+1] += 1;
v[i+1][j-1] += 1;
}
else if(i == 8)
{
v[i-1][j] += 1;
v[i][j+1] += 1;
v[i][j-1] += 1;
v[i-1][j-1] += 1;
v[i-1][j+1] += 1;
}
else if(j == 0)
{
v[i+1][j] += 1;
v[i-1][j] += 1;
v[i][j+1] += 1;
v[i+1][j+1] += 1;
v[i-1][j+1] += 1;
}
else if(j == 8)
{
v[i+1][j] += 1;
v[i-1][j] += 1;
v[i][j-1] += 1;
v[i-1][j-1] += 1;
v[i+1][j-1] += 1;
}
else if(0 < i < 8&&0< j < 8)
{
v[i+1][j] += 1;
v[i-1][j] += 1;
v[i][j+1] += 1;
v[i][j-1] += 1;
v[i+1][j+1] += 1;
v[i-1][j-1] += 1;
v[i-1][j+1] += 1;
v[i+1][j-1] += 1;
}
}
}
}
}
第二种方法:封装成函数套函数(其实是因为后面视野展开的时候也要用到类似的方法)
void addfunc(int x,int y){v[x][y] += 1;}
void expand(int i,int j,int lenth,int width,void (*func)(int,int))
{
for(int a = -1;a <= 1;a++)
{
for(int b = -1;b <= 1;b++)
{
if(a == 0&&b == 0){continue;} //如果是方块本身就直接跳过
//通过遍历所有可能并且加上坐标条件的限制可以锁定周围的所有方块并保证不会出界
//再强调一遍,用其他函数作为本函数参数是因为,之后视野扩张的时候还会再使用一次
if((0 <= i + a && i + a <= lenth)&&(0 <= j + b && j + b <= width)){(*func)(i+a,j+b);}
}
}
}
void numberRespawn()
{
for(int i = 0;i < 9;i++) //对所有方块进行遍历
{
for(int j = 0;j < 9;j++)
{
if(v[i][j] >= 9) //地雷方块为9,条件为>=9是因为会出现地雷挨着地雷的情况
{
expand(i,j,8,8,addfunc); //函数作为其他函数的参数
}
}
}
}
expand(i,j,8,8,addfunc); 这里传入的是addfunc函数的地址
- vprint函数
void vprint(int authority = 0) //这里给了一个权限,如果权限是1的话直接打印整张地图
{
cout<<" y 0 1 2 3 4 5 6 7 8 "<<endl<<"x"<<endl;
for(int i = 0;i < 9;i++)
{
cout<<i<<" ";
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1||authority == 1) //如果权限为0的话,必须在showv中为1的方块才能被显示
{
if(v[i][j] >= 9) //数字大于9即为雷
{
cout<<"# ";
}
else if(v[i][j] == 0) //如果数字为0,打印空格
{
cout<<" ";
}
else //除此之外,方块数字是啥打印啥
{
cout<<v[i][j]<<" ";
}
}
else if(showv[i][j] == 0||authority == 1)
{
cout<<"* ";
}
}
cout<<endl;
}
}
平平无奇的打印,未优化
- inputV函数
void equalfunc(int x,int y){showv[x][y] = 1;} //showv为0是不可见,为1是可见
void expand(int i,int j,int lenth,int width,void (*func)(int,int))
{
for(int a = -1;a <= 1;a++)
{
for(int b = -1;b <= 1;b++)
{
if(a == 0&&b == 0){continue;}
if((0 <= i + a && i + a <= lenth)&&(0 <= j + b && j + b <= width)){(*func)(i+a,j+b);}
}
}
}
void inputV()
{
int num = 0;
int inputNum = 0;
cout<<"请输入两位数字,第一位为x轴,第二位为y轴"<<endl;
cin>>inputNum;
if(inputNum > 88||inputNum < 0){return;}
int x = (int)(inputNum/10); //取十位数
int y = (int)(inputNum%10); //取个位数
showv[x][y] = 1;
if(v[x][y] != 0){return;}
do
{
num++;
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1&&v[i][j] == 0) //如果已经被显示且其值为0的话开辟周围视野
{
expand(i,j,8,8,equalfunc);
}
}
}
}while(num<=10); //这里我没有什么好的想法判断视野是否全部扩展,于是偷懒了
}
inputV函数集合了输入和视野开辟(扫雷中的特性,如果点到的方块为0,则与0相连部分的视野也会拓展)
视野开辟与上文中的地雷周围数字的生成有点类似,但又不相同,故封装函数。
- judge函数 - 判断是否成功
int judge()
{
int num = 0;
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1)
{
num++;
if(v[i][j] >= 9)
{
system("cls");
vprint(1);
cout<<"失败"<<endl;
return 0;
}
}
}
}
if(num == 71) //一共81个方块,10个雷
{
system("cls");
vprint(1);
cout<<"恭喜"<<endl;
return 0;
}
return 1; //主函数中 while(judge()) return 1则继续
}
效果如下
完整代码如下
#include<iostream>
using namespace std;
#include<algorithm>
#include<ctime>
#include<numeric>
#include<vector>
vector<int>vbase;
vector<vector<int>>v;
vector<vector<int>>showv;
void MineRespawn()
{
srand((unsigned int)time(NULL));
for(int i = 0;i < 10;i++)
{
int randomNumLenth = rand()%9;
int randomNumHeigh = rand()%9;
if(v[randomNumLenth][randomNumHeigh]!=9)
{
v[randomNumLenth][randomNumHeigh]=9;
}
else
{
i--;
}
}
}
void addfunc(int x,int y){v[x][y] += 1;}
void equalfunc(int x,int y){showv[x][y] = 1;}
void expand(int i,int j,int lenth,int width,void (*func)(int,int))
{
for(int a = -1;a <= 1;a++)
{
for(int b = -1;b <= 1;b++)
{
if(a == 0&&b == 0){continue;}
if((0 <= i + a && i + a <= lenth)&&(0 <= j + b && j + b <= width)){(*func)(i+a,j+b);}
}
}
}
void numberRespawn()
{
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(v[i][j] >= 9)
{
expand(i,j,8,8,addfunc);
}
}
}
}
void vprint(int authority = 0)
{
cout<<" y 0 1 2 3 4 5 6 7 8 "<<endl<<"x"<<endl;
for(int i = 0;i < 9;i++)
{
cout<<i<<" ";
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1||authority == 1)
{
if(v[i][j] >= 9)
{
cout<<"# ";
}
else if(v[i][j] == 0)
{
cout<<" ";
}
else
{
cout<<v[i][j]<<" ";
}
}
else if(showv[i][j] == 0||authority == 1)
{
cout<<"* ";
}
}
cout<<endl;
}
}
void inputV()
{
int num = 0;
int inputNum = 0;
cout<<"请输入两位数字,第一位为x轴,第二位为y轴"<<endl;
cin>>inputNum;
if(inputNum > 88||inputNum < 0){return;}
int x = (int)(inputNum/10);
int y = (int)(inputNum%10);
showv[x][y] = 1;
if(v[x][y] != 0){return;}
do
{
num++;
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1&&v[i][j] == 0) //如果已经被显示且其值为0的话开辟周围视野
{
expand(i,j,8,8,equalfunc);
}
}
}
}while(num<=10);
}
int judge()
{
int num = 0;
for(int i = 0;i < 9;i++)
{
for(int j = 0;j < 9;j++)
{
if(showv[i][j] == 1)
{
num++;
if(v[i][j] >= 9)
{
system("cls");
vprint(1);
cout<<"失败"<<endl;
return 0;
}
}
}
}
if(num == 71)
{
system("cls");
vprint(1);
cout<<"恭喜"<<endl;
return 0;
}
return 1;
}
int main()
{
Flag:
vbase.assign(9,0);
v.assign(9,vbase);
showv.assign(9,vbase);
MineRespawn();
numberRespawn();
while(judge())
{
system("cls");
vprint();
inputV();
}
system("pause");
goto Flag;
return 0;
}