第三次实训工作报告
设计题目
华南理工大学:植此青绿
本周工作小结:
本周我主要进行了以下工作:
- 创建地图:
- 生成了足够数量的地图,并成功地存储在指定的文件路径下。
- 创建关卡对应按钮(后期可能会改为拖拽)
- 创建4个点击按钮用于实现游戏对应的点击逻辑,后期考虑到用户的可玩性,会考虑改为拖拽。
- 实现核心算法:
- 边界判定:在玩家填充格子时,通过检查所选区域的底部行和右侧列来确保所选区域不超出拼图的边界。
- 地图破解:参考隐藏地图的信息,我实现了玩家正确填充格子的功能。当玩家填充的地图与隐藏地图完全匹配时,即视为破解成功。
环境与工具:
- 开发环境:Qt Creator 13.0.0
- 操作系统:Windows 11
前置工作
- 地图创建:
- 设计了不同的地图布局,确保游戏难度适中。
- 地图存储在了指定的文件路径 "C:/cat/qqzl.txt" 下。
- 核心算法设计:
- 详细设计了边界判定和地图破解的算法逻辑,为后续的编程工作提供了清晰的方向。
程序功能说明
各模块功能
- 选择网格大小:
根据玩家选择的网格大小更新游戏状态。
- 点击拼图格子:
根据当前选择的网格大小修改相应区域的数字。
后期预备开启功能:
- 开始游戏: 初始化计时器、隐藏地图和游戏区域。
- 撤销操作: 撤销上一步的修改。
- 提交游戏: 将游戏结果保存到文件中。
- 四个点击按钮的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| for (int i = 0; i < 4; ++i) { int gridSize =i+1;
QPushButton *button = new QPushButton(QString::number(gridSize) + "x" + QString::number(gridSize), this); button->setMinimumWidth(150); button->setMaximumWidth(150); button->setMinimumHeight(50); button->setMaximumHeight(50); connect(button, &QPushButton::clicked, this, [this, gridSize]() { on_GridSizeButton_clicked(gridSize); }); gridSizeLayout->addSpacing(50); gridSizeButtons[i] = button; gridSizeButtons[i]->setStyleSheet( "font-family: \"Microsoft YaHei\"; font-size: 28px; "); gridSizeLayout->addWidget(button); }
|
image-20240424223321584
- 地图有解判定:
- 边界判定:在填充格子时,需要确保所选区域不超出拼图的边界。这可以通过检查所选区域的底部行和右侧列来实现。
- 地图破解:根据隐藏地图的信息,玩家需要正确地填充格子。当玩家的填充与隐藏地图完全匹配时,即为破解成功。
- 点击拼图格子: 遍历点击的区域内的所有格子,将其数字增加1。
主要实现如下:
两个主要方法:一个用于判断点击以实现叠加,一个用于判断游戏是否胜利
on_PuzzleCell_clicked
方法
- 检查按钮点击状态:如果
isButtonClicked
标志变量为 false
,则直接返回,不处理格子点击事件。
- 检查点击区域:计算点击区域的底部行和右侧列,并检查它们是否超出拼图边界。
- 更新单个格子或多个格子:
- 如果
currentGridSize
为
1
,则更新单个点击的格子的文本。
- 否则,更新以点击格子为起点,大小为
currentGridSize
的区域内的所有格子的文本。
- 重置按钮点击状态:将
isButtonClicked
重置为 false
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| void LevelOnepa::on_PuzzleCell_clicked(QLabel *cell, int clickedRow, int clickedCol) { if (!isButtonClicked) { return; } int bottomRow = clickedRow + currentGridSize - 1; int rightCol = clickedCol + currentGridSize - 1;
if (bottomRow >= puzzleSize || rightCol >= puzzleSize) { QMessageBox::warning(this, tr("警告"), tr("您选择的区域超过了拼图边界!")); return; } if (currentGridSize == 1) { cell->setText(QString::number(cell->text().toInt() + 1)); } else { for (int row = clickedRow; row < std::min(clickedRow + currentGridSize, 6); ++row) { for (int col = clickedCol; col < std::min(clickedCol + currentGridSize, 6); ++col) { QLabel *targetCell = puzzleCells[row * puzzleSize + col]; targetCell->setText(QString::number(targetCell->text().toInt() + 1)); } } } isButtonClicked=false; }
|
这个方法处理改变拼图大小按钮点击的事件。它执行以下操作:
- 检查所有按钮是否已用完:检查所有按钮是否已达到其最大使用次数。
- 比较拼图和隐藏地图:调用
comparePuzzleWithHiddenMap
方法比较玩家拼图与隐藏地图。其中隐藏地图为一开始我存储的地图,用于与玩家操作后的地图进行比较。
- 检查游戏状态:
- 如果所有按钮都已用完并且拼图与隐藏地图匹配,游戏成功。
- 如果所有按钮都已用完但拼图与隐藏地图不匹配,游戏失败。
- 检查当前按钮是否已达最大使用次数:如果当前按钮已达到其最大使用次数,则直接返回。
- 设置当前网格大小:将
currentGridSize
设置为点击按钮的大小。
- 重置按钮点击状态:将
isButtonClicked
设置为 true
。
- 递增按钮使用计数:增加当前按钮的使用计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| void LevelOnepa::on_GridSizeButton_clicked(int size) { bool allButtonsUsed = true; for (int i = 1; i <= 4; ++i) { if (useCounts[i] < maxUseCounts[i]) { allButtonsUsed = false; break; } } bool puzzleMatchesHidden = comparePuzzleWithHiddenMap(); if(allButtonsUsed && puzzleMatchesHidden) { isGameFinished = true; showSuccessDialog(); timer->stop(); } else if(allButtonsUsed && !puzzleMatchesHidden) { isGameFinished = true; showFailureDialog(); timer->stop(); }
if (useCounts[size] >= maxUseCounts[size ]) { return; }
currentGridSize = size; isButtonClicked = true;
useCounts[size]++;
}
|
两个辅助方法:
mousePressEvent
方法
这个方法处理鼠标点击事件,特别是左键点击事件。当玩家在
puzzleGrid
区域内点击鼠标左键时,该方法会执行以下操作:
- 获取鼠标点击的位置:获取鼠标点击在
puzzleGrid
中的位置坐标。
- 检查点击位置:检查点击位置是否在
puzzleGrid
区域内,如果不在,则直接返回。
- 计算点击的单元格索引:根据
puzzleSize
(拼图大小)和 puzzleGrid
的尺寸计算出实际点击的单元格索引。
- 获取点击的单元格:找到对应索引的
cellWrapper
。
- 检查子标签:遍历
cellWrapper
中的所有子对象,查找被点击的 QLabel
。
- 处理点击事件:如果找到被点击的
QLabel
,则调用 on_PuzzleCell_clicked
方法处理点击事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| void LevelOnepa::mousePressEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton) { return; }
QPoint gridPos = event->pos();
if (!puzzleGrid->parentWidget()->rect().contains(gridPos)) { return; }
gridPos -= puzzleGrid->geometry().topLeft();
int cellWidth = puzzleGrid->geometry().width() / puzzleSize; int cellHeight = puzzleGrid->geometry().height() / puzzleSize;
int clickedRow = gridPos.y() / cellHeight; int clickedCol = gridPos.x() / cellWidth;
QWidget *cellWrapper = puzzleGrid->itemAt(clickedRow * puzzleSize + clickedCol)->widget();
if (cellWrapper) { QLabel *label = nullptr;
for (QObject *child : cellWrapper->children()) { label = qobject_cast<QLabel *>(child);
if (label && label->underMouse()) { break; } }
if (label) { on_PuzzleCell_clicked(label, clickedRow, clickedCol); } } }
|
comparePuzzleWithHiddenMap
方法
这个方法用于比较玩家地图与隐藏地图,确保它们是相同的。
- 遍历每个单元格:对于每一个单元格,它获取玩家地图和隐藏地图的值。
- 比较值:
- 当隐藏地图的值为
2
时,玩家地图的值必须是
0
或 2
。
- 当隐藏地图的值为
0
时,玩家地图的值必须是
0
。
- 当隐藏地图的值为
4
时,玩家地图的值必须是
4
。
- 当隐藏地图的值为
1
时,玩家地图的值必须是
1
或 3
。
如果有任何不匹配的情况,该方法将返回
false
,表示玩家地图与隐藏地图不匹配。如果所有的比较都通过了,该方法将返回
true
,表示玩家地图与隐藏地图匹配。
隐藏地图和用户地图的比较主要参照于游戏规则中对于各种块的定义和玩法,因游戏规则而异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| bool LevelOnepa::comparePuzzleWithHiddenMap() { for (int row = 0; row < puzzleSize; ++row) { for (int col = 0; col < puzzleSize; ++col) { QLabel *cell = puzzleCells[row * puzzleSize + col]; int playerValue = cell->text().toInt(); int hiddenValue = hiddenMap[row * puzzleSize + col]; if (hiddenValue == 2 && playerValue != 0 && playerValue != 2) { return false; } else if (hiddenValue == 0 && playerValue != 0) { return false; } else if (hiddenValue == 4 && playerValue != 4) { return false; } else if (hiddenValue == 1 && playerValue != 1 && playerValue != 3) { return false; } } } return true; }
|
初步效果如下:
image-20240424222900813
一些细枝末节但是也不可缺少的实现:
对图片和数字两个QLable的结合,也是在程序设计时候出现的第一个难关,起初我是将图片和数字都放在一个QLable里面,但数字总是离奇消失,无论怎么修改都没用,百般无奈下才得知QLable只能够设计一种属性,迫不得已通过布局加两个QLabel实现了地图布局的可视化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| for (int row = 0; row < puzzleSize; ++row) { for (int col = 0; col < puzzleSize; ++col) { QLabel *textCell = new QLabel(this); textCell->setFixedSize(70, 70); textCell->setStyleSheet("color: black; font-size: 20pt; text-align: center; vertical-align: middle"); textCell->setText(QString::number(0));
QLabel *symbolCell = new QLabel(this); symbolCell->setFixedSize(60, 60); symbolCell->setAlignment(Qt::AlignCenter);
setSpecialSymbolForCell(symbolCell, hiddenMap[row * puzzleSize + col]);
QVBoxLayout *cellLayout = new QVBoxLayout(); cellLayout->setContentsMargins(0, 0, 0, 0); symbolCell->setAttribute(Qt::WA_TransparentForMouseEvents, true); cellLayout->insertWidget(0,symbolCell); cellLayout->insertWidget(1,textCell); QWidget *cellWrapper = new QWidget(this); cellWrapper->setLayout(cellLayout); puzzleCells[row * puzzleSize + col] = textCell; puzzleGrid->addWidget(cellWrapper, row, col); } }
|
还有一些页面美化工作
如调用的
setStyleSheet函数等,后期还会继续进行页面美化与添加有趣的功能。
1 2 3
| returnButton->setStyleSheet( "font-family: \"Microsoft YaHei\"; font-size: 30px; font-weight: bold; color: black; " "border-radius: 8px; padding: 15px; min-width: 60px; min-height: 30px;");
|
寄语
希望在接下来的几周能够追求更高的代码质量和用户体验。