基于Qt开发的扫雷

github项目:https://github.com/Kamisama999/C_code

gitee项目:https://gitee.com/Kamisama_02/curriculum-design

大致效果

                    

                         图 1 初始界面                                                     图 2 胜利界面

框架结构

(1)MainWindow类

        MainWindow主要负责建立人机交互界面,例如:绘制扫雷界面,对鼠标事件进行响应等。

        MainWindow类通过信号和槽机制对使用者的操作进行响应和完成对应操作,使用者通过特定事件发出信号,由对应的槽函数进行接受,并实现相应操作。

        MainWindow类通过PaintEvent进行绘图,调用了绘图事件相关的头文件,再添加图片资源文件,最后通过调用绘图函数将图片资源绘制在窗口上。

        MainWindow类可以通过ui界面对窗口进行设置,可以增加菜单栏对游戏进行调整,改变游戏难度。

(2)MineMap游戏逻辑类

        MineMap主要负责扫雷游戏核心逻辑的实现,例如:随机生成地图,鼠标的操作,判定游戏的胜负等。

        类的实现:

class MineMap
{
public:
    MineMap();
    ~MineMap();
    void Create();              //布雷
    void Create(int row, int column, int number);
    void Restart();             //重新布雷
    int NearFlag(int m, int n);    //计算周围的旗子数
    int** Map;                   //地图
    int MineNumber;       //雷数
    int Row;                       //行
    int Column;                 //列
    bool RClick(int x, int y);      //右击
    bool LRClick(int x, int y);    //左右同时按
    bool LClick(int x, int y);       //左击
    bool Win();             //判断是否获胜
    int winflag;             //判断胜利 0为失败 2为胜利
    int RemainMine;   //剩余雷数
    bool first;             //判断是否为第一次,防止直接踩雷
    int timer;              //记录时间
};

游戏逻辑核心代码

1.生成地图空间

//创造Map空间
void MineMap::Create()
{
    int i;
    if(Map!=NULL)         //删除原本地图
    {
        for(i=0;i<Row;i++)
        {
            delete Map[i];
        }
        delete Map;
        Map=NULL;
    }
    Map=new int*[Row];
    for(i=0;i<Row;i++)
    {
        Map[i]=new int[Column];
    }
    Restart();     //生成雷和数字
}

//自定义创造Map空间大小
void MineMap::Create(int row,int column,int number)
{
    int i;
    if(Map!=NULL)         //删除原本地图
    {
        for(i=0;i<Row;i++)
        {
            delete Map[i];
        }
        delete Map;
        Map=NULL;
    }
    Row=row;                     //自定义行列数和炸弹数
    Column=column;
    MineNumber=number;
    Map=new int*[Row];
    for(i=0;i<Row;i++)
    {
        Map[i]=new int[Column];
    }
    Restart();     //生成雷和数字
}

2.初始化模块

//生成雷和数字
void MineMap::Restart()
{
    int i,j;
    //初始化
    for(i=0;i<Row;i++)
        for(j=0;j<Column;j++)
            Map[i][j] = 100;            //100为空白

    //随机布雷
    srand(time(NULL));
    for(i=0;i<MineNumber;i++)
    {
        int x=rand()%Row;
        int y=rand()%Column;
        if(Map[x][y]!=99)
            Map[x][y]=99;            //99为雷
        else
            i--;            //如果已经布雷就重新来一次
    }

    //生成雷周围的数字
    for(i=0;i<Row;i++)
    {
        for(j=0;j<Column;j++)
        {
            if(Map[i][j]==99)
            {
                int x,y;
                for(x=-1;x<2;x++)
                {
                    for(y=-1;y<2;y++)
                    {
                        // 越界或是雷就跳过
                        if(i+x>=Row||j+y>=Column||i+x<0||j+y<0||Map[i+x][j+y]==99)
                        {
                            continue;
                        }
                        Map[i+x][j+y]++;        //101到109为数字
                    }
                }
            }
        }
    }
    winflag=1;
    RemainMine=MineNumber;
    first=true;
    timer=0;
}

3.鼠标左击模块

bool MineMap::LClick(int x,int y)
{
    if(winflag==0||winflag==2)
        return false;

    //越界或已打开
    if(x>=Row||y>=Column||x<0||y<0||Map[x][y]<=90)
        return false;

    //有数字
    if(Map[x][y]>=101&&Map[x][y]<=108)
    {
        Map[x][y]-=100;    //打开数字为1到8
        first=false;
        Win();                       //判断是否胜利
        return true;
    }

    //没有数字,递归打开周围的格子
    if(Map[x][y]==100)
    {
        Map[x][y]-=100;       //打开为0
        LClick(x-1,y);
        LClick(x+1,y);
        LClick(x,y+1);
        LClick(x,y-1);
        LClick(x-1,y-1);
        LClick(x+1,y-1);
        LClick(x-1,y+1);
        LClick(x+1,y+1);
    }

    //踩雷
    if(Map[x][y]==99)
    {
        int i,j;
        if(first)       //第一步就踩雷
        {
            RemainMine--;       //消除当前位置的雷
            int t=0;
            //改变周围的数字
            for(i=-1;i<2;i++)
            {
                for(j=-1;j<2;j++)
                {
                    if((i+x<Row)&&(j+y)<Column&&(i+x>=0)&&(j+y>=0)&&(x||y))
                    {
                        if(Map[i+x][j+y]>99)
                            Map[i+x][j+y]--;
                        if(Map[i+x][j+y]==99)
                            t++;
                    }
                }
            }
            first=false;
            Map[x][y]=100+t;
            LClick(x,y);           //消除雷后点开
            return true;
        }

        //显示出雷和插错的旗子
        for(i=0;i<Row;i++)
        {
            for(j=0;j<Column;j++)
            {
                if(Map[i][j]==99)
                Map[i][j]=-1;                   //-1为雷
                if(Map[i][j]>49&&Map[i][j]<60)
                Map[i][j]=-2;                   //-2为插错的旗子
            }
        }
        winflag=0;
    }
    return true;
}

4.鼠标右击模块

bool MineMap::RClick(int x,int y)
{
    if(winflag==0||winflag==2)
        return false;
    if(x>=Row||x<0||y>=Column||y<0||Map[x][y]<40)
        return false;
    if(Map[x][y]>90)       //未打开
    {
        Map[x][y]-=50;
        RemainMine--;
    }
    else if(Map[x][y]>40&&Map[x][y]<60)    //已插旗
    {
        RemainMine++;
        Map[x][y]+=50;
    }
    return true;
}

5.左右键双击模块

//计算周围的旗子数
int MineMap::NearFlag(int x,int y)
{
    if(x>=Row||x<0||y>=Column||y<0)     //越界
    {
        return -1;
    }
    int n=0;
    int i,j;
    for(i=-1;i<2;i++)
    {
        for(j=-1;j<2;j++)
        {
            //越界或不是旗子就跳过
            if(x+i>=Row||y+j>=Column||x+i<0||y+j<0||Map[x+i][y+j]>60||Map[x+i][y+j]<40)
                continue;
            n++;
        }
    }
    return n;
}

