用Qt实现扫雷
发布日期:2021-05-14 09:10:42 浏览次数:19 分类:精选文章

本文共 4381 字,大约阅读时间需要 14 分钟。

通过一个经典的游戏——扫雷,我对Qt的认识得到了进一步提升。以下是基于Qt的扫雷游戏开发过程中的关键技术和实现细节。

一、游戏基本组件

扫雷游戏的核心组件是雷区,即游戏中的每个小方格。这些方格可以看作是图元,图元的状态由多个参数决定,包括是否被挖掘、是否被标记等。鼠标点击某个图元会触发其鼠标函数,从而改变其状态。

图元类myItem

图元类myItem双继承于QObject和QGraphicsPixmapItem。通过将图元放入场景QGraphicsScene中,并设置UI组件QGraphicsView的场景,才能显示出一系列图元。

class myItem : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
//省略其他函数和信号
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
private:
int row; // 行号
int col; // 列号
bool swept = false; // 是否已被挖掘
bool mine = false; // 是否有地雷
bool mark = false; // 是否有标记
bool stepMine = false; // 是否踩雷
int numOfMines = 0; // 周围地雷个数
};

鼓励信号机制

当左击一个没有地雷且周围地雷数为0的方格时,会自动打开周围所有格子。这个过程通过递归实现。

void myItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
if (event->buttons() == (Qt::LeftButton | Qt::RightButton)) {
if ((swept && numOfMines != 0) || (!swept && mark)) {
emit doubleClickSignal(row, col);
}
} else if (event->button() == Qt::LeftButton) {
simulateLeftClick();
} else if (event->button() == Qt::RightButton) {
if (!swept) {
setMark(!mark);
emit markChangedSignal();
}
}
emit checkSignal(); // 检测游戏是否成功
}

二、场景类myScene

场景类myScene包含一个myItem类对象指针的二维数组,用于管理所有图元。

class myScene : public QObject, public QGraphicsScene {
//省略其他函数和信号
private:
static const int MAX_HEIGHT = 24;
static const int MAX_WIDTH = 30;
myItem *items[MAX_WIDTH][MAX_HEIGHT];
int mine = 10; // 地雷数
int height = 9; // 地图宽高
int width = 9; // 地图宽高
};

三、核心功能findBlock()

findBlock()函数是扫雷的核心逻辑。当左击一个没有地雷且周围地雷数为0的方格时,会自动打开周围所有方格。

void myScene::findBlocks(int row, int col) {
items[row][col]->setSwept(true);
if (items[row][col]->getNumOfMines() != 0) return;
int nx, ny;
int dx[8] = { -1, -1, -1, 0, 0, 1, 1, 1 };
int dy[8] = { -1, 0, 1, -1, 1, -1, 0, 1 };
for (int i = 0; i < 8; ++i) {
nx = row + dx[i];
ny = col + dy[i];
if (0 <= nx && nx < width && 0 <= ny && ny < height) {
if (!items[nx][ny]->isSwept()) {
findBlocks(nx, ny);
}
}
}
}

四、界面细节

1. 设置场景大小与视图显示一致

scene的大小由添加的图元大小决定。每个图元大小为20x20,scene大小为20x20。当scene大小与view大小接近时,设置view的大小略大于scene即可。

2. 去掉滚动框

this->ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
this->ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

3. 界面布局

不建议使用布局管理,直接使用setGeometry和setFixedSize设置位置和固定大小即可。

4. 自定义地图设置

写一个自定义函数customSet(int width, int height, int mine),用于方便调用。

void MainWindow::on_actionPrimary_triggered() {
customSet(9, 9, 10);
}
void MainWindow::on_actionMid_triggered() {
customSet(16, 16, 40);
}
void MainWindow::on_actionSenior_triggered() {
customSet(30, 16, 99);
}

5. 数字显示屏

设计一个numItem图元类,用于显示3位数字。通过调用numScene类即可实现双数字显示。

void numItem::setNum(int a) {
if (a == 0) {
setPixmap(QPixmap(":/num/pic/num0.png"));
} else if (a == 1) {
setPixmap(QPixmap(":/num/pic/num1.png"));
} else if (a == 2) {
setPixmap(QPixmap(":/num/pic/num2.png"));
} else if (a == 3) {
setPixmap(QPixmap(":/num/pic/num3.png"));
} else if (a == 4) {
setPixmap(QPixmap(":/num/pic/num4.png"));
} else if (a == 5) {
setPixmap(QPixmap(":/num/pic/num5.png"));
} else if (a == 6) {
setPixmap(QPixmap(":/num/pic/num6.png"));
} else if (a == 7) {
setPixmap(QPixmap(":num/pic/num7.png"));
} else if (a == 8) {
setPixmap(QPixmap(":num/pic/num8.png"));
} else if (a == 9) {
setPixmap(QPixmap(":num/pic/num9.png"));
}
}
void numScene::setNumer(int num) {
if (num >= 0) {
this->clear();
int hundred, ten, one;
hundred = num / 100;
ten = (num % 100) / 10;
one = num % 10;
numItem *hunItem, *tenItem, *oneItem;
hunItem = new numItem;
tenItem = new numItem;
oneItem = new numItem;
hunItem->setPos(1, 0);
tenItem->setPos(20, 0);
oneItem->setPos(39, 0);
hunItem->setNum(hundred);
tenItem->setNum(ten);
oneItem->setNum(one);
this->addItem(hunItem);
this->addItem(tenItem);
this->addItem(oneItem);
}
}

通过这个经典的小游戏,我对Qt的理解得到了显著提升。从图元类到场景类,从信号机制到界面优化,每一个环节都让我对Qt的强大功能有了更深的体会。这次项目不仅让我掌握了扫雷游戏的开发技巧,更让我对 Qt 的信号与槽机制、场景与图元的管理有了更深入的理解,为后续的GUI开发打下了坚实的基础。

上一篇:2个fetch顺序实行
下一篇:五子棋ai:极大极小搜索和α-β剪枝算法的思想和实现(qt和c++)(四)算杀模块的简单实现

发表评论

最新留言

很好
[***.229.124.182]2025年05月02日 20时04分51秒