Qt和C++实现可视化扫雷游戏

目录

一、游戏简介

二、整体框架

1.Qt设计师界面类:GameWindow

2.head.h和function.cpp

3.源文件main.cpp

4.Qt控件类:CustomButton

三、具体实现

1.界面设计

2.雷的布置

3.界面生成

4.实现扫雷

5.功能细化

(1)界面整改

(2)尺寸更改

(3)其他功能

四、效果展示

五、完整代码

1.CustomButton类

2.GameWindow类

3.head.h、function.cpp、main.cpp

4.gamewindow.ui 


一、游戏简介

扫雷游戏大家应该都玩过,就是给你一个棋盘,让你点击棋盘上的格子,如果格子下有雷则游戏失败,否则显示一个数字,表示周围八格内含雷的数量,然后根据数字来推断哪些格子是安全的。当所有无雷的方格都被排查了,游戏胜利。

接下来,让我们用Qt和c++实现一个仿制版扫雷游戏。

二、整体框架

1.Qt设计师界面类:GameWindow

为了实现扫雷游戏的可视化界面,我们仿照原版游戏,在项目中添加Qt设计师界面类GameWindow(继承自QWidget)作为游戏的主界面。因为原版也只有一个界面,所以只需添加一个Qt界面类,便足以实现游戏全部的功能。

该类带来了gamewindow.h、gamewindow.cpp、gamewindow.ui三个文件,分别用于类的定义、类成员函数的具体实现、界面的图形化设计。

2.head.h和function.cpp

为了实现完整的游戏项目,我们还需要在类GameWindow的外部定义一些函数。因此添加源文件function.cpp用于包含这些函数的实现方法,并添加头文件head.h来进行库文件的包含和函数的声明,以便于在多个源文件中共享这些函数。

此外,项目中还可能会用到一些全局变量,为了实现源文件之间的共享,同样需要在head.h中进行这些变量的外部声明。

3.源文件main.cpp

编写main函数作为程序的入口。此外,我把全局变量的定义也放在了这个文件里。

4.Qt控件类:CustomButton

为什么需要定义这个类,后面会细讲。

三、具体实现

1.界面设计

由于图形化程序主要依靠用户的鼠标点击进行交互,因此在搭建项目时,我们先从游戏界面的设计入手,在GameWindow窗口中添加一些需要的控件。

原版扫雷游戏有一块大棋盘,左键点击棋盘中的方格即可进行排雷,右键点击则是标旗。考虑到方格具有点击效果,我们在Qt中使用QPushButton按钮来实现方格的功能,棋盘里有多少方格就创建多少个按钮,如16*16的棋盘则需要16*16个按钮。

而为了方便地给这么多按钮统一设计样式,这时候就需要定义一个CustomButton类了。我们让它继承自QPushButton,便能继承普通按钮的所有功能;而通过在类的构造函数中调用setStyleSheet方法,就可将所有这样的按钮设置为同一样式。

同时,还可为每个方格添加一个int类型的状态值condition,方便在后续的游戏过程中对方块的行为进行管理。

custombutton.h

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H

#include "head.h"
#include <QMouseEvent>

class CustomButton : public QPushButton
{
public:
    CustomButton(QWidget *parent = nullptr);
    void mousePressEvent(QMouseEvent *event);
    int condition = 0;
    //表示方格的状态 0.未排查未标记 1.已排查 2.已标记
};

#endif // CUSTOMBUTTON_H

custombutton.cpp

这里除了设置按钮的一般样式,还设置了左键点击(排雷)右键点击(标旗)后的样式改变效果。

需要注意的是,由于QPushButton类中好像没有专门用于右键点击事件的槽函数(至少我没找到),因此只能重写QPushButton的mousePressEvent事件,当判断为右键点击时就执行自定义的功能(标旗),左键点击则调用父类的方法。

样式表里需要用到旗子图标作为border-image,可以自己在网上找免费素材(自己画也行),然后添加到资源文件resourse.qrc中。

#include "custombutton.h"

CustomButton::CustomButton(QWidget *parent): QPushButton(parent)
{
    setStyleSheet("QPushButton{"
                  "background-color: #8FE1F3;"
                  "border-style: solid;"
                  "border-width: 2px;"
                  "border-color: #71B2D2;"
                  "}");
}