//左右键同时按
bool MineMap::LRClick(int x,int y)
{
    if(x>=Row||x<0||y>=Column||y<0||Map[x][y]>40)   //越界或未打开
        return false;
    if(Map[x][y]==NearFlag(x,y))     //数字刚好和周围的旗子相等
    {                                                    //打开周围的格子
        LClick(x-1,y);
        LClick(x+1,y);
        LClick(x,y+1);
        LClick(x,y-1);
        LClick(x-1,y-1);
        LClick(x+1,y-1);
        LClick(x-1,y+1);
        LClick(x+1,y+1);
    }
    return true;
}

 绘制人机交互界面

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("扫雷");
    mineMap.Create();
    offsetx=5;
    offsety=30;
    setFixedSize(mineMap.Row*20+offsetx*2,mineMap.Column*20+offsety+46);
    runtime=new QTimer(this);
    connect(runtime,SIGNAL(timeout()),this,SLOT(on_sectime()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

//绘图事件
void MainWindow::paintEvent(QPaintEvent *)
{
    QPixmap nub(":/item1.bmp");
    QPixmap map(":/item2.bmp");
    QPixmap fame(":/item3.bmp");
    QPixmap face(":/item4.bmp");
    QPainter painter(this);

    //绘制上面的框架
    painter.drawPixmap( 0+offsetx, 0+offsety, fame, 0, 0, 70, 40);
    painter.drawPixmap(mineMap.Row * 10 - 20+offsetx, 0+offsety, fame, 80, 0, 40, 40);
    painter.drawPixmap(mineMap.Row * 20 - 70+offsetx, 0+offsety, fame, 130, 0, 70, 40);
    painter.drawPixmap(70+offsetx, 0+offsety, mineMap.Row * 10 - 90, 40, fame, 70, 0, 10, 40);
    painter.drawPixmap(mineMap.Row * 10 + 20+offsetx, 0+offsety, mineMap.Row * 10 - 90, 40, fame, 70, 0, 10, 40);
    painter.drawPixmap(mineMap.Row * 10 - 12+offsetx, 7+offsety, face, mineMap.winflag * 24, 0, 24, 24);

    //绘制雷区
    for(int i=0;i<mineMap.Row;i++)
    {
        for(int j=0;j<mineMap.Column;j++)
        {
            if(mineMap.Map[i][j]>=0&&mineMap.Map[i][j]<9)
            {
                painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,mineMap.Map[i][j]*20,0,20,20);
            }
            if(mineMap.Map[i][j]==-1)
                painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,9*20,0,20,20);
            if(mineMap.Map[i][j]>90)
                painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,10*20,0,20,20);
            if(mineMap.Map[i][j]>40&&mineMap.Map[i][j]<60)
                painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,11*20,0,20,20);
            if(mineMap.Map[i][j]==-2)
                painter.drawPixmap(i*20+offsetx,j*20+40+offsety,map,12*20,0,20,20);
        }
    }

    int remainMine=mineMap.RemainMine;
    int remainTime=mineMap.timer;

    //绘制剩余雷数
    if(remainMine<0)
        remainMine=0;
    painter.drawPixmap(6+offsetx, 5+offsety, nub, remainMine / 100 * 20, 0, 20, 28);
    if(remainMine>=100)
        remainMine%=100;
    painter.drawPixmap(26+offsetx, 5+offsety, nub, remainMine / 10 * 20, 0, 20, 28);
    painter.drawPixmap(46+offsetx, 5+offsety, nub, remainMine % 10 * 20, 0, 20, 28);

    //绘制时间
    if(remainTime>=1000)
        remainTime%=1000;
    painter.drawPixmap(mineMap.Row*20-66+offsetx, 5+offsety, nub, remainTime / 100 * 20, 0, 20, 28);
    if(remainTime>=100)
        remainTime%=100;
    painter.drawPixmap(mineMap.Row*20 - 46+offsetx, 5+offsety, nub, remainTime / 10 * 20, 0, 20, 28);
    painter.drawPixmap(mineMap.Row*20 - 26+offsetx, 5+offsety, nub, remainTime % 10 * 20, 0, 20, 28);
}

//鼠标按下事件
void MainWindow::mousePressEvent(QMouseEvent *event)
{
    int px=event->x()-offsetx;
    int py=event->y() -offsety;
    int x = (px) / 20;
    int y = (py) / 20 - 2;
    if(event->buttons()==(Qt::LeftButton|Qt::RightButton))
    {
        if(mineMap.LRClick(x, y))
            update();
    }
    else if(event->button()==Qt::LeftButton)
    {
        //点击表情图标重新开始
        if (px>mineMap.Row * 10 - 15 && px< mineMap.Row * 10 + 15 && py>4 && py < 34)
        {
            mineMap.Restart();
            update();
        }
        if(mineMap.LClick(x,y))
        {
            if (!(runtime->isActive() ))
                runtime->start(1000);
            update();
        }
        if (mineMap.winflag==0 || mineMap.winflag == 2)
        {
            if (runtime->isActive() )
                runtime->stop();
        }
    }
    else if(event->button()==Qt::RightButton)
    {
        if(mineMap.RClick(x,y))
            update();
    }
}

 PS:第一次写博客,希望多多包涵。图片资源上传有水印就不放了。

 参考博客:https://blog.csdn.net/u013407923/article/details/50472635

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值