Qt Creator实现中国象棋人机对战:从棋盘绘制到AI搜索的完整实践
简介面向Qt/C初学者的中国象棋人机对战完整工程示例基于Qt Creator开发聚焦图形界面下的棋盘绘制、鼠标交互与博弈算法搜索适合学习Qt实战、棋类规则实现和简单AI设计的开发者参考。压缩包共47个文件大小仅575KB以14个头文件与14个C源文件为核心附带2个Qt界面文件、工程配置文件.pro/.qrc、9张棋盘与提示图片素材及README说明目录结构紧凑便于按模块阅读。已有1001人学习下载说明这一入门级项目具备较好的参考价值。工程完整实现了棋盘二维数组建模、红黑棋子属性管理、各个棋子的合法移动判定、鼠标落子反馈、胜负判断以及基于alpha-beta剪枝的电脑落子逻辑读者可从中掌握Qt窗口、绘图、事件处理的开发流程了解评估函数与搜索剪枝的设计思路也能为课程设计或毕业设计提供可直接改造的模板。1. Qt Creator 里的中国象棋人机对战先看清楚这个项目解决什么问题Qt Creator 里用 C 从零搭一个中国象棋人机对战听起来像普通练手项目真正写完你会发现它把面向对象、事件循环、QPainter 绘图、线程通信全部串在了一起。这套源码是一个完整的 C 小游戏实例界面、规则、AI 三层都有不是空壳窗口。项目解决的核心问题有三个棋盘和棋子的渲染怎么做才自然走棋规则怎么用 C 表达成可校验的代码AI 走棋的搜索逻辑怎么和 Qt 界面共存、不把界面卡死。如果你写过 C 基础语法想在 Qt Creator 里找一个能看见完整成果、又能逐步拆解的实战代码这份项目比零散的 Qt 示例更贴近真实开发。对我来说它最大的价值是把棋类 AI 怎么跟 GUI 配合这件事完整跑通了一遍而不是一个控制台 demo。下面按拆包顺序走绘制、规则、AI、踩坑、进阶。2. 棋盘绘制与坐标换算QPainter 画盘、画子、像素点位互换2.1 棋盘和棋子的绘制方案写中国象棋的第一步是决定棋盘用什么方式画。很多人学 Qt 界面设计一上来就摆控件、调布局但象棋这种固定 9×10 网格的界面用 QPainter 直接绘制比堆 QLabel 高效得多也更容易实现选中高亮和落子标记。这套绘制逻辑不依赖 Qt 的特定版本我在 Qt 5.14.2 和 Qt 6.x 上都验证过paintEvent 里 QPainter 的用法完全一致。棋盘本身不是 9×10 个完整方格而是交叉点阵。绘制时要处理四类元素外边框、10 根横向直线、9 根纵向直线中间跨河断开、河界两边的九宫斜线和炮位标记。把这些全部扔进 ChessBoard 的 paintEvent每次界面刷新自动重绘void ChessBoard::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); // 棋盘边距和格子大小同时也是坐标换算的基准 const int leftMargin 40; const int topMargin 40; const int cellSize 56; // 10 根横线从第 0 行画到第 9 行贯穿整个棋盘 for (int row 0; row 10; row) { int y topMargin row * cellSize; painter.drawLine(leftMargin, y, leftMargin 8 * cellSize, y); } // 9 根竖线分上下两段中间河界位置留空 for (int col 0; col 9; col) { int x leftMargin col * cellSize; painter.drawLine(x, topMargin, x, topMargin 4 * cellSize); painter.drawLine(x, topMargin 5 * cellSize, x, topMargin 9 * cellSize); } }这段代码里横线数量是 10、竖线数量是 9因为象棋的落子点落在交叉点上。竖线为什么要分两段因为第 4 行到第 5 行之间是河界“楚河汉界”四个字正好画在中间没有线的区域。如果偷懒把竖线一次性从 0 画到 9河界就消失了棋盘看起来很不专业。cellSize 是全局影响参数调大调小会整体缩放棋盘但左上角的 leftMargin 和 topMargin 要同步调整否则棋盘会偏出窗口。棋子的绘制放在同一个 paintEvent 里遍历棋盘数据逐个画void ChessBoard::drawPiece(QPainter painter, const ChessPiece piece, int boardRow, int boardCol) { const int leftMargin 40; const int topMargin 40; const int cellSize 56; QPoint center(leftMargin boardCol * cellSize, topMargin boardRow * cellSize); // 底色区分阵营红方暖色黑方冷色 painter.setBrush(piece.side RED ? QColor(230, 90, 80) : QColor(55, 55, 55)); painter.setPen(QPen(QColor(200, 180, 120), 2)); painter.drawEllipse(center, 24, 24); // 棋子文字用楷体加粗保证小字号下可读 painter.setPen(Qt::white); QFont font(QStringLiteral(楷体), 18, QFont::Bold); painter.setFont(font); painter.drawText(QRect(center.x() - 20, center.y() - 20, 40, 40), Qt::AlignCenter, piece.name()); }参数调整点半径 24 是按 cellSize56 换算出来的大约是 0.43 倍格子宽画出来不会压到相邻交叉点想做大棋子风格半径可以提到 26但相邻棋子的边缘距离会变小。文字区域固定 40×40比棋子直径小一圈确保文字不超出圆形边缘。这里不用 QLabel 或贴图因为选中高亮、最后一步落子标记都要叠加绘制用 QPainter 画最灵活。2.2 坐标换算把鼠标像素点转成棋盘点位界面层和规则层之间隔着一层坐标系鼠标按下拿到像素坐标而棋盘逻辑只认 row 和 col。换算公式是固定套路像素坐标减去棋盘边距除以格子大小再做一次四舍五入。void ChessBoard::mousePressEvent(QMouseEvent *event) { QPoint pos event-pos(); // 像素坐标 - 棋盘行列坐标加 cellSize/2 做四舍五入 int col (pos.x() - leftMargin cellSize / 2) / cellSize; int row (pos.y() - topMargin cellSize / 2) / cellSize; if (row 0 || row 9 || col 0 || col 8) return; // 信号交给规则层处理 emit boardClicked(row, col); }最关键是 cellSize / 2。因为(pos.x() - leftMargin) / cellSize是向下取整如果不加半个格子点击靠近交叉点右侧 1 个像素时会归到左边格子手感明显不对。加上 cellSize/2 后每个格子被划分成四个象限落点离哪个交叉点近就选哪个。边界判断写在信号发出之前规则层只接收合法坐标。下表是这套换算里所有常量的关系以后改棋盘大小只需要调 cellSize不需要动换算逻辑名称计算方式默认值左边距 leftMargin固定常量40上边距 topMargin固定常量40格子大小 cellSize固定常量56鼠标所在列(x - leftMargin cellSize/2) / cellSize—鼠标所在行(y - topMargin cellSize/2) / cellSize—交叉点像素 XleftMargin col * cellSize—交叉点像素 YtopMargin row * cellSize—再补充一个容易翻车的点交叉点周围的点击归属。鼠标落在两个交叉点正中间时 cellSize / 2会把它归到右下侧如果你希望归到离鼠标更近的一侧可以把cellSize / 2改成cellSize * 0.45这种轻微右偏的值。这种微调在 Qt 里只改一个表达式不用动规则层。上面这些常数要做自适应窗口也可以在 resizeEvent 里按控件宽度重算void ChessBoard::resizeEvent(QResizeEvent *event) { // 按窗口可用尺寸重新计算格子大小再触发重绘 int newCell qMin((width() - 80) / 8, (height() - 80) / 9); if (newCell 30) { cellSize newCell; } QWidget::resizeEvent(event); update(); }这样窗口拉大时棋盘跟着变大但 leftMargin 和 topMargin 要同步改成(width() - 8 * cellSize) / 2这类动态值。这份源码默认用固定 cellSize逻辑更简单把棋盘嵌进可缩放布局时再做自适应也不迟。3. 走棋规则与吃子判定把中国象棋规则写成 C 可执行代码3.1 基类设计棋子类型、阵营与走法校验规则层是纯 C不依赖任何 Qt 绘制类。棋子的数据模型用一个结构体就能承载类型、阵营、存活状态。没用多态继承去定义七种棋子类因为象棋所有棋子的共性很少差异集中在一步走法判断里用枚举加 switch 反而让代码集中、容易排查。enum Side { RED, BLACK }; enum PieceType { KING, // 帅/将 ROOK, // 车 KNIGHT, // 马 CANNON, // 炮 BISHOP, // 象 ADVISOR, // 士 PAWN // 兵 }; struct ChessPiece { PieceType type; Side side; bool alive; };两个设计取舍值得展开说。第一用Side side而不是bool isRed后续要扩展成“执红执黑可选”或复盘模式枚举比布尔值容易扩展第二alive单独标记而不是通过棋盘位置是否为空判断是为了悔棋和复盘时能恢复被吃棋子——被吃只是移出棋盘不是销毁对象。走法校验入口是规则层的总闸bool ChessGame::isMoveLegal(int fromRow, int fromCol, int toRow, int toCol) { ChessPiece *piece grid[fromRow][fromCol]; if (!piece || !piece-alive) return false; ChessPiece *target grid[toRow][toCol]; if (target target-side piece-side) return false; // 不能吃己方棋子 int dr toRow - fromRow; int dc toCol - fromCol; switch (piece-type) { case ROOK: return isPathClear(fromRow, fromCol, toRow, toCol); case KNIGHT: return isKnightMoveLegal(fromRow, fromCol, toRow, toCol, dr, dc); case CANNON: return isCannonMoveLegal(fromRow, fromCol, toRow, toCol); // BISHOP / ADVISOR / PAWN / KING 按同样风格实现 default: return false; } }入口先做两层判断目标位置是不是己方棋子再做具体类型分流。第一层漏掉会出现“吃自己人”的走法AI 在搜索时甚至可能为了吃掉己方棋子拿到虚假估值。规则层越早拦截AI 搜索树的无效分支越少。3.2 单步走法检查从马脚、象眼到炮的翻山马、象、炮是象棋里最容易写错的三种棋子。马的规则是走日字但蹩马腿是很多初版代码的翻车点。核心思路移动两格的方向上中间那个点不能有棋子。bool ChessGame::isKnightMoveLegal(int fr, int fc, int tr, int tc, int dr, int dc) { // 马走日|dr|2 且 |dc|1或 |dr|1 且 |dc|2 if (!((abs(dr) 2 abs(dc) 1) || (abs(dr) 1 abs(dc) 2))) return false; // 马脚位置横移两格时马脚在中间横格竖移两格时马脚在中间竖格 int legRow (abs(dr) 2) ? fr dr / 2 : fr; int legCol (abs(dc) 2) ? fc dc / 2 : fc; return grid[legRow][legCol] nullptr; }参数说明abs(dr) 2对应纵向走两格此时马腿在纵向的中间一行横向同理。dr / 2在 dr 为 ±2 时结果是 ±1正好是中间格偏移。如果grid[legRow][legCol]不为空马就被别住走法非法。象的蹩象眼是同一个套路区别是象每次走两格斜线象眼在两格对角线的中点并且象不能过河。炮的翻山是整个规则层最特殊的一条。炮平地走棋时中间不能有任何棋子吃子时反而必须且只能隔一个棋子。代码里用一个计数函数统计两点之间路径上的棋子数bool ChessGame::isCannonMoveLegal(int fr, int fc, int tr, int tc) { // 统计路径上的棋子数纯移动要 0 个吃子必须恰好 1 个 int between countPiecesBetween(fr, fc, tr, tc); if (grid[tr][tc] nullptr) return between 0; // 炮走直线中间不能有子 return between 1; // 炮吃子必须且只能翻一座山 }这里的 countPiecesBetween 要区分三种情况同行、同列、既不同行也不同列后一种直接返回 0因为炮不能斜走。实现时循环逐格扫描步长为 1遇到非空就计数。边界条件是起点和终点本身不参与统计循环从 from 的下一个点开始到 to 的前一个点结束。统计范围写错炮的走法会整体偏移一格排查起来很费劲。将帅和士相对简单但有一条规则特别关键将帅不能出九宫也不能照面。照面指两个将帅在同一竖线上且中间没有棋子。这个判断在搜索算法里尤其重要漏掉它 AI 可能走出让将帅面对面的非法局面。有了单步合法判断整个走法生成器就顺理成章generateAllMoves(side)遍历己方每个棋子对每个目标位置调用isMoveLegal收集合法走法。这个函数是 AI 模块最高频调用点它的效率直接决定搜索深度能到几层。4. 人机对战 AI极大极小搜索与评估函数怎么落地4.1 评估函数用棋盘局势量化给 AI 一个“好与坏”人机对战的 AI 不用机器学习传统做法是把局面变成数值给每个棋子一个价值红方为正、黑方为负所有棋子求和得到总分。AI 执黑时取反搜索目标是让这个分数尽量小。棋子基础价值帅/将100000车900马400炮450象/相200士/仕200兵/卒未过河100兵/卒过河150int ChessGame::evaluate() const { int score 0; for (int r 0; r 10; r) { for (int c 0; c 9; c) { const ChessPiece *p grid[r][c]; if (!p || !p-alive) continue; int value pieceValue(p-type); if (p-side RED) score value; // 红方加分 else score - value; // 黑方减分 } } return score; }这段代码双重循环遍历棋盘红方加、黑方减返回值大于 0 表示红方占优。这套评估函数已经能区分丢车保帅和弃马抢攻的大方向但判断不了具体位置优劣——比如一个卡住对方要道的马和一个困在角落的马价值一样。这是基础评估函数的天花板进阶可以给每个位置加一个位置偏移表。4.2 搜索流程从当前局面爆发走法到 AI 落子AI 核心是极大极小搜索。原理不复杂假设红黑双方都选择对自己最优的走法红方想让评估值最大黑方想让评估值最小轮流往下推。深度为 0 时返回评估函数结果作为搜索树的叶子节点。直接递归不加优化深度 4 的搜索树有几十万节点调试版下会卡到秒级。alpha-beta 剪枝是标准提速手段搜索过程中维护两个边界值alpha 是红方已知能做到的最大收益beta 是黑方已知能做到的最小损失。当某个分支返回值跌破 beta 时这棵子树对黑方没有保留价值直接剪掉。int ChessGame::alphaBeta(int depth, int alpha, int beta, Side side) { if (depth 0) return evaluate(); QVectorMove moves generateAllMoves(side); if (moves.isEmpty()) return (side RED) ? -INF_SCORE : INF_SCORE; // 无路可走即输 if (side RED) { int best -INF_SCORE; for (const Move m : moves) { makeMove(m); int score alphaBeta(depth - 1, alpha, beta, BLACK); undoMove(m); best qMax(best, score); alpha qMax(alpha, score); // 提高红方收益下限 if (beta alpha) break; // 剪掉黑方不会接受的分支 } return best; } else { int best INF_SCORE; for (const Move m : moves) { makeMove(m); int score alphaBeta(depth - 1, alpha, beta, RED); undoMove(m); best qMin(best, score); beta qMin(beta, score); // 压低黑方损失上限 if (beta alpha) break; } return best; } }调用入口是 bestMoveForSide遍历当前一方所有走法每走一步调用 alphaBeta 拿一个评分选评分最高的走法落子。Move ChessGame::bestMoveForSide(Side aiSide, int depth) { QVectorMove moves generateAllMoves(aiSide); Move bestMove moves.first(); int bestScore -INF_SCORE; for (const Move m : moves) { makeMove(m); // 对方回合用相反搜索alpha/beta 初始化为正负无穷 int score alphaBeta(depth - 1, -INF_SCORE, INF_SCORE, opponent(aiSide)); undoMove(m); if (score bestScore) { bestScore score; bestMove m; } } return bestMove; }搜索深度和耗时的关系不是线性的。深度 2 可能几十毫秒深度 3 大概一两秒深度 4 就可能要十几秒。源码里默认深度取 3是平衡棋力和响应速度之后的结果。想验证剪枝是否生效可以把深度设到 4对比开剪枝和不开剪枝的节点数通常差一个数量级。走法排序直接决定了剪枝效率。如果第一手就搜到很强的走法后续分支只需对比一次就能剪掉。常用策略是把吃子走法排前面尤其是用车吃子、炮吃子这种高价值交换让搜索尽早发现好参考线void sortMovesByCaptureFirst(QVectorMove moves) { std::stable_sort(moves.begin(), moves.end(), [](const Move a, const Move b) { // 有吃子的走法排在前面优先压低上界 bool aIsCapture a.capturedType ! EMPTY; bool bIsCapture b.capturedType ! EMPTY; return aIsCapture bIsCapture; }); }一个容易被忽略的细节undoMove 必须和 makeMove 完全对称。makeMove 里移动棋子、切换轮次undoMove 就要把这些全部还原否则搜索到深层后棋盘状态会乱掉。我调试时遇到过估值时好时坏、复现概率不稳定的情况最后定位到是 undoMove 少还原了一个字段。用断点在 makeMove 和 undoMove 成对处检查棋盘哈希值能很快定位这类问题。5. 避坑与常见问题Qt 环境下编译、绘图与界面假死的四个典型坑5.1 中文路径与 MSVC/MinGW 混用导致的编译失败现象项目源码放在“D:\象棋项目\”这样的中文目录下Qt Creator 构建时报“cannot open source file”。MinGW 报错频率明显高于 MSVC有时候前一次构建正常clean 之后重新构建又随机失败。原因MinGW 的 make 工具对非 ASCII 路径处理不完善MSVC 在中文代码页环境下moc 生成的临时文件路径也可能解析失败。这不是代码逻辑问题是构建工具链对路径编码的兼容性差异。解决项目从创建起就统一放到纯英文、无空格的路径下例如 D:\QtProjects\ChineseChess。如果是从别处拷贝项目先删除 build 目录和 .pro.user 文件重新打开 .pro 再构建。即使 qmake 能通过链接阶段也可能因为路径编码随机失败所以英文路径是最省心的做法。5.2 棋盘刷新时的绘图闪烁与残影现象每次走棋后调用 update()棋盘出现轻微闪烁快速连续走棋时棋子位置留下残影。原因QWidget 默认开了双缓冲但如果在 paintEvent 里加了setAttribute(Qt::WA_OpaquePaintEvent)又手动清空背景会破坏双缓冲机制把绘制中间状态暴露到屏幕上。另一个加剧因素是 paintEvent 里频繁创建 QFont、QPen 等临时对象绘制开销变大闪烁更明显。解决去掉 WA_OpaquePaintEvent让 Qt 自己处理背景擦除QFont 和 QPen 做成成员变量或 static 局部变量不要每次绘制都重新创建刷新统一用 update() 而不是 repaint()。update() 会把多次绘制请求合并成一次repaint() 是强制同步刷新连续走棋时会频繁触发重绘。5.3 AI 计算跑在 UI 线程导致界面假死现象轮到 AI 走棋时界面卡住不动窗口拖不动、按钮点不了几秒后 AI 落子才恢复。调试模式下卡顿尤其明显把深度调到 4 以后直接变成“死机”体验。原因AI 搜索是计算密集型递归如果在点击事件的槽函数里同步调用主线程事件循环被阻塞所有 UI 事件包括重绘、鼠标、键盘都排不上队。这不是 Qt 的 bug是所有 GUI 框架的通病。解决把搜索放到后台线程。最轻量的做法是用 QtConcurrent::run 跑 lambda再用 QFutureWatcher 监听结果结果准备好后通过信号槽回到主线程更新棋盘。// 玩家走棋结束后异步启动 AI 搜索 void Game::onPlayerMoved(int row, int col) { // ... 玩家走棋合法性处理 ... QFutureMove future QtConcurrent::run([this]() { return bestMoveForSide(BLACK, 3); // 深度 3 在子线程跑 }); watcher-setFuture(future); } // 子线程执行完毕后Qt 在主线程触发这个槽 void Game::onAiMoveReady() { Move aiMove watcher-result(); makeMove(aiMove); updateBoard(); }注意QFutureWatcher 的 finished 信号由 Qt 保证在主线程触发所以 onAiMoveReady 里可以直接操作棋盘控件。如果用 QThread moveToThread 方案同样原则子线程只跑纯计算界面更新一律通过信号回主线程。5.4 棋子中文文字乱码与 Qt 平台插件崩溃现象编译运行后棋子上的“车”“马”“炮”显示成乱码方块在另一个环境里程序启动直接报 “qt.qpa.plugin: could not find the Qt platform plugin windows”。原因乱码通常是源码文件编码问题。源码不是 UTF-8 时Qt 字符串字面量按本地代码页解释中文就变乱码。平台插件崩溃是 Qt 的 bin 目录或 plugins/platforms 目录没有被正确设置常见于把编译出的 exe 单独拷到别的机器上运行——很多人只拷可执行文件Qt 运行库没跟着走。解决源码统一 UTF-8 编码MSVC 编译器在 .pro 里加QMAKE_CXXFLAGS /utf-8字符串统一用 QStringLiteral 包裹。拷 exe 到其他机器时用 windeployqt 把 Qt 运行依赖一并收集。第二种情况我遇到最多同事只拷 exe 导致启动崩溃和代码本身没有关系。6. 验证与进阶悔棋、棋谱记录与 AI 难度调参6.1 悔棋功能用历史栈回滚两步人机对战的悔棋不只是撤销最后一步而是连续回退两步——AI 的一步和玩家的一步。维护一个历史栈每次 makeMove 时把起点、终点、被吃子信息压栈struct MoveRecord { int fromRow, fromCol, toRow, toCol; PieceType capturedType; Side capturedSide; }; QStackMoveRecord history; void ChessGame::makeMove(const Move m) { // 落子前记录现场悔棋和搜索恢复都要用 history.push({m.fromRow, m.fromCol, m.toRow, m.toCol, m.capturedType, m.capturedSide}); // 棋盘更新代码... } void ChessGame::undoLastMove() { if (history.isEmpty()) return; MoveRecord rec history.pop(); // 恢复被吃棋子把落子移回原位 }悔棋按钮点击后调用两次 undoLastMove再刷新棋盘。注意一个细节如果 AI 还没走完玩家就点悔棋需要先取消异步搜索否则 QFutureWatcher 结果回来后会用旧局面覆盖棋盘。常见的处理是给每回合一个自增编号结果返回时先比对编号不一致就丢弃。6.2 调整 AI 难度的三个参数AI 棋力不用改算法三个参数就能调参数作用推荐范围搜索深度 depth推算步数越多越强1~4走法排序开关影响剪枝效率和棋风建议开启子力价值表不同棋子的重视程度按需微调深度是决定棋力的首要参数也直接决定耗时。把它做成线程安全的一个成员变量就能在菜单里加难度选项简单1、中等2、困难3。价值表微调更偏个人感受把过河兵价值提到 200AI 会更积极送兵过河把炮价值调低AI 会主动减少兑炮。我最初把 AI 深度设到 5界面假死、一步棋等半分钟后来固定深度 3 走法排序 子线程体验才稳定下来。拿到这套源码后建议先跑一局默认参数再按这张表调难度感受会比直接改代码直观得多。从那以后我每次给 Qt 项目加计算密集逻辑都先问自己一句这个循环能不能放进工作线程希望帮到你。本文还有配套的精品资源点击获取

