扫雷游戏大家应该都玩过,就是给你一个棋盘,让你点击棋盘上的格子,如果格子下有雷则游戏失败,否则显示一个数字,表示周围八格内含雷的数量,然后根据数字来推断哪些格子是安全的。当所有无雷的方格都被排查了,游戏胜利。
接下来,让我们用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.功能细化
再回去看一眼原版游戏,发现上面有五个选项可以重新选择棋盘的尺寸;此外,还有一个表情图标,用于标识游戏的状态(胜利、失败、进行中)。
此外,玩家可能也需要在游戏中重置棋盘,或者选择退出游戏。
因此,我们在这里对游戏的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
}
四、效果展示
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
可以使用Layout和Spacer将界面规划得更工整
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved