如何在同一QML文件中实现带5种难度的C++数独游戏?
5种难度数独的最简实现方案
核心逻辑
给C端的Grid模型类加难度属性和对应难度的数独生成方法,在QML里加个难度选择栏,点击切换时触发C端重新生成数独并通知视图更新——这是最省心的实现方式,不用搞多个模型,直接复用现有代码就行。
第一步:改造C++的Grid类
1. 更新头文件(Grid.h)
添加难度枚举、可被QML访问的属性,以及生成数独的核心方法:
#pragma once #include <QAbstractTableModel> // 定义5种难度,让QML能识别枚举类型 enum class Difficulty { Easy, Medium, Hard, Expert, Extreme }; Q_ENUM(Difficulty) class Grid : public QAbstractTableModel { Q_OBJECT // 暴露难度属性给QML,支持读写和变更通知 Q_PROPERTY(Difficulty difficulty READ difficulty WRITE setDifficulty NOTIFY difficultyChanged) public: explicit Grid(QObject *parent = nullptr); // 原有的模型方法完全保留 int rowCount(const QModelIndex & = QModelIndex()) const override; int columnCount(const QModelIndex & = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; QHash<int, QByteArray> roleNames() const override; // 难度属性的get/set方法 Difficulty difficulty() const; void setDifficulty(Difficulty newDifficulty); // 生成完整数独+按难度挖空的核心方法 void generateSudoku(); signals: void difficultyChanged(); private: int gridData[9][9]; // 显示用的网格(含空白) int originalGrid[9][9]; // 保存完整数独,挖空时复用 Difficulty m_difficulty = Difficulty::Easy; // 辅助方法:回溯法生成合法数独 bool solveSudoku(); // 检查当前位置能否放置指定数字 bool isSafe(int row, int col, int num); // 根据难度挖掉对应数量的格子 void removeCellsByDifficulty(); };
2. 更新源文件(Grid.cpp)
实现新增方法,替换原来构造函数里的固定网格生成逻辑:
#include "Grid.h" #include <QRandomGenerator> #include <algorithm> Grid::Grid(QObject *parent) : QAbstractTableModel(parent) { generateSudoku(); // 构造时直接生成初始难度的数独 } // 原有的rowCount、columnCount、data、roleNames代码完全保留,此处省略重复内容 // 难度属性的getter Difficulty Grid::difficulty() const { return m_difficulty; } // 难度属性的setter,切换难度时重新生成数独 void Grid::setDifficulty(Difficulty newDifficulty) { if (m_difficulty == newDifficulty) return; m_difficulty = newDifficulty; emit difficultyChanged(); generateSudoku(); } // 生成完整数独+挖空的核心方法 void Grid::generateSudoku() { // 清空两个网格 memset(gridData, 0, sizeof(gridData)); memset(originalGrid, 0, sizeof(originalGrid)); // 用回溯法生成一个合法的完整数独 solveSudoku(); // 复制完整数独到备份网格 memcpy(originalGrid, gridData, sizeof(gridData)); // 根据当前难度挖空 removeCellsByDifficulty(); // 通知QML视图数据更新 emit dataChanged(index(0,0), index(8,8), {Qt::DisplayRole}); } // 回溯法生成/求解数独(随机打乱数字顺序,避免每次生成相同数独) bool Grid::solveSudoku() { for (int row = 0; row < 9; row++) { for (int col = 0; col < 9; col++) { if (gridData[row][col] == 0) { // 随机排序1-9,增加数独多样性 QList<int> nums = {1,2,3,4,5,6,7,8,9}; std::shuffle(nums.begin(), nums.end(), *QRandomGenerator::global()); for (int num : nums) { if (isSafe(row, col, num)) { gridData[row][col] = num; if (solveSudoku()) return true; gridData[row][col] = 0; } } return false; } } } return true; } // 检查当前位置是否可以放置数字num bool Grid::isSafe(int row, int col, int num) { // 检查行 for (int c = 0; c < 9; c++) { if (gridData[row][c] == num) return false; } // 检查列 for (int r = 0; r < 9; r++) { if (gridData[r][col] == num) return false; } // 检查所在的3x3宫 int boxRow = row - row % 3; int boxCol = col - col % 3; for (int r = boxRow; r < boxRow+3; r++) { for (int c = boxCol; c < boxCol+3; c++) { if (gridData[r][c] == num) return false; } } return true; } // 根据难度挖掉对应数量的格子,难度越高挖的越多 void Grid::removeCellsByDifficulty() { int removeCount = 0; switch(m_difficulty) { case Difficulty::Easy: removeCount = 40; break; // 剩余41个数字 case Difficulty::Medium: removeCount = 45; break; case Difficulty::Hard: removeCount = 50; break; case Difficulty::Expert: removeCount = 55; break; case Difficulty::Extreme: removeCount = 58; break; // 只剩13个数字,接近最难 } // 随机挖空,避免重复位置 QSet<QPair<int, int>> removedPos; while (removedPos.size() < removeCount) { int row = QRandomGenerator::global()->bounded(9); int col = QRandomGenerator::global()->bounded(9); QPair<int, int> pos = {row, col}; if (!removedPos.contains(pos)) { removedPos.insert(pos); gridData[row][col] = 0; // 用0表示空白格子 } } }
第二步:修改QML代码
添加难度选择按钮栏,绑定Grid的难度属性,同时调整TableView的空白格子显示逻辑:
import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 // 导入我们注册的Grid类 import com.example.sudoku 1.0 Item { anchors.fill: parent // 实例化数独模型 Grid { id: sudokuModel } // 难度选择按钮组 RowLayout { anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter spacing: 12 padding: 10 Button { text: "简单" onClicked: sudokuModel.difficulty = Grid.Easy } Button { text: "中等" onClicked: sudokuModel.difficulty = Grid.Medium } Button { text: "困难" onClicked: sudokuModel.difficulty = Grid.Hard } Button { text: "专家" onClicked: sudokuModel.difficulty = Grid.Expert } Button { text: "地狱" onClicked: sudokuModel.difficulty = Grid.Extreme } } // 原有的TableView,修改绑定和显示逻辑 TableView { anchors.top: parent.top anchors.topMargin: 60 anchors.fill: parent anchors.bottomMargin: 10 columnSpacing: 1 rowSpacing: 1 clip: true model: sudokuModel delegate: Rectangle { implicitWidth: 50 implicitHeight: 50 border { color: "white" width: 1 } color: model.row % 2 ? "lightpink" : "lightblue" Text { anchors.centerIn: parent // 空白格子(值为0)显示空字符串 text: display === 0 ? "" : display font.pointSize: 12 } } } }
第三步:注册Grid类到QML
在main.cpp里把Grid类注册到QML,否则QML无法识别:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "Grid.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); // 注册Grid类到QML,命名空间可自行修改 qmlRegisterType<Grid>("com.example.sudoku", 1, 0, "Grid"); QQmlApplicationEngine engine; const QUrl url(u"qrc:/main.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
关键细节说明
- 难度差异控制:通过挖掉不同数量的格子实现难度区分,你可以根据需求调整
removeCount的数值,比如简单难度少挖几个,地狱难度多挖几个。 - 数据更新机制:生成新数独后调用
dataChanged通知视图刷新,确保QML的TableView能实时显示新数独。 - 代码复用性:完全基于你现有的
QAbstractTableModel和TableView逻辑改造,不需要新建多个模型,代码复用率高。
内容的提问来源于stack exchange,提问作者Arlyn Annie
相关产品推荐
相关产品推荐

