news 2026/11/4 17:04:18

C++数独GUI实战:生成算法、唯一解校验与Qt界面开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++数独GUI实战:生成算法、唯一解校验与Qt界面开发

简介:这是一份基于C++实现的数独游戏GUI源码包,适合正在学习C++编程、算法设计与图形界面开发的初学者或进阶者。压缩包内共9个文件,主要包括1个cpp源程序、1个dsp工程文件、1个dsw工作区文件以及调试生成的pch、pdb、ncb等辅助文件,整体大小约24KB,属于轻量级可直接编译学习的小型项目。已有360人浏览学习,说明具备一定的参考价值。源码完整覆盖了数独游戏的核心逻辑:采用9x9二维数组作为盘面数据结构,结合回溯算法自动求解或生成题目,并通过MFC等GUI框架实现用户交互界面;代码中还涉及事件驱动处理、输入合法性检查、局部约束剪枝优化以及基于fstream的进度存取等功能。通过阅读这份代码,可以直观理解数独规则在程序中的表达方式,掌握回溯搜索与约束传播的实际应用,同时学习如何在C++工程中组织界面与业务逻辑。对于希望快速上手C++游戏编程或算法实践的开发者而言,是一份结构简洁、便于剖析的入门实例。

1. 拿到 shudu.rar 先别急着编译:C++ 数独 GUI 项目的真正难点在生成与校验

多数人拿到 shudu.rar 这个压缩包,第一反应是解压、找 exe、点开玩两局,能玩就算结束。但如果你是以 C++ 数独游戏 GUI 编程为目标的从业者或学生,把这个压缩包当成练手项目来拆,就会发现真正的仗从来不在画格子——数独盘面的生成、唯一解的保证、玩家输入后的冲突提醒,这三块才是数独编程里最容易翻车、也最值得认真抄作业的地方。这个压缩包在初学者手里流通很广,里面的代码质量参差不齐,与其抱怨,不如沿着功能把生成算法和界面拆开重写一遍。下面按我自己做这类项目的惯例,把工程结构、算法实现、Qt 界面和踩坑点一条龙说清楚,适合刚学完 C++ 基础、想从控制台走向图形界面的读者,也适合做课程设计或期末项目的人。

2. 把工程拆开看:数独项目的模块边界与数据结构选型

2.1 解压后先找 .h 文件:模块划分决定项目能不能改

拿到压缩包先不要急着按 F5 编译。解压后我一般先看文件列表,确定它是 Qt 工程还是 Win32 工程。这类老包里最典型的文件布局是:main.cpp、MainWindow 相关的 .h/.cpp、一个 SudokuGenerator 类、一个 SudokuSolver 类,再加上 CMakeLists.txt 或者 .pro 工程文件。如果只有一个巨大的 main.cpp,所有逻辑全塞在窗口回调里,那说明作者的模块切得很糙,重写是比修补更明智的选择。

文件常见职责你最该关注的点
main.cpp程序入口、创建主窗口高 DPI 设置、QApplication 初始化
MainWindow.h/.cpp主窗体、按钮和表格是否把算法逻辑写进了 UI 类
SudokuGenerator.h/.cpp终盘生成、挖洞是否验证唯一解
SudokuSolver.h/.cpp回溯求解、填写候选数求解时是否深拷贝整个盘面
资源文件 .qrc/.rc图标、背景图路径是否写成绝对路径

看头文件的顺序是:先看 SudokuGenerator 和 SudokuSolver 的头文件。如果这两个类的公开接口里只有generate()和solve(),没有把盘面状态扔给 UI 层,那这个项目就还有救。我见过最难受的写法是在 MainWindow 里套三层 for 循环直接操作 QTableWidget 的单元格,这种代码加一个难度分级需求就得改一整片逻辑。

2.2 盘面用二维数组还是位掩码:数独编程的第一次选型

很多人的第一版数独程序用int grid[9][9],0 表示空格,1 到 9 表示已填数字。这个结构直观、好调试、打印方便,对于练手项目完全够用。但如果你想在生成谜题或求解时频繁判断"某个格子还能填哪些数字",每次都扫行、列和九宫格,循环次数会变得非常难看。

我一般会在早期就把位掩码引入,用 9 个 int 分别记录每行、每列、每个 3x3 宫已经使用过的数字。int 的低 9 位对应 1 到 9,第 n 位是 1 表示该数字已经存在。这样判断某个位置能否放数字,只需要三次与运算和一次或运算,比三层循环至少快一个数量级。常见的做法是这样:

// maskRow[9] 记录每行已占用的数字掩码,maskCol[9] 记录列,maskBox[9] 记录宫 int maskRow[9] = {0}; int maskCol[9] = {0}; int maskBox[9] = {0}; // 把数字 num 放到 (row, col),同步更新三个掩码 void placeNumber(int row, int col, int num) { int bit = 1 << (num - 1); // 数字 1 对应 bit0,数字 9 对应 bit8 maskRow[row] |= bit; // 占位后置 1 maskCol[col] |= bit; int boxIndex = (row / 3) * 3 + (col / 3); maskBox[boxIndex] |= bit; } // 判断当前格能否放 num bool canPlace(int row, int col, int num) { int bit = 1 << (num - 1); return !(maskRow[row] & bit) && !(maskCol[col] & bit) && !(maskBox[(row / 3) * 3 + (col / 3)] & bit); }

这段代码里的位运算在三个地方都用了:占位、判重、回溯时取消占位。取消占位就是把对应位清零,也就是maskRow[row] &= ~bit。这套接口在生成终盘和玩家输入校验时能复用同一套逻辑。参数上要注意boxIndex一定用(row / 3) * 3 + (col / 3),有人写成row / 3 * 3 + col / 3,由于优先级问题直接越界,这是数独编程里最经典的隐蔽 bug 之一。

2.3 生成、求解、校验三个类的接口怎么切

模块边界我推荐这样切:SudokuSolver 只做一件事,给定盘面求一个解或者统计解的个数;SudokuGenerator 依赖 SudokuSolver 来挖洞;MainWindow 负责把盘面显示到 QTableWidget 上,并把用户输入扔给一个简单的校验函数。这三层之间不要互相引用 UI 对象,否则你以后想加一个命令行版或者写单元测试都会很痛苦。

下面是我常用的类接口骨架,你可以直接抄进自己的头文件里:

// SudokuSolver.h class SudokuSolver { public: static bool solve(int grid[9][9]); static int countSolutions(int grid[9][9], int limit = 2); private: static bool isSafe(int grid[9][9], int row, int col, int num); }; // SudokuGenerator.h class SudokuGenerator { public: // difficulty: 0=简单, 1=中等, 2=困难 static void generate(int grid[9][9], int difficulty); static void fillDiagonal(int grid[9][9]); static void shuffle(int arr[], int len); };

接口设计成静态方法是为了让算法层不依赖窗口实例,也方便你在 main 函数里直接做自动化测试。需要注意的是countSolutions的第二个参数limit是性能关键:我们判断唯一解时只需要数到 2 就可以停止了,不需要把整棵搜索树跑完。很多新手写的计算全解的递归,在挖洞较多时会卡到毫秒级别都回不来,本质上是没有这个提前终止的开关。

3. 核心算法落地:回溯求解、唯一解验证与挖洞参数

3.1 回溯法求解:递归加候选数剪枝,别用全盘搜索

数独求解最稳的算法就是回溯。基本思路是找到一个空格,尝试填入 1 到 9,如果某次填入后能走通就继续递归,走不通就撤销。这个思路本身不难,难在写得够快。入门写法是固定从左到右、从上到下找空格,但我会改成"每次找候选数最少的格子"优先尝试,也就是 MRV 策略,这样能让递归树大幅瘦身。

// 返回 1 表示找到解,0 表示无解 int solveByMRV(int grid[9][9]) { // 1. 找候选数最少的空格 int bestRow = -1, bestCol = -1; int bestCount = 10; for (int r = 0; r < 9; ++r) { for (int c = 0; c < 9; ++c) { if (grid[r][c] != 0) continue; int cnt = 0; for (int num = 1; num <= 9; ++num) { if (isSafe(grid, r, c, num)) cnt++; } if (cnt < bestCount) { bestCount = cnt; bestRow = r; bestCol = c; } } } // 2. 没有空格说明已经填完 if (bestRow == -1) return 1; // 3. 逐个尝试候选数字 int originalRow = bestRow, originalCol = bestCol; for (int num = 1; num <= 9; ++num) { if (!isSafe(grid, originalRow, originalCol, num)) continue; grid[originalRow][originalCol] = num; if (solveByMRV(grid)) return 1; grid[originalRow][originalCol] = 0; } return 0; }

注意两个细节:第一,isSafe函数里要检查行、列和宫,漏掉宫检查会让你求出的解在九宫格内重复;第二,递归函数传的是二维数组的引用或者指针,不要按值传int grid[9][9]作为函数参数,否则每次递归都会在栈上复制 81 个 int,性能会有肉眼可见的下降。如果你在 Debug 模式下卡顿明显,先看看是不是这里踩坑了。

3.2 唯一解验证:忽略了它,你的游戏一半概率会翻车

很多旧版数独源码的挖洞逻辑是"随机挖掉一串数字,谁管有没有多解"。结果玩家玩到最后发现有两个数字可以互换,整盘仍然成立,这就是多解迷局。这种问题特别隐蔽,因为开发时你可能根本不会完整地玩一局——等到别人给你提 bug 时,你连哪两个数字是自由项都不好找。想保证唯一解,唯一可靠的做法是在生成谜题阶段就对每个挖掉的洞做唯一性验证。

我一般用一个countSolutions函数来完成验证,它不返回具体解,而是统计解的数量,数到 2 就立即终止:

// 数独求解与唯一解验证:统计解的数量,最多数到 limit 就停止 int countSolutions(int grid[9][9], int limit) { // 找候选数最少的空格 int bestRow = -1, bestCol = -1, bestCount = 10; for (int r = 0; r < 9; ++r) for (int c = 0; c < 9; ++c) { if (grid[r][c] != 0) continue; int cnt = 0; for (int num = 1; num <= 9; ++num) if (isSafe(grid, r, c, num)) cnt++; if (cnt < bestCount) { bestCount = cnt; bestRow = r; bestCol = c; if (cnt <= 2) break; // 候选数已经很少,没有必要继续找更少的 } } if (bestRow == -1) return 1; // 已填满,当前路径是一个解 int total = 0; for (int num = 1; num <= 9; ++num) { if (!isSafe(grid, bestRow, bestCol, num)) continue; grid[bestRow][bestCol] = num; total += countSolutions(grid, limit); // 注意这里要传累计值 grid[bestRow][bestCol] = 0; if (total >= limit) break; // 达到目标,裁剪 } return total; }

这个函数的参数limit是最关键的地方。你要判断唯一解时调用countSolutions(puzzle, 2),它返回 1 就是唯一解,返回 2 就是多解。验证已经给出的区域时,把 limit 设为 2 可以省掉大量无用递归。这里有个容易写错的点:递归调用时不能把total也作为 limit 传进去,否则上一层的累加逻辑会乱掉,我见过有人写出countSolutions(grid, total + 1)这种奇怪的调用,结果在挖洞较深时随机出错。

3.3 从终盘挖洞到谜题:难度参数怎么设才合理

生成谜题的常见做法是两步走:先生成一个完整的终盘,然后按随机顺序逐个挖洞,每挖一个洞都调用countSolutions验证唯一解,一旦出现多解就回填数字。下面这个表是我平时用的难度区间,按"盘面剩余已给数字个数"来划分,比按挖洞比例更好控制:

难度剩余已给数字挖掉个数适用场景
简单45 到 5031 到 36第一次接触数独的小朋友
中等36 到 4041 到 45日常练手、演示项目
困难28 到 3249 到 53给玩家一点压力

挖洞的时候,我一般让剩余数字与难度参数匹配。注意如果挖得太多,比如剩余数字少于 24 个,生成的题往往要么需要非常高级的链式推理,要么干脆无解,容易体验崩坏。下面是挖洞函数的核心片段:

// 在完整终盘 base 上挖洞,生成谜题 puzzle void digHoles(int puzzle[9][9], const int base[9][9], int holes) { // 生成随机打乱的 0~80 的顺序 int order[81]; for (int i = 0; i < 81; ++i) order[i] = i; std::shuffle(order, order + 81, std::mt19937(std::random_device{}())); int dug = 0; for (int i = 0; i < 81 && dug < holes; ++i) { int index = order[i]; int row = index / 9, col = index % 9; int backup = base[row][col]; puzzle[row][col] = 0; // 挖掉后如果不是唯一解,回填 if (countSolutions(puzzle, 2) != 1) { puzzle[row][col] = backup; } else { dug++; } } }

参数说明:std::shuffle需要 C++11 的<random>头文件,std::random_device{}()用于真正随机种子,而不是老式srand(time(0))的写法。countSolutions(puzzle, 2)在这里是性能热点,简单难度可能几次就挖完,困难难度可能要反复回填很多次。如果发现生成时间超过一秒,可以加一个尝试次数上限,尝试超过 2000 次后直接重新生成一个终盘,而不是死磕当前盘。

4. GUI 交互层:Qt Widgets 搭建九宫格与输入联动

4.1 选 Qt Widgets 还是 Win32:我把对比表给你

压缩包里的老工程很多是基于 MFC 或者 Win32 写的,因为它们年代早。但如果你现在从零写一个 C++ 数独 GUI,我更推荐 Qt Widgets 而不是 Win32 或 MFC。原因不是 Win32 不能做,而是同样的功能在 Qt 里代码量大概是 Win32 的三分之一,跨平台还能顺手编译出 macOS 版本。

方案跨平台控件灵活性打包体积上手成本
Qt Widgets好高,信号槽机制方便约 20 到 50 MB低,文档多
Win32 API只能在 Windows低,所有控件手写小,几百 KB高,消息循环绕
MFC只能在 Windows中,有历史包袱中高,类库老化
控制台 + 数字输入无所谓无最小最低但不符合 GUI 标题

如果你拿到的 shudu.rar 里是 Qt 3 或 Qt 4 的工程,建议直接升级思路,用现代 Qt 5 或 Qt 6 重新搭界面。类名和信号槽机制差别不大,照着改最经济。有一点要提醒:如果目标是发布给别人用,别选 Qt Quick/QML,一个数独游戏不需要动画特效,QWidget 的 QTableWidget 已经够用且打包问题更少。

4.2 用 QTableWidget 搭一个能跑起来的最小界面

数独界面最朴素的做法是放一个 9x9 的 QTableWidget,关闭行表头和列表头,设置每个格子的大小和字体。下面这段代码可以直接放进主窗口的构造函数里:

#include <QTableWidget> #include <QHeaderView> void MainWindow::createBoard() { m_table = new QTableWidget(9, 9, this); m_table->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); m_table->setSelectionMode(QAbstractItemView::SingleSelection); // 去掉表格左侧和上方的行号列号 m_table->verticalHeader()->setVisible(false); m_table->horizontalHeader()->setVisible(false); // 每个格子固定 48 像素,9 格共 432 像素 for (int i = 0; i < 9; ++i) { m_table->setRowHeight(i, 48); m_table->setColumnWidth(i, 48); } // 关闭焦点虚框,否则点击后会出现难看的蓝色边框 m_table->setFocusPolicy(Qt::NoFocus); }

注意setEditTriggers的参数是 QAbstractItemView::ItemEditTrigger 的或组合,这里允许双击或者按 F2 进入编辑。如果你希望玩家单击后直接输入数字,那就改成AllEditTriggers,但是这样容易误触,我建议还是双击或者按数字键直接响应策略。这里没有直接调setItem,因为单元格内容由后面的updateBoard统一填充,这样新增盘面时只需要改一个函数。

4.3 输入校验与冲突高亮:把用户操作变成有效反馈

玩家输入数字后,你需要立刻判断这一格是否与同行、同列或同宫的其他数字冲突。这里有一个高频 bug:当你调用setItem刷新界面时,QTableWidget 会再次触发itemChanged信号,如果你在信号回调里又去更新界面,就会形成无限循环。我见过不少新手的写法是"判断到冲突就调用 setBackground",结果每次重绘都会重新触发 itemChanged,界面直接卡死。规避方法是加一个 bool 守卫。

void MainWindow::onItemChanged(QTableWidgetItem *item) { if (m_updating) return; // 自己刷新时直接忽略 int row = item->row(); int col = item->column(); // 已给的初始数字格子是只读的,不会触发该回调 QString text = item->text().trimmed(); if (text.isEmpty()) { item->setBackground(QBrush(Qt::white)); return; } bool ok = false; int value = text.toInt(&ok); if (!ok || value < 1 || value > 9) { // 输入了 0 或字母,直接清空并标红 item->setText(""); item->setBackground(QBrush(Qt::red)); return; } // 判定冲突:忽略自身 if (isConflict(row, col, value)) { item->setBackground(QBrush(QColor(255, 180, 180))); } else { item->setBackground(QBrush(Qt::white)); } }

isConflict函数的实现逻辑很简单:遍历同一行 9 列,同一列 9 行,以及所在 3x3 宫里的其余 8 个格子,检查是否有相同数字。这里最容易漏掉的是"忽略当前格子"这个条件,不然玩家输入数字后自己也会被判成冲突。另一个容易被忽略的点是:输入字母或 0 时不能直接item->setText("")后就不管了,因为 setText 又会触发一次 itemChanged,再次进入回调后 text 为空,走的是return分支,问题不大,但如果你在空分支里也做了setBackground,配合 m_updating 的缺失就可能出现重入。

4.4 新游戏、检查、提示三个按钮的联动逻辑

界面上的三个核心按钮分别是"新游戏"、"检查"和"提示"。新游戏的逻辑是先生成终盘,再挖洞,最后把谜题填充到表格里。这里有一个线程问题:生成困难难度时 countSolutions 可能耗时几百毫秒,如果在主线程里跑,界面会短暂无响应。我一般的判断标准是:先直接在按钮回调里生成,如果 QElapsedTimer 测出来超过 200 毫秒,再考虑丢到 QThread 里;对多数课程设计来说,单线程足够。

void MainWindow::onNewGame() { int base[9][9] = {0}, puzzle[9][9] = {0}; // 1. 生成完整终盘 base SudokuGenerator::generateFullSudoku(base); // 2. 按当前难度挖洞得到 puzzle int holes = 40; // 由难度选项决定 SudokuGenerator::digHoles(puzzle, base, holes); // 3. 一次性刷新界面 updateBoard(puzzle); } void MainWindow::updateBoard(int puzzle[9][9]) { m_updating = true; // 进入刷新模式 for (int r = 0; r < 9; ++r) { for (int c = 0; c < 9; ++c) { QTableWidgetItem *item = m_table->item(r, c); if (!item) { item = new QTableWidgetItem; m_table->setItem(r, c, item); } if (puzzle[r][c] == 0) { item->setText(""); item->setFlags(item->flags() | Qt::ItemIsEditable); } else { item->setText(QString::number(puzzle[r][c])); item->setFlags(item->flags() & ~Qt::ItemIsEditable); } item->setTextAlignment(Qt::AlignCenter); item->setBackground(QBrush(Qt::white)); } } m_updating = false; // 刷新完成,允许用户输入触发回调 }

m_updating这个布尔守卫是整套 GUI 模块里最重要的变量,它防止 updateBoard 里的 setItem 触发 onItemChanged 造成递归。检查按钮的逻辑更简单:把当前表格内容提取为int grid[9][9],调用countSolutions(grid, 2),返回 1 且玩家填完所有空格,就弹出"恭喜完成"或"答案正确"。注意检查时不要用solveByMRV(grid),因为solve会修改 grid,而 check 的语义是不改变盘面。

5. 避坑手册:C++ 数独 GUI 高频故障的现象、原因与修复

5.1 中文乱码:界面按钮全是问号或者乱码

现象:按钮文字、提示框里的中文在 Windows 上显示成乱码,但同一个源码在 Linux 上正常。原因:MSVC 编译器把源文件当作 GBK 读取,而你的 .cpp 文件保存成了 UTF-8 无 BOM 格式;反过来也成立。Qt 的 QString 默认按 UTF-8 解释字符串字面量,两种编码一错位就乱码。解决:把整个工程的源码统一保存为 UTF-8 with BOM,或者在项目里加编译选项/utf-8;如果你在维护老代码,有一个兼容做法是给中文字符串套QString::fromLocal8Bit(...),但我不推荐到处都这样写,治标不治本。我在 MSVC 下会优先改工程属性里的"命令行附加选项",写入/utf-8。

5.2 挖洞死循环:新游戏按键一按就卡死

现象:点击"新游戏"按钮后,界面卡住不动,CPU 却跑满,甚至内存持续上涨。原因:挖洞过程中每次尝试都调用 countSolutions,但 countSolutions 在无解盘面上会遍历整棵搜索树,而你写的是暴力全盘搜索;再加上对多解判断用的 limit 参数没有生效,导致每次都要把树跑完。解决:先检查countSolutions是否在total >= limit时提前返回;再把挖洞循环加上最大尝试次数,比如超过 2000 次直接重新生成终盘。我自己的经验是,一旦挖洞失败次数超过一整轮 81 次,说明当前终盘的挖洞空间已经很小了,与其反复试,不如换个终盘重新开始,花费时间反而更少。

5.3 QTableWidget 信号死循环:设置背景色时界面崩溃

现象:在 itemChanged 回调里设置背景色后,程序会持续高占用,甚至爆栈崩溃。原因:QTableWidgetItem::setBackground 会触发数据变化,进而再次发射 itemChanged 信号;如果你的回调没有 m_updating 守卫,信号会递归调用。解决:所有由程序主动修改单元格内容或背景的地方,都要先用m_updating = true包围,回调入口第一行判断if (m_updating) return,修改完再复位。另一个类似的坑是 setText 也会触发信号,所以 updateBoard 里也要加守卫,不能只在回调里加。

5.4 回溯求解在 Release 下崩溃,在 Debug 下却好的

现象:Debug 版本运行正常,切到 Release 后不定时崩溃,或者递归一深就栈溢出。原因:最常见的是函数参数里按值传递了大数组,比如void solve(int grid[9][9])在调用时把整个数组复制到栈上,递归 60 层后栈压力剧增。Debug 下栈空间和数组对齐方式和 Release 不同,掩盖了问题。解决:在所有求解函数中,把参数改为int grid[9][9]的引用传递,或者传入int* grid指针;同时检查递归深度,数独的递归深度最多 81 层,正常情况下不该爆栈,一旦爆栈几乎都是深拷贝或无限递归。

5.5 打包之后到别的机器运行提示缺 DLL

现象:在自己电脑上双击 exe 正常,拷到另一台 Windows 电脑上提示"找不到 Qt5Core.dll"或者 "The application was unable to start correctly" 这类错误。原因:Qt 程序默认是动态链接,需要 Qt 运行库和 platforms/qwindows.dll 这个平台插件;直接拷贝 exe 不带上 DLL 肯定跑不起来。解决:用 Qt 自带的 windeployqt 工具把依赖补全。命令大致是windeployqt --release release\shudu.exe,执行后它会把你需要的 Qt DLL 和 platforms 文件夹放到 exe 旁边。这个步骤是这类 GUI 项目最容易被忽略的血泪经验,如果你还要发给别人演示,建议再压成一个 zip 或者用 Qt Installer Framework 做安装包。

6. 让 shudu 项目真正拿得出手:自测清单与追加功能

先把项目跑通,再做下面这个自测清单,每一条都能卡住不少半成品:

  • 连续生成 20 盘困难难度,全部能在一秒内完成,并且每一盘用countSolutions(grid, 2)验证返回 1;
  • 在任意空格输入字母、0、10 和负号,界面不崩溃且输入会被重置;
  • 输入一个与行、列或宫重复的数字,背景变红,把该格改对后红色消失;
  • 初始题目格无论怎么双击或按键,都不能被修改;
  • 填满所有格子且完全正确时,点击"检查"弹出胜利提示;
  • 点击"新游戏"后,上一盘的冲突红色全部清空。

通过这套自测之后,再考虑追加能力。难度分级并不只是改挖洞数量,还可以在挖洞前先按布局把终盘随机转置、交换行组,增加视觉变化;给初始题目格加浅灰色背景区分玩家填充格;加一个 60 秒计时器,用 QTimer 每秒刷新标题栏;存档读档用到QSettings,把 81 个格子的数字写成一个 81 字符的字符串存起来,比写配置文件简单得多。如果你还想继续深入 C++ 数独编程,可以去做"候选数高亮"或者"唯一候选提示",这两块都是前端显示与算法结合的好练习。

我在最早写这个项目时,只做了随机挖洞,完全没做唯一解验证,结果同事玩到一半指出两个数字可以互换,相当于我交出去一把有后门的锁。那晚我重写了整个生成器,把countSolutions抽成独立模块,之后所有新功能都先过一遍上面的自测清单。这种教训用一次就长记性:数独游戏的判断标准不是"能点会动",而是"生成的每一盘题只有一个答案"。希望这篇文章能帮你少走这段弯路,也希望你自己动手改一遍后,能对生成、求解、界面联动这套链路有更踏实的体感。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 3:02:22

国产AI智能体从演示到生产:ReAct架构、容错控制与工作流搭建实战

1. 从“对话框”到“执行体”&#xff1a;国产AI智能体到底在解决什么问题 过去两年&#xff0c;大多数人接触大模型的入口就是一个聊天框。你问它答&#xff0c;答完就结束&#xff0c;它不会主动帮你把事办了。这个模式有个根本性的天花板&#xff1a;模型再聪明&#xff0c;…

作者头像 李华
网站建设 2026/10/30 9:35:32

DAB双有源桥变换器:结构原理、参数设计与ZVS调试指南

接触过车载充电器或者储能变换器的朋友&#xff0c;大概率绕不开DAB这三个字母。别急着联想到什么广播电台&#xff0c;在电力电子领域&#xff0c;DAB指的是Dual Active Bridge&#xff0c;双有源桥。简单说&#xff0c;它就是一个原边、副边各有一个全桥电路&#xff0c;中间…

作者头像 李华
网站建设 2026/11/1 19:44:19

航空票务系统JavaWeb实战:从架构选型到余票扣减的完整开发指南

简介&#xff1a;这是一份面向JavaWeb课程设计与初学者练手的航空系统完整源码&#xff0c;采用Java为核心、JavaWeb技术栈构建&#xff0c;前端以HTML、CSS和JavaScript实现用户交互界面&#xff0c;后端处理航班管理、订票、值机等业务逻辑&#xff0c;适合作为课程设计参考或…

作者头像 李华
网站建设 2026/11/2 2:28:57

常驻 Agent 四层治理架构:声明、校验、版本、预演与 72 条实测

做 Agent 开发这几年&#xff0c;我的体会是&#xff1a;一次性对话式 Agent 好写&#xff0c;常驻 Agent 难养。常驻 Agent 挂着定时任务、持着文件权限、连着外部工具&#xff0c;一天自动跑几百次&#xff0c;出一次问题就是批量误操作。最近我把手头的常驻运维 Agent 重构了…

作者头像 李华
网站建设 2026/11/2 1:58:22

JSP网上书店系统毕业设计:从环境配置到答辩避坑全攻略

简介&#xff1a;面向毕业设计场景的基于JSP的网上书店系统完整项目包&#xff0c;整合了源码、论文与操作视频&#xff0c;适合计算机相关专业学生进行Java Web方向的学习或二次开发。系统采用B/S结构&#xff0c;以JSP和SQL Server数据库为核心&#xff0c;围绕图书销售业务实…

作者头像 李华