You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在同一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();
}

关键细节说明

  1. 难度差异控制:通过挖掉不同数量的格子实现难度区分,你可以根据需求调整removeCount的数值,比如简单难度少挖几个,地狱难度多挖几个。
  2. 数据更新机制:生成新数独后调用dataChanged通知视图刷新,确保QML的TableView能实时显示新数独。
  3. 代码复用性:完全基于你现有的QAbstractTableModel和TableView逻辑改造,不需要新建多个模型,代码复用率高。

内容的提问来源于stack exchange,提问作者Arlyn Annie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 00:42:07