C++扫雷小游戏(非递归实现版)

最近刚学完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;
}
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
做一个M*N的扫雷游戏,每个方格包含两种状态:关闭和打开,初始化时每个方格都是关闭的,一个打开的方格也会包含两种状态:一个数字或者一个雷。你可以打开一个方格,如果你打开的是一个雷,那么就失败;否则就会打开一个数字,该数字是位于[0,8]的一个整数,该数字表示其所有邻居方格所包含的雷数,应用该信息可以帮助你扫雷。 要求细节: (1) 能够打开一个方格,一个已打开的方格不能再关闭。 (2) 能够标记一个方格,标记方格的含义是对该方格有雷的预测,当一个方格标记后该方格不能被打开,只能执行取消标记的操作,取消标记后才能被打开。 (3) 合理分配各个操作的按键,以及各方格各种状态如何合理显示。 基本要求: 能够给出游戏结果(输,赢,剩余雷数,用掉的时间按秒计)。 游戏界面最好图形化,否则一定要有清楚的字符界面。 输入: 用户鼠标左键点击界面格子打开格子,鼠标右键点击界面格子进行标记。 输出: 界面上用户点击的格子打开,计时器开始计时并显示在界面上。如果不是雷,则显示格子周围格子数目,如果数目是0,则自动打开周围雷数为0的格子,如果是雷,游戏结束。当用户标记一个格子,对应格子显示被标记符号,同时界面显示的剩余雷数减1。当扫完所有雷,玩家获胜,游戏结束。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值