基于Qt的扫雷游戏

本游戏是模仿经典游戏扫雷的玩法写成

文章目录


前言

基于Qt的经典扫雷小程序;


一、软件描述

本游戏的所有代码都是基于Qt的编译环境工作;

简单演示一下程序效果:

              


二、基本框架

1.基类选择:MainWindow

MainWindow主要进行游戏各项操作;

MainWindow中通过对菜单栏的设置,得到想要的功能;例如新游戏,更改难度等;

MainWindow中通过PaintEvent绘图事件对游戏页面的绘制;例如游戏网格、数字等等;

MainWindow中通过mousePressEvent鼠标点击事件的重写,执行游戏功能;例如左击等;

通过构建类,构建第二页面,来选择新的游戏难度;


三、代码部分

1.类和函数的声明、头文件的声明 gamewindow.h

#ifndef GAMEWINDOW_H
#define GAMEWINDOW_H

#include "change_game.h"
#include <QDebug>
#include <QMainWindow>
#include <QMenu>        //菜单头文件
#include <QMenuBar>
#include <QAction>
#include <QToolBar>     //工作栏头文件
#include <QStatusBar>   //状态栏头文件
#include <QDockWidget>  //工作区头文件
#include <QLabel>
#include <QMessageBox>
#include <QTime>
#include <QPainter>     //绘图方法
#include <QPen>         //绘图工具、画笔
#include <QBrush>       //绘图工具、画刷
#include <QColor>
#include <QEvent>
#include <QMouseEvent>
#include <QAction>

class GameWindow : public QMainWindow
{
    Q_OBJECT

public:
    GameWindow(int line = 16, int column = 16, int minenumber = 40,QWidget *parent = 0);
    ~GameWindow();

    void Create();    //构建游戏板
    void Restart();                                   //重新布雷
    int NearFlag(int m, int n);                       //计算周围的旗子数
    bool LClick(int x, int y);                        //左击
    bool MidClick(int x, int y);                      //点击滑轮
    bool RClick(int x, int y);                        //右击
    bool Win();                                       //判断是否获胜
    void Over();                                      //游戏结束
    int** Map;                                        //地图
    int** flag;                                       //插旗的地图 11为没插旗子 88为插了旗子
    int MineNumber;                                   //总雷数
    int Line;                                         //列数
    int Column;                                       //行数
    int winflag;                                      //判断胜利 1为失败 2为胜利
    int RemainMine;                                   //剩余雷数
    bool first;                                       //判断是否为第一次,防止直接踩雷
    int offsetx = 5;                                  //左右两侧留的空间
    int offsety = 135;                                //上下两侧留的空间
    int MineButton = 30;                              //一个按键模块的大小
    void paintEvent(QPaintEvent *event);              //画图事件
    void mousePressEvent(QMouseEvent *event);         //鼠标点击事件

    QMenu *menugeme, *menuhelp, *menume;
    QAction *actnew, *actchange, *actclose, *acthelp, *actme;
    QLabel *lab1, *lab2;

    /******************************菜单选项槽函数**************************************/
    void actnew_fun();
    void actchange_fun();
    void actclose_fun();
    void acthelp_fun();
    void actme_fun();

};

#endif // GAMEWINDOW_H


2.对成员函数和槽函数的定义

构造函数、析构函数和绘图事件:

GameWindow::GameWindow(int line, int column, int minenumber, QWidget *parent)
    : QMainWindow(parent),
      Line(line),
      Column(column),
      MineNumber(minenumber)
{
    setFixedSize(MineButton*Line+offsetx*2, MineButton*Column+offsety);             //设置初始页面大小(普通难度);
    /***********************构建程序窗口页面**************************************/
    setWindowTitle("扫雷");                        //设置软件名(窗口名)
    setWindowIcon(QIcon(":/mine.png"));           //设置窗口界面图标

    /************************构造菜单项******************************************/
    menugeme = menuBar()->addMenu("游戏");         //构建游戏菜单
    menuhelp = menuBar()->addMenu("帮助");
    menume = menuBar()->addMenu("关于");

    actnew = new QAction("新游戏");                //构造菜单项
    actnew->setShortcut(Qt::CTRL + Qt::Key_O);    //设置快捷方式
    actnew->setStatusTip("新游戏");                //设置状态栏的提升信息

    actchange = new QAction("更改难度");
    actchange->setShortcut(Qt::CTRL + Qt::Key_C);
    actchange->setStatusTip("更改难度");

    actclose = new QAction("关闭游戏");
    actclose->setShortcut(Qt::CTRL + Qt::Key_Q);
    actclose->setStatusTip("关闭游戏");

    acthelp = new QAction("帮助");
    acthelp->setStatusTip("帮助");

    actme = new QAction("关于");
    actme->setStatusTip("关于");
    /******************************************添加菜单项***********************************/
    menugeme->addAction(actnew);                    //添加菜单项
    menugeme->addAction(actchange);
    menugeme->addAction(actclose);
    menuhelp->addAction(acthelp);
    menume->addAction(actme);
    /******************************************关联菜单信号与槽*****************************/
    connect(actnew, &QAction::triggered, this, &GameWindow::actnew_fun);
    connect(actchange, &QAction::triggered, this, &GameWindow::actchange_fun);
    connect(actclose, &QAction::triggered, this, &GameWindow::actclose_fun);
    connect(acthelp, &QAction::triggered, this, &GameWindow::acthelp_fun);
    connect(actme, &QAction::triggered, this, &GameWindow::actme_fun);
    /********************初始化二级指针,谨防段错误*************************/
    Map = NULL;
    flag = NULL;
    /***********************创建棋盘*************************************/
    Create();
}

GameWindow::~GameWindow()
{

}

/**********************************画图事件**********************************/
void GameWindow::paintEvent(QPaintEvent *event)
{
    /*****************************初始化素材图片*******************************/
    QPixmap nub0(":/0.png");
    QPixmap nub1(":/1.png");
    QPixmap nub2(":/2.png");
    QPixmap nub3(":/3.png");
    QPixmap nub4(":/4.png");
    QPixmap nub5(":/5.png");
    QPixmap nub6(":/6.png");
    QPixmap nub7(":/7.png");
    QPixmap nub8(":/8.png");
    QPixmap my_new(":/new.png");
    QPixmap mine(":/mine.png");
    QPixmap mine_red(":/mine_red.png");
    QPixmap my_flag(":/flag.png");
    /***********************初始化画笔*******************************/
    QPainter p(this);
    QPen pen(Qt::LinearGradientPattern);
    pen.setColor("gray");
    pen.setWidth(2);
    p.setPen(pen);
    int x_start, y_start, x_end, y_end;
    /*****************画横线**********************/
    x_start = offsetx;
    x_end = offsetx + Line*MineButton;
    for(y_start=0;y_start <= Column;y_start++)
    {
        y_end = y_start*MineButton + offsety -5;
        p.drawLine(QPoint(x_start, y_end), QPoint(x_end, y_end));
    }
    /*****************画竖线**********************/
    y_start = offsety -5 ;
    y_end = offsety - 5 + Column*MineButton;
    for(x_start=0; x_start <= Line;x_start++)
    {
        x_end = x_start*MineButton + offsetx;
        p.drawLine(QPoint(x_end, y_start), QPoint(x_end, y_end));
    }
    /************************************绘制初始棋盘*********************************************/
    for(int i=0; i<Line; i++)
    {
        for(int j=0; j<Column; j++)
            p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, my_new, 0, 0, 0, 0);

    }

    /*******************************绘制雷区***********************************/
    for(int i=0; i<Line; i++)
    {
        for(int j=0; j<Column; j++)
        {
            if( Map[i][j] == 88){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, mine_red, 0, 0, 0, 0);
            }else if(Map[i][j] == -1){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, mine, 0, 0, 0, 0);
            }else if(Map[i][j] == 0){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub0, 0, 0, 0, 0);
            }else if(Map[i][j] == 1){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub1, 0, 0, 0, 0);
            }else if(Map[i][j] == 2){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub2, 0, 0, 0, 0);
            }else if(Map[i][j] == 3){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub3, 0, 0, 0, 0);
            }else if(Map[i][j] == 4){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub4, 0, 0, 0, 0);
            }else if(Map[i][j] == 5){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub5, 0, 0, 0, 0);
            }else if(Map[i][j] == 6){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub6, 0, 0, 0, 0);
            }else if(Map[i][j] == 7){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub7, 0, 0, 0, 0);
            }else if(Map[i][j] == 8){
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, nub8, 0, 0, 0, 0);
            }else{
                continue;
            }
        }
    }
    /*********************************绘制插旗的情况************************************/
    for(int i=0; i<Line; i++)
    {
        for(int j=0; j<Column; j++)
        {
            if(flag[i][j] == 88)
            {
                p.drawPixmap(offsetx+i*MineButton+1, offsety+j*MineButton -5 +1, 28, 28, my_flag, 0, 0, 0, 0);
            }
        }
    }
    /********************************绘制剩余雷数量提示框***********************************/
    int remainMine = this->RemainMine;
    if(remainMine < 0 )
        remainMine = 0;
    QPainter painter(this);
    QFont font = painter.font();
    font.setPixelSize(48);
    pen.setColor("red");
    painter.setPen(pen);
    painter.setFont(font);
    const QRect rectangle = QRect(5, 55, 300, 60);
    QRect boundingRect;
    painter.drawText(rectangle, 0, tr("雷:""%1").arg(remainMine), &boundingRect);
    pen.setColor("gray");
    painter.setPen(pen);
    painter.drawRect(boundingRect.adjusted(0, 0, -pen.width(), -pen.width()));

}

重写鼠标点击事件:

/********************************重写鼠标点击事件************************************/
void GameWindow::mousePressEvent(QMouseEvent *event)
{
    int px = event->x();
    int py = event->y();
    int x = (px - offsetx) / MineButton;
    int y = (py - offsety + 5) / MineButton;
    if(event->button()==(Qt::MidButton))
    {
        if(MidClick(x, y))
            update();
    }
    else if(event->button()==Qt::LeftButton)
    {
        if(LClick(x,y))
            update();

    }
    else if(event->button()==Qt::RightButton)
    {
        if(RClick(x,y))
            update();
    }
}
/*********************************点击鼠标左键********************************/
bool GameWindow::LClick(int x, int y)
{
    if(x>=Line||y>=Column||x<0||y<0||Map[x][y]<10){    //越界或已打雷
        return false;
    }
    if(flag[x][y] == 88){                               //已经插旗就不能打开
        return false;
    }
    if(winflag==1||winflag==2){
        return false;
    }
    if(Map[x][y]>=101&&Map[x][y]<=108)                 //有数字
    {
        Map[x][y]-=100;                                //打开数字为1到8
        first=false;
        if(Win())                                     //判断是否胜利
            winflag = 2;
        Over();
        return true;
    }

    if(Map[x][y]==100)                                  //没有数字,递归打开周围的格子
    {
        Map[x][y] = 0;                                     //打开为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]==66)                              //踩雷
    {
        int i,j;
        if(first)                                  //第一步就踩雷
        {
            RemainMine--;       //消除当前位置的雷
            Map[x][y]=100;
            //改变周围的数字
            for(i=-1;i<2;i++)
            {
                for(j=-1;j<2;j++)
                {
                    if((i+x<Line)&&(j+y<Column)&&(i+x>=0)&&(j+y>=0)&&(i!=0||j!=0))
                    {
                        if(Map[i+x][j+y]>100)
                            Map[i+x][j+y]--;
                        else
                            Map[x][y]++;
                    }
                }
            }
            first=false;
            LClick(x,y);           //消除雷后点开
            return true;
        }
        Map[x][y] = 88;
        for(i=0;i<Line;i++)                       //显示出雷和插错的旗子
        {
            for(j=0;j<Column;j++)
            {
                if(Map[i][j]==66)
                    Map[i][j]=-1;                   //-1为雷
            }
        }
        winflag=1;
    }
    Over();
    return true;
}
/************************************点击鼠标滑轮*********************************/
bool GameWindow::MidClick(int x, int y)
{

    if(x>=Line||x<0||y>=Column||y<0||Map[x][y]>10)          //越界或未打开
        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);
    }
    if(Win())
        winflag = 2;
    Over();
    return true;
}
/***************************************点击鼠标右键****************************************/
bool GameWindow::RClick(int x, int y)
{
    if(Map[x][y] < 10)              //若已经被点开就不能插旗
        return false;
    else {
        if(flag[x][y]==11)          //若未插旗就插旗
        {
            flag[x][y] = 88;
            RemainMine--;
        }else{                      //若已插旗就拔掉
            flag[x][y] = 11;
            RemainMine++;
        }
    }
    if(Win())
        winflag = 2;
    Over();
    return true;
}