void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::RightButton)
    {
        // 如果是右键点击,则修改按钮的样式
        if (condition == 1) return; //如果按钮已被排查,则不可标旗
        if (condition == 0) //按钮未排查未标记
        {
            setStyleSheet("QPushButton{"
                          "background-color: #8FE1F3;"
                          "border-style: solid;"
                          "border-width: 2px;"
                          "border-color: #71B2D2;"
                          "border-image: url(:/icons/icons/note.png);"
                          "}");
             condition = 2; //改为标记态
        }
        else //按钮已被标记
        {
            setStyleSheet("QPushButton{"
                          "background-color: #8FE1F3;"
                          "border-style: solid;"
                          "border-width: 2px;"
                          "border-color: #71B2D2;"
                          "}");
            condition = 0; //改回未排查未标记状态
        }
    }
    else
    {
        // 如果是其它的鼠标按键事件,则调用父类的鼠标按下事件处理方法
        QPushButton::mousePressEvent(event);
    }
}
2.雷的布置

由于游戏里的雷分布在二维的棋盘上,因此我们定义一个char类型的二维数组(mine)来存储雷的信息,让棋盘的长和宽与数组的尺寸相对应。这样,棋盘上的每一个方格都对应mine中的一个元素,如果有雷,则存放'1',无雷则存放‘0’。

因为游戏中随时可能改变棋盘的长宽,为了方便改变数组的大小,我们使用二维动态数组,将数组的行(ROW)列(COL)定义为全局变量,这样在程序运行过程中也可进行大小的调整。

有了mine数组后,理论上棋盘的信息就完全确定下来了。但实际游戏里,棋盘上显示的信息却不能直接靠mine数组来生成。

回顾一下原版游戏:鼠标没按下时,方格是空白的,表示尚未进行排雷;左键点击后,若方格下不是雷,则显示一个数字,表示该方格周围八格的含雷数量;若是雷,则游戏失败,同时棋盘上显示出全部雷和数字的信息。

因此,我们可以再定义一个char类型的二维数组(showl)(ps:本来用的show作为数组名,表示该数组存放棋盘的实际显示信息,但似乎会与Qt某个类的自带函数重名而报错)。用‘*’将数组初始化,表明该方格还未被排查;如果某个元素对应的方格被右键点击,若不是雷,则在mine中查询该方块周围八格的含雷数,并在showl中将元素值改为数字。

在main.cpp中定义的全局变量

char **mine;
char **showl;

int ROW = 9;
int COL = 9;
//棋盘实际的行和列
int ROWS = ROW + 2;
int COLS = COL + 2;
//用于存放棋盘信息的数组的行和列
//因为扫雷时要对周围八格进行检索,为了使边缘的方格行为与内部一致,在数组棋盘的外围加一圈空白
int EASY_COUNT = 10;//雷的数量
int NOT_MINE = ROW * COL - EASY_COUNT;//不含雷的方格数量

int not_mine = 0;// 游戏中已排查的无雷方格数量

function.cpp中棋盘的初始化和雷的布置

//棋盘的初始化,set表示用于填充的值
void InitBoard(char**& board, int rows, int cols, char set)
{
    board = new char*[rows];
    for (int i=0;i<rows;i++)
        board[i] = new char[cols];
    for (int i = 0;i < rows; i++)
        for (int j = 0; j < cols; j++)
        {
            board[i][j] = set;
        }
}

//布置雷
void SetMine(char **board, int row, int col)
{
    int count = EASY_COUNT;
    while (count)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;
        }
    }
}
3.界面生成

当然,扫雷时仅仅对数组进行操作是不够的,毕竟数组是储存在内存中的变量,对玩家不可见,真正可进行交互的只是图形化的按钮棋盘。为了将按钮的行为与数组的行为连接到一起,我们需要进一步对按钮的左键点击事件进行处理,这需要用到Qt的信号-槽机制

而由于这里有多个按钮,每个按钮的点击行为都相同,所以可使用Qt 的信号-槽映射机制(Signal-Slot Mapping),将每个按钮的点击信号都连接到同一个槽函数(OnButtonClicked)上。

为了得知用户所点击的按钮具体是哪一个按钮,可以在GameWindow类中添加CustomButton*类型的QVector数组buttons作为成员,在buttons中存放所有按钮的指针,以下标来确定每一个按钮。

gamewindow.h

public:
    QVector<CustomButton *> buttons;

gamewindow.cpp

#include "gamewindow.h"
#include "ui_GameWindow.h"
#include <QSignalMapper>


GameWindow::GameWindow(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::GameWindow)
{
    ui->setupUi(this);
    createBoard();
    ui->label_2->hide();
    ui->label_3->hide();
    ui->label_4->hide();
    ui->spinBox->hide();
    ui->spinBox_2->hide();
    ui->spinBox_3->hide();
    ui->pushButton_7->hide();
}

//游戏中按钮棋盘的生成
void GameWindow::createBoard()
{
    for (int i=0;i<ROW;i++)
        for (int j=0;j<COL;j++)
        {
            CustomButton *button=new CustomButton(this);
            button->setGeometry(365-(double(COL)/2)*30+j*30,180+i*30,30,30);
            //为了控制棋盘生成的位置在窗口的中央,我联系窗口和棋盘的尺寸进行了计算
            button->show();
            buttons.append(button); //将创建的按钮指针添加到buttons数组中
            QSignalMapper *mapper=new QSignalMapper(this);
            mapper->setMapping(button,i*COL+j); //将按钮对应的下标作为mapper传递的参数
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
            connect(mapper, SIGNAL(mapped(int)), this, SLOT(onButtonClicked(int)));
        }
}
4.实现扫雷

注意:在原版游戏里,有时点击一个无雷的方格,只会显示出该格下面的数字,而有时则会显示出周围一大片数字。这是因为:如果点击的那个方格周围八格里至少有一个雷,那可直接将统计出的数字放进方格,让玩家来推断是周围哪几格中含雷;如果那个方格周围八格没有雷,要是仍按照刚才的做法,把数字0放进方格就结束,玩家自然知道周围八格也没有雷,但还得用鼠标一个个点击排查,徒增无用功。

所以,正确的处理方法是:如果被点击的某个方格周围八格没有雷,则程序应代替玩家对周围八格继续进行排查,直到排查到的某个方格周围八格有雷为止。这就需要用到函数递归的思想。

同时,为了美观,经过排查后的方格也应改变样式。是雷的方格还应换上地雷图标,同样自己去找,然后添加到项目的资源文件中。

扫雷过程中会涉及到游戏状态的改变(胜利或失败),失败即踩雷,调用一个自己写的reveal_All函数,将棋盘上所有信息显示出来;胜利则要通过已排查的方格数量来判断,这里用之前定义好的全局变量not_mine来更新排查过的方格数目,NOT_MINE保存所有无雷方格的数量,当not_mine与NOT_MINE相等时,判断为游戏胜利。

GameWindow.cpp中按钮点击的槽函数

void GameWindow::onButtonClicked(int index) //index为按钮在buttons中的下标
{
    if (buttons[index]->condition == 1 || buttons[index]->condition == 2)return;
    //如果按钮处于已排查或已标记状态,则点击不起作用
    int x=0,y=0;
    x=index/COL;
    y=index-x*COL+1;
    x++;
    //将按钮在buttons中的一维下标转化为棋盘中的二维下标
    if (mine[x][y] == '1') //踩到雷,则本局游戏结束,并调用reveal_All()显示棋盘全部信息
    {
        not_mine=0;
        this->ui->label_result->setText("对不起,你踩雷了。");
        this->ui->label_face->setStyleSheet("QLabel{"
                                            "border-image: url(:/icons/icons/sad.png);"
                                            "}");
        //这里会用到表情图标,之后会提到
        reveal_All(mine, showl, this, ROW, COL);
        return;
    }
    else //没踩雷,则调用Sweep函数进一步扫雷
    {
        Sweep(mine, showl, this, ROW, COL, x, y);
        if (not_mine == NOT_MINE) //判断为游戏胜利
        {
            not_mine=0; //将not_mine清空,方便进行下一局游戏
            this->ui->label_result->setText("恭喜你,扫雷成功!");
            this->ui->label_face->setStyleSheet("QLabel{"
                                                "border-image: url(:/icons/icons/cool.png);"
                                                "}");
            return;
        }
    }
}

function.cpp

//递归实现扫雷
void Sweep(char **mine, char **show, GameWindow* w,int row, int col, int x, int y)
{   //因为要在GameWindow窗口上更改按钮状态,而Sweep不是其成员函数,所以需要传递窗口指针过去
    int index=y-1+(x-1)*COL; //将二维下标转化为一维下标
    if (show[x][y]!='*')return;
    int count = 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 - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - '0' * 8;
    show[x][y]=count+'0';
    w->buttons[index]->setText(QString::number(count));
    w->buttons[index]->setStyleSheet("QPushButton{"
                                     "font-size: 20px;"
                                     "background-color: #89CDEF;"
                                     "border-style: solid;"
                                     "border-width: 1px;"
                                     "border-color: #71B2D2;"
                                     "}");
    w->buttons[index]->condition = 1; //将按钮状态修改为已排查
    not_mine++;

    if (count == 0) //周围八格没有雷,则递归地对八格进行扫雷
    {
        Sweep(mine, show, w, row, col, x - 1, y - 1);
        Sweep(mine, show, w, row, col, x - 1, y);
        Sweep(mine, show, w, row, col, x - 1, y + 1);
        Sweep(mine, show, w, row, col, x, y - 1);
        Sweep(mine, show, w, row, col, x, y + 1);
        Sweep(mine, show, w, row, col, x + 1, y - 1);
        Sweep(mine, show, w, row, col, x + 1, y);
        Sweep(mine, show, w, row, col, x + 1, y + 1);
    }
}

//踩雷时,棋盘显示全部信息
void reveal_All(char **mine, char **show, GameWindow* w,int row, int col)
{
    for (int i=1;i<=row;i++)
        for (int j=1;j<=col;j++)
        {
            int index=j-1+(i-1)*COL;
            if (mine[i][j]=='1')
            {
                w->buttons[index]->condition = 1;
                w->buttons[index]->setStyleSheet("QPushButton{"
                                                 "background-color: #89CDEF;"
                                                 "border-image: url(:/icons/icons/mine.png);"
                                                 "border-style: solid;"
                                                 "border-width: 1px;"
                                                 "border-color: #71B2D2;"
                                                 "}");
            }
            else Sweep(mine, show, w, row, col, i, j);
        }
}
5.功能细化
(1)界面整改

再回去看一眼原版游戏,发现上面有五个选项可以重新选择棋盘的尺寸;此外,还有一个表情图标,用于标识游戏的状态(胜利、失败、进行中)

此外,玩家可能也需要在游戏中重置棋盘,或者选择退出游戏

因此,我们在这里对游戏的UI进行一个整体的美化,这可以方便地在Qt的设计界面里通过改变控件的样式表来实现。同时增添改变棋盘尺寸的按钮、重新开始的按钮、退出游戏的按钮(均使用QPushButton),再用QLabel来放置表情图标

这里我用的图标仍然来源于网上的免费素材。

(可能不算太美观,但我尽力了)

(2)尺寸更改

以“中级”(11*11的棋盘,15个雷)为示例,转到“中级”按钮的click槽函数,在该函数中实现按钮的功能。

由于旧棋盘的尺寸可能与新棋盘不一样,因此mine和showl数组的尺寸也需要更改。这里先清除掉旧的按钮棋盘(使用自定义的成员函数deleteBoard),然后将两个数组的空间全部释放,根据新的尺寸重新初始化,再重新生成新的按钮棋盘。之前定义好的ROW、COL、ROWS、COLS、EASY_COUNT、NOT_MINE这些全局变量在这里用于传递棋盘的尺寸和雷数量信息。

gamewindow.cpp中deleteBoard函数

遍历buttons,将每个按钮的空间释放,然后将buttons数组的空间释放。

void GameWindow::deleteBoard(int row_old, int col_old)
{
    for (int i=0;i<row_old*col_old;i++)
    {
        delete buttons[i];
    }
    QVector<CustomButton *> tmp;
    buttons.swap(tmp);
}

gamewindow.cpp中“中级”按钮的槽函数

void GameWindow::on_pushButton_2_clicked()
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 11;
    COL = 11;
    ROWS = 13;
    COLS = 13;
    EASY_COUNT = 15;
    NOT_MINE = ROW * COL - EASY_COUNT; 
    //因为初始化时会以全局变量为参数,所以在这里进行全局变量的修改
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

在实现“自定义”按钮时,我原本希望点击按钮能显示一个可输入行、列、雷数据的附属窗口,但尝试实现时发现很麻烦,所以索性直接在GameWindow主窗口上添加了三个spinBox控件和一个按钮,将它们放在了“自定义”的下方。这些新控件在构造时调用hide()方式进行隐藏,点击“自定义”后使用show()显示,再点一下又进行隐藏。为了指示隐藏和显示的状态,在GameWindow类中添加bool类型成员is_custom(是否要进行自定义)

gamewindow.cpp中“自定义”按钮的槽函数

void GameWindow::on_pushButton_5_clicked()
{
    if (is_custom)
    {
        ui->label_2->hide();
        ui->label_3->hide();
        ui->label_4->hide();
        ui->spinBox->hide();
        ui->spinBox_2->hide();
        ui->spinBox_3->hide();
        ui->pushButton_7->hide();
        is_custom = false;
        return;
    }
    ui->label_2->show();
    ui->label_3->show();
    ui->label_4->show();
    ui->spinBox->show();
    ui->spinBox_2->show();
    ui->spinBox_3->show();
    ui->pushButton_7->show();
    is_custom = true;
}

自定义下“确定”按钮的槽函数

这里要保证棋盘格子的数量多于雷的数量,否则无法触发棋盘的重新生成。

void GameWindow::on_pushButton_7_clicked()
{
    ui->label_5->setText("");
    int Column = ui->spinBox->value(); //列数据,即棋盘的长
    int Row = ui->spinBox_2->value(); //行数据,即棋盘的宽
    int Mine = ui->spinBox_3->value(); //雷的数量
    if ( Mine >= Column * Row)
    {
        ui->label_5->setText("雷的数量应该少于格子的数量!");
        return;
    }
    is_custom = false;
    //后面的操作与尺寸更改按钮的基本一样
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;

    COL = Column;
    ROW = Row;
    COLS = COL + 2;
    ROWS = ROW + 2;
    EASY_COUNT = Mine;
    NOT_MINE = ROW * COL - EASY_COUNT;
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");

    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    createBoard();
    ui->label_2->hide();
    ui->label_3->hide();
    ui->label_4->hide();
    ui->spinBox->hide();
    ui->spinBox_2->hide();
    ui->spinBox_3->hide();
    ui->pushButton_7->hide();
}
(3)其他功能

gamewindow.cpp中“重新开始”按钮的槽函数

和尺寸更改按钮的槽函数差不多,只不过不用修改ROW、COL等全局变量,重新生成数组和棋盘即可。

void GameWindow::on_pushButton_restart_clicked()
{
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    //由于棋盘的尺寸不变,无需释放数组的空间,调用Init进行初始化即可
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    deleteBoard(ROW, COL);
    createBoard();
}

gamewindow.cpp中“退出游戏”按钮的槽函数

void GameWindow::on_pushButton_quit_clicked()
{
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    deleteBoard(ROW, COL);
    QApplication::quit();
}

 另外,为了在Qt中能打出中文,还需要在项目文件.pro中加上这样一段代码:

minesweeper.pro

msvc {
    QMAKE_CFLAGS += /utf-8
    QMAKE_CXXFLAGS += /utf-8
}

四、效果展示

五、完整代码

1.CustomButton类

custombutton.h

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H

#include "head.h"
#include <QMouseEvent>

class CustomButton : public QPushButton
{
public:
    CustomButton(QWidget *parent = nullptr);
    void mousePressEvent(QMouseEvent *event);
    int condition = 0;
    //表示方格的状态 0.未排查未标记 1.已排查 2.已标记
};

#endif // CUSTOMBUTTON_H

custombutton.cpp

#include "custombutton.h"

CustomButton::CustomButton(QWidget *parent): QPushButton(parent)
{
    setStyleSheet("QPushButton{"
                  "background-color: #8FE1F3;"
                  "border-style: solid;"
                  "border-width: 2px;"
                  "border-color: #71B2D2;"
                  "}");
}

void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::RightButton)
    {
        // 如果是右键点击,则修改按钮的样式
        if (condition == 1) return;
        if (condition == 0)
        {
            setStyleSheet("QPushButton{"
                          "background-color: #8FE1F3;"
                          "border-style: solid;"
                          "border-width: 2px;"
                          "border-color: #71B2D2;"
                          "border-image: url(:/icons/icons/note.png);"
                          "}");
             condition = 2;
        }
        else
        {
            setStyleSheet("QPushButton{"
                          "background-color: #8FE1F3;"
                          "border-style: solid;"
                          "border-width: 2px;"
                          "border-color: #71B2D2;"
                          "}");
            condition = 0;
        }
    }
    else
    {
        // 如果是其它的鼠标按键事件,则调用父类的鼠标按下事件处理方法
        QPushButton::mousePressEvent(event);
    }
}
2.GameWindow类

gamewindow.h

#ifndef GAMEWINDOW_H
#define GAMEWINDOW_H

#pragma execution_character_set("utf-8")

#include <QWidget>
#include "head.h"
#include "custombutton.h"

namespace Ui {
class GameWindow;
}

class GameWindow : public QWidget
{
    Q_OBJECT

public:
    explicit GameWindow(QWidget *parent = nullptr);
    ~GameWindow();
    QVector<CustomButton *> buttons;
    bool is_custom = false;
    void createBoard();
    void GameWindow::deleteBoard(int row_old, int col_old);

public slots:
    void onButtonClicked(int index);
private slots:
    void on_pushButton_restart_clicked();

    void on_pushButton_quit_clicked();

    void on_pushButton_2_clicked();

    void on_pushButton_clicked();

    void on_pushButton_6_clicked();

    void on_pushButton_3_clicked();

    void on_pushButton_4_clicked();

    void on_pushButton_5_clicked();

    void on_pushButton_7_clicked();

private:
    Ui::GameWindow *ui;
};

#endif // GAMEWINDOW_H

gamewindow.cpp

#include "gamewindow.h"
#include "ui_GameWindow.h"
#include <QSignalMapper>


GameWindow::GameWindow(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::GameWindow)
{
    ui->setupUi(this);
    createBoard();
    ui->label_2->hide();
    ui->label_3->hide();
    ui->label_4->hide();
    ui->spinBox->hide();
    ui->spinBox_2->hide();
    ui->spinBox_3->hide();
    ui->pushButton_7->hide();
}

void GameWindow::createBoard()
{
    for (int i=0;i<ROW;i++)
        for (int j=0;j<COL;j++)
        {
            CustomButton *button=new CustomButton(this);
            button->setGeometry(365-(double(COL)/2)*30+j*30,180+i*30,30,30);
            //为了控制棋盘生成的位置在窗口的中央,我联系窗口和棋盘的尺寸进行了计算
            button->show();
            buttons.append(button);
            QSignalMapper *mapper=new QSignalMapper(this);
            mapper->setMapping(button,i*COL+j);
            connect(button, SIGNAL(clicked()), mapper, SLOT(map()));
            connect(mapper, SIGNAL(mapped(int)), this, SLOT(onButtonClicked(int)));
        }
}

void GameWindow::deleteBoard(int row_old, int col_old)
{
    for (int i=0;i<row_old*col_old;i++)
    {
        delete buttons[i];
    }
    QVector<CustomButton *> tmp;
    buttons.swap(tmp);
}

void GameWindow::onButtonClicked(int index) //棋盘按钮的点击
{
    if (buttons[index]->condition == 1 || buttons[index]->condition == 2)return;
    int x=0,y=0;
    x=index/COL;
    y=index-x*COL+1;
    x++;
    if (mine[x][y] == '1')
    {
        not_mine=0;
        this->ui->label_result->setText("对不起,你踩雷了。");
        this->ui->label_face->setStyleSheet("QLabel{"
                                            "border-image: url(:/icons/icons/sad.png);"
                                            "}");
        reveal_All(mine, showl, this, ROW, COL);
        return;
    }
    else
    {
        Sweep(mine, showl, this, ROW, COL, x, y);
        if (not_mine == NOT_MINE)
        {
            not_mine=0;
            this->ui->label_result->setText("恭喜你,扫雷成功!");
            this->ui->label_face->setStyleSheet("QLabel{"
                                                "border-image: url(:/icons/icons/cool.png);"
                                                "}");
            return;
        }
    }
}

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


void GameWindow::on_pushButton_restart_clicked() //重新开始
{
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    deleteBoard(ROW, COL);
    createBoard();
}

void GameWindow::on_pushButton_quit_clicked() //退出游戏
{
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    deleteBoard(ROW, COL);
    QApplication::quit();
}

void GameWindow::on_pushButton_2_clicked() //“中级”按钮
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 11;
    COL = 11;
    ROWS = 13;
    COLS = 13;
    EASY_COUNT = 15;
    NOT_MINE = ROW * COL - EASY_COUNT;
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

void GameWindow::on_pushButton_clicked() //“基础”按钮
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 9;
    COL = 9;
    ROWS = 11;
    COLS = 11;
    EASY_COUNT = 10;
    NOT_MINE = ROW * COL - EASY_COUNT;
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

void GameWindow::on_pushButton_6_clicked() //“极简”按钮
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 4;
    COL = 4;
    ROWS = 6;
    COLS = 6;
    EASY_COUNT = 5;
    NOT_MINE = ROW * COL - EASY_COUNT;
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

void GameWindow::on_pushButton_3_clicked() //“专家”按钮
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 11;
    COL = 16;
    ROWS = 13;
    COLS = 18;
    EASY_COUNT = 25;
    NOT_MINE = ROW * COL - EASY_COUNT;
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

void GameWindow::on_pushButton_4_clicked() //“满屏”按钮
{
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;
    ROW = 11;
    COL = 22;
    ROWS = 13;
    COLS = 24;
    EASY_COUNT = 30;
    NOT_MINE = ROW * COL - EASY_COUNT;
    NOT_MINE = ROW * COL - EASY_COUNT;
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");
    createBoard();
}

void GameWindow::on_pushButton_5_clicked() //“自定义”按钮
{
    if (is_custom)
    {
        ui->label_2->hide();
        ui->label_3->hide();
        ui->label_4->hide();
        ui->spinBox->hide();
        ui->spinBox_2->hide();
        ui->spinBox_3->hide();
        ui->pushButton_7->hide();
        is_custom = false;
        return;
    }
    ui->label_2->show();
    ui->label_3->show();
    ui->label_4->show();
    ui->spinBox->show();
    ui->spinBox_2->show();
    ui->spinBox_3->show();
    ui->pushButton_7->show();
    is_custom = true;
}

void GameWindow::on_pushButton_7_clicked() //自定义下的“确定”按钮
{
    ui->label_5->setText("");
    int Column = ui->spinBox->value();
    int Row = ui->spinBox_2->value();
    int Mine = ui->spinBox_3->value();
    if ( Mine >= Column * Row)
    {
        ui->label_5->setText("雷的数量应该少于格子的数量!");
        return;
    }
    is_custom = false;
    deleteBoard(ROW, COL);
    for (int i = 0; i < ROWS; ++i)
    {
        delete[] mine[i];
        delete[] showl[i];
    }
    delete[] mine;
    delete[] showl;

    COL = Column;
    ROW = Row;
    COLS = COL + 2;
    ROWS = ROW + 2;
    EASY_COUNT = Mine;
    NOT_MINE = ROW * COL - EASY_COUNT;
    not_mine = 0;
    ui->label_result->setText("");
    ui->label_face->setStyleSheet("border-image: url(:/icons/icons/smile.png);");

    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    SetMine(mine, ROW, COL);
    createBoard();
    ui->label_2->hide();
    ui->label_3->hide();
    ui->label_4->hide();
    ui->spinBox->hide();
    ui->spinBox_2->hide();
    ui->spinBox_3->hide();
    ui->pushButton_7->hide();
}
3.head.h、function.cpp、main.cpp

head.h

#define _CRT_SECURE_NO_WARNINGS 1

extern int ROW;
extern int COL;
extern int ROWS;
extern int COLS;
extern int EASY_COUNT;
extern int NOT_MINE;

#include <QDebug>
#include <QPushButton>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

extern int not_mine;
extern char **mine;
extern char **showl;

class GameWindow;

void InitBoard(char **&board, int rows, int cols, char set);
void SetMine(char **board, int row, int col);
void Sweep(char **mine, char **showl, GameWindow* w, int row, int col, int x, int y);
void reveal_All(char **mine, char **show, GameWindow* w,int row, int col);

function.cpp

#include "head.h"
#include "gamewindow.h"

//棋盘的初始化,set表示用于填充的值
void InitBoard(char**& board, int rows, int cols, char set)
{
    board = new char*[rows];
    for (int i=0;i<rows;i++)
        board[i] = new char[cols];
    for (int i = 0;i < rows; i++)
        for (int j = 0; j < cols; j++)
        {
            board[i][j] = set;
        }
}

//布置雷
void SetMine(char **board, int row, int col)
{
    int count = EASY_COUNT;
    while (count)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;
        }
    }
}

//递归实现扫雷
void Sweep(char **mine, char **show, GameWindow* w,int row, int col, int x, int y)
{
    int index=y-1+(x-1)*COL;
    if (show[x][y]!='*')return;
    int count = 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 - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - '0' * 8;
    show[x][y]=count+'0';
    w->buttons[index]->setText(QString::number(count));
    w->buttons[index]->setStyleSheet("QPushButton{"
                                     "font-size: 20px;"
                                     "background-color: #89CDEF;"
                                     "border-style: solid;"
                                     "border-width: 1px;"
                                     "border-color: #71B2D2;"
                                     "}");
    w->buttons[index]->condition = 1;
    not_mine++;
    qDebug()<< not_mine;

    if (count == 0)
    {
        Sweep(mine, show, w, row, col, x - 1, y - 1);
        Sweep(mine, show, w, row, col, x - 1, y);
        Sweep(mine, show, w, row, col, x - 1, y + 1);
        Sweep(mine, show, w, row, col, x, y - 1);
        Sweep(mine, show, w, row, col, x, y + 1);
        Sweep(mine, show, w, row, col, x + 1, y - 1);
        Sweep(mine, show, w, row, col, x + 1, y);
        Sweep(mine, show, w, row, col, x + 1, y + 1);
    }
}

//踩雷时,棋盘应显示全部信息
void reveal_All(char **mine, char **show, GameWindow* w,int row, int col)
{
    for (int i=1;i<=row;i++)
        for (int j=1;j<=col;j++)
        {
            int index=j-1+(i-1)*COL;
            if (mine[i][j]=='1')
            {
                w->buttons[index]->condition = 1;
                w->buttons[index]->setStyleSheet("QPushButton{"
                                                 "background-color: #89CDEF;"
                                                 "border-image: url(:/icons/icons/mine.png);"
                                                 "border-style: solid;"
                                                 "border-width: 1px;"
                                                 "border-color: #71B2D2;"
                                                 "}");
            }
            else Sweep(mine, show, w, row, col, i, j);
        }
}

main.cpp

#include "gamewindow.h"
#include <QApplication>
#include "head.h"

char **mine;
char **showl;

int ROW = 9;
int COL = 9;
//棋盘实际的行和列
int ROWS = ROW + 2;
int COLS = COL + 2;
//用于存放棋盘信息的数组的行和列
//因为扫雷时要对周围八格进行检索,为了使边缘的方格行为与内部的一致,在数组棋盘的外围加一圈空白
int EASY_COUNT = 10;//雷的数量
int NOT_MINE = ROW * COL - EASY_COUNT;//不含雷的方格数量

int not_mine = 0;// 已排查的无雷方格数量

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    srand((unsigned int)time(NULL));
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(showl, ROWS, COLS, '*');
    //棋盘的初始化
    SetMine(mine, ROW, COL);//雷的布置
    GameWindow *w=new GameWindow;
    w->show();

    return a.exec();
}
4.gamewindow.ui 

可以使用LayoutSpacer将界面规划得更工整,但是我不太会用。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值