相关新闻

基于OpenCV与Python的智能监考系统:人脸识别与行为检测实战

基于OpenCV与Python的智能监考系统:人脸识别与行为检测实战

简介:基于PythonOpenCV的智能监考系统源码,是一套面向计算机相关专业毕业设计的高分完整项目,适合正在准备毕设、课程设计或期末大作业的学生,也适合需要项目实战练习的开发者。系统围绕考场环境下的身份验证、人脸识别、行为异常…

2026/10/11 17:13:08 阅读更多 →
光伏仿真软件PVSYST实操指南:从组件建模到发电量预测

光伏仿真软件PVSYST实操指南:从组件建模到发电量预测

简介:这是一份PVSYST光伏系统设计软件的入门操作教程PPT,适合光伏系统设计人员、新能源专业学生及零基础学习者,用于快速掌握从项目选址、组件排布、参数设置到发电量模拟的完整流程。教程为单个PPT文件,约2.88MB,内容…

2026/10/11 17:13:08 阅读更多 →
基于4000张杂草数据集的YOLO训练与田间部署实战

基于4000张杂草数据集的YOLO训练与田间部署实战

简介:这份资源面向从事农业智能识别、计算机视觉方向的学生与算法工程师,提供一套可直接投入训练的YOLO杂草检测数据集,用于解决田间杂草与作物区分、目标检测模型训练等实际问题。压缩包共约2000个文件,以xml格式的VOC标注文件为…

2026/10/11 17:13:08 阅读更多 →

最新新闻

Spring Boot + Vue民宿预订网站全栈开发实战与部署指南

Spring Boot + Vue民宿预订网站全栈开发实战与部署指南

1. 项目概述手记做民宿房源预订网站,这几年算是个非常典型的全栈练手项目,同时也是很多毕业设计、个人作品集里的常客。市面上类似的系统不少,但大多数要么只停留在管理后台,要么前端拿模板硬套,真正能做到前后端分离、…

2026/10/11 18:06:40 阅读更多 →
输电线路分布式故障诊断系统合规设计指南

输电线路分布式故障诊断系统合规设计指南

简介:本资源为《国家标准 输电线路分布式故障诊断系统(征求意见稿)》正式文本,面向电力系统设计、运维、检测及标准研究领域的工程师、科研人员与高校师生,旨在支撑高电压等级输电线路故障快速定位与智能诊断技术的规范…

2026/10/11 18:06:40 阅读更多 →
SQL数据库课程设计宾馆房间管理系统:从ER图到窗口函数的完整落地

SQL数据库课程设计宾馆房间管理系统:从ER图到窗口函数的完整落地

简介:《SQL数据库课程设计宾馆房间管理系统.doc》是面向软件工程专业学生的课程设计参考文档,以宾馆客房管理为业务场景,完整演示从需求分析、概念结构设计、逻辑/物理设计到SQL Server 2000建库建表及C#.NET程序实现的全过程。文档包含数据流…

2026/10/11 18:06:40 阅读更多 →
编译原理实验:词法分析与语法分析器从零实现指南

编译原理实验:词法分析与语法分析器从零实现指南

简介:面向编译原理课程实验的词法分析与语法分析报告,系统讲解单词识别原理、状态图设计以及LL(1)语法分析表构造。资源围绕标识符、关键字、十进制整数、运算符和分隔符的识别展开,给出使用C语言实现的扫描函数完整代码,并以表达…

2026/10/11 18:06:40 阅读更多 →
Spring Boot+Vue前后端分离旅游订票系统实战:从库存防超卖到订单状态机

Spring Boot+Vue前后端分离旅游订票系统实战:从库存防超卖到订单状态机

上个季度我完整做了一个“旅游线路展示 在线订票”的前后端分离项目:Spring Boot 做后端接口,Vue 做前端页面,整个系统包含线路浏览、景点详情、日期团期选择、订单提交、支付状态回跳、后台线路维护这些核心环节。项目不大,但业…

2026/10/11 18:06:40 阅读更多 →
Linux线程同步指南:从互斥锁、条件变量到生产者消费者模型

Linux线程同步指南:从互斥锁、条件变量到生产者消费者模型

1. 一条计数器的崩溃现场:竞态条件到底怎么回事上一周我在调一个批量图片压缩工具,开了四个线程同时去处理任务队列,结果跑出来的图片里有好几张是花的,还有一次直接段错误。我排查了很久,最后定位到问题根源不在压缩算…

2026/10/11 18:05:40 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →