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

幻想机械 2024-07-10 15:35:01 阅读 63

目录

一、游戏简介

二、整体框架

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

<code>#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数组的空间释放。

<code>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

<code>#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将界面规划得更工整,但是我不太会用。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。