辅助判断函数:

/***********************判断周围有插了多少旗子*********************/
int GameWindow::NearFlag(int m, int n)
{
    if(m>=Line||m<0||n>=Column||n<0)                                        //越界
    {
        return -1;
    }
    int top=0;
    int i,j;
    for(i=-1;i<2;i++)
    {
        for(j=-1;j<2;j++)
        {
            if(m+i>=Line||n+j>=Column||m+i<0||n+j<0||flag[m+i][n+j]==11)   //越界或不是旗子就跳过
                continue;
            top++;
        }
    }
    return top;
}
/**************************************判断是否胜利**************************/
bool GameWindow::Win()
{
    for(int i=0; i<Line; i++)
    {
        for(int j=0; j<Column; j++)
        {
            if(Map[i][j] == 66)                 //判断所有雷上是否插旗
            {
                if(flag[i][j] == 11)
                    return false;
            }else if(Map[i][j]>9){              //判断所有数字是否已经点开
                return false;
            }
        }
    }
    return true;
}
/*****************************************判断游戏是否结束**********************/
void GameWindow::Over()
{
    if(winflag == 1){
        QMessageBox::warning(this, "游戏结束", "失败");
    }else if(winflag == 2){
        QMessageBox::warning(this, "游戏结束", "扫雷成功");
    }else {
        return;
    }
}

到此处基本功能已经实现,以下为附加功能函数;

构造菜单功能函数:

/***********************新游戏*************************/
void GameWindow::actnew_fun()
{
    GameWindow *w;
    w = new GameWindow(Line, Column, MineNumber);
    this->close();
    w->show();
}
/**************************选择新难度********************/
void GameWindow::actchange_fun()
{
    change_game *G;
    G = new change_game;
    this->close();
    G->show();
}
/**************************关闭游戏************************/
void GameWindow::actclose_fun()
{
    if(QMessageBox::Ok == QMessageBox::warning(this, "结束游戏", "请确认是否退出游戏", QMessageBox::Ok | QMessageBox::Cancel))
        this->close();
}
/*************************游戏帮助*************************/
void GameWindow::acthelp_fun()
{
    QMessageBox msgBox;
    msgBox.setText("该游戏为自制游戏,鼠标点击左键为翻开方块,鼠标点击右键为插旗,点击滑轮为探测,为了您的游戏体验,自定义列和行不得超过34*66,不得低于9*9,雷的数量不得超过25%");
    msgBox.exec();
}
/**************************关于作者*****************************/
void GameWindow::actme_fun()
{
    QMessageBox msgBox;
    msgBox.setText("此作品为CSDN博主「澂瀄.」出品,查看代码可前往CSDN搜索博主");
    msgBox.exec();
}

3.第二页面,只用于改变难度

第二页面效果展示:

 

功能代码:

/****************************第二页面 只用于改变难度********************************/
change_game::change_game(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::change_game)
{
    ui->setupUi(this);
}

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

void change_game::on_btn_1_clicked()
{
    GameWindow *w;
    w = new GameWindow(9, 9, 10);                //简单难度
    this->close();
    w->show();
}

void change_game::on_btn_2_clicked()
{
    GameWindow *w;
    w = new GameWindow(16, 16, 40);             //普通难度
    this->close();
    w->show();
}

void change_game::on_btn_3_clicked()
{
    GameWindow *w;
    w = new GameWindow(30, 16, 99);             //困难难度
    this->close();
    w->show();
}

void change_game::on_btn_4_clicked()
{
    GameWindow *w;
    w = new GameWindow(66, 34, 462);            //噩梦难度
    this->close();
    w->show();
}

void change_game::on_btn_5_clicked()
{                                                      //自定义难度
    int line = ui->edt_line->text().toInt();
    int column = ui->edt_column->text().toInt();
    int mine = ui->edt_mine->text().toInt();
    int over = line*column;
    if((line>=9) && (column>=9) && (line<=66) && (column<=34) && ((over/4) >= mine))
    {
        GameWindow *w;
        w = new GameWindow(line, column, mine);
        this->close();
        w->show();
    }else {
        QMessageBox::warning(this, "警告" , "非法输入! 为了您的游戏体验,自定义行列不得超过66*34,不得低于9*9,雷的数量不得超过25%");
    }
}

总结


以上就是对扫雷游戏的主要内容,本程序还有许多不足,大家感兴趣的可以广发奇想,写的帖子写得粗糙,希望对看帖子的你有所帮助,还希望各位大佬担待,诚心欢迎各位批评指正。

  • 13
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 要制作一个扫雷游戏图片,在Excel中可以通过以下步骤实现: 1. 打开Excel,创建一个新的工作表。 2. 在表格中选择合适的大小,可以根据实际需要自行调整。 3. 使用表格工具栏中的“绘图”功能,在工作表中绘制游戏地图的框架。你可以使用矩形工具来绘制游戏的每个方块。 4. 然后,使用其他绘图工具(如画笔、线条、填充)在方块中绘制雷和数字等游戏元素。可以使用不同的颜色和样式来区分不同类型的方块。 5. 如果需要,可以使用文本工具来添加数字或文字标签,用于显示雷和周围方块中的数字。 6. 当绘制完整的游戏地图后,可以根据自己的喜好进行美化。可以调整方块的大小、颜色、字体样式等等。 总结起来,要制作扫雷游戏图片,你只需要在Excel中利用表格和绘图工具绘制游戏地图的框架,然后使用其他绘图工具绘制游戏元素,如雷和数字。最后,可以美化图片以增加视觉效果。希望这些步骤对你有所帮助! ### 回答2: 要制作Excel中的扫雷游戏,首先需要了解扫雷游戏的基本规则和游戏界面。下面是制作扫雷游戏图片的步骤: 1. 打开Excel并创建一个新的工作表。 2. 使用绘图工具栏上的形状工具,绘制一个正方形或矩形,这将是游戏的主要游戏区域。 3. 在游戏区域中,使用线条工具或形状工具绘制水平和垂直的线条,将游戏区域分割成小方块。这些小方块的数量和大小应根据实际需要进行调整。 4. 在小方块中,使用绘图工具绘制雷的图标。可以使用闪电形状或任何其他形状来表示雷。 5. 使用Excel的插入图片功能,插入不同颜色或图案的旗子图标,用于标记玩家已经发现的雷。 6. 在游戏区域的边缘或其他适当位置,使用绘图工具绘制数字。这些数字将表示相邻方块中的雷的数量。 7. 使用填充颜色或纹理,给游戏区域、雷和数字方格添加一些装饰效果。 8. 可以通过在Excel中添加按钮,为游戏添加重置和退出功能。 9. 最后,根据需要调整游戏区域的大小和比例。 完成上述步骤后,你就可以在Excel中制作一个简单的扫雷游戏图片了。你可以根据实际需要进一步修改和完善游戏界面,添加其他功能,使得游戏更加有趣。